From e14be4460b532c1805471eb8174b1b0d304d1682 Mon Sep 17 00:00:00 2001 From: Pedro Igor Date: Fri, 5 May 2017 22:48:51 -0300 Subject: [PATCH] [KEYCLOAK-4867] - Cluster events and invalidations --- .../infinispan/AbstractCachedStore.java | 73 ++++++ .../infinispan/CachedPolicyStore.java | 237 +++++++++++------- .../infinispan/CachedResourceServerStore.java | 88 +++---- .../infinispan/CachedResourceStore.java | 199 +++++++-------- .../infinispan/CachedScopeStore.java | 142 ++++++----- .../InfinispanStoreFactoryProvider.java | 58 ++++- .../InfinispanStoreProviderFactory.java | 32 ++- .../infinispan/StoreFactoryCacheManager.java | 72 ++++++ .../AuthorizationInvalidationEvent.java | 44 ++++ .../events/ResourceServerRemovedEvent.java | 36 +++ .../jpa/entities/PolicyEntity.java | 4 +- .../authorization/store/PolicyStore.java | 9 - .../keycloak-infinispan.xml | 4 +- .../keycloak-infinispan2.xml | 4 +- 14 files changed, 662 insertions(+), 340 deletions(-) create mode 100644 model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java create mode 100644 model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java new file mode 100644 index 0000000000..90fe8b636c --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/AbstractCachedStore.java @@ -0,0 +1,73 @@ +/* + * Copyright 2016 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.authorization.infinispan; + +import java.util.Arrays; +import java.util.List; + +import org.keycloak.authorization.store.StoreFactory; + +/** + * @author Pedro Igor + */ +public abstract class AbstractCachedStore { + + private final InfinispanStoreFactoryProvider cacheStoreFactory; + private final StoreFactory storeFactory; + + AbstractCachedStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) { + this.cacheStoreFactory = cacheStoreFactory; + this.storeFactory = storeFactory; + } + + protected void addInvalidation(String cacheKeyForPolicy) { + getCachedStoreFactory().addInvalidation(cacheKeyForPolicy); + } + + protected E putCacheEntry(String resourceServerId, String cacheKeyForPolicy, E cachedPolicy) { + cacheStoreFactory.putCacheEntry(resourceServerId, cacheKeyForPolicy, Arrays.asList(cachedPolicy)); + return cachedPolicy; + } + + protected List resolveCacheEntry(String resourceServerId, String cacheKeyForPolicy) { + return cacheStoreFactory.resolveCachedEntry(resourceServerId, cacheKeyForPolicy); + } + + protected void removeCachedEntry(String resourceServerId, String key) { + getCachedStoreFactory().removeCachedEntry(resourceServerId, key); + } + + protected void invalidate(String resourceServerId) { + cacheStoreFactory.invalidate(resourceServerId); + } + + protected StoreFactory getStoreFactory() { + return this.storeFactory; + } + + protected boolean isInvalid(String cacheKey) { + return cacheStoreFactory.isInvalid(cacheKey); + } + + protected InfinispanStoreFactoryProvider.CacheTransaction getTransaction() { + return cacheStoreFactory.getTransaction(); + } + + protected InfinispanStoreFactoryProvider getCachedStoreFactory() { + return cacheStoreFactory; + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java index b517098951..7defaab8c7 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedPolicyStore.java @@ -30,18 +30,13 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; -import org.infinispan.Cache; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.PolicyStore; import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.connections.infinispan.InfinispanConnectionProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedPolicy; -import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; import org.keycloak.representations.idm.authorization.AbstractPolicyRepresentation; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.representations.idm.authorization.Logic; @@ -49,28 +44,15 @@ import org.keycloak.representations.idm.authorization.Logic; /** * @author Pedro Igor */ -public class CachedPolicyStore implements PolicyStore { +public class CachedPolicyStore extends AbstractCachedStore implements PolicyStore { - private static final String POLICY_ID_CACHE_PREFIX = "policy-id-"; + private static final String POLICY_CACHE_PREFIX = "pc-"; - private final Cache>> cache; - private final CachedStoreFactoryProvider cacheStoreFactory; - private final CacheTransaction transaction; - private final List cacheKeys; - private final StoreFactory storeFactory; private PolicyStore delegate; - public CachedPolicyStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { - this.cacheStoreFactory = cacheStoreFactory; - this.transaction = transaction; - InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); - this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); - cacheKeys = new ArrayList<>(); - cacheKeys.add("findByResource"); - cacheKeys.add("findByResourceType"); - cacheKeys.add("findByScopeIds"); - cacheKeys.add("findByType"); - this.storeFactory = delegate; + public CachedPolicyStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) { + super(cacheStoreFactory, storeFactory); + this.delegate = storeFactory.getPolicyStore(); } @Override @@ -78,13 +60,11 @@ public class CachedPolicyStore implements PolicyStore { Policy policy = getDelegate().create(representation, getStoreFactory().getResourceServerStore().findById(resourceServer.getId())); String id = policy.getId(); - this.transaction.whenRollback(() -> { - resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForPolicy(id)); - }); + addInvalidation(getCacheKeyForPolicy(policy.getId())); + addInvalidation(getCacheKeyForPolicyName(policy.getName())); + addInvalidation(getCacheKeyForPolicyType(policy.getType())); - this.transaction.whenCommit(() -> { - invalidateCache(resourceServer.getId()); - }); + configureTransaction(resourceServer, id); return createAdapter(new CachedPolicy(policy)); } @@ -95,9 +75,13 @@ public class CachedPolicyStore implements PolicyStore { if (policy == null) { return; } - ResourceServer resourceServer = policy.getResourceServer(); + + addInvalidation(getCacheKeyForPolicy(policy.getId())); + addInvalidation(getCacheKeyForPolicyName(policy.getName())); + addInvalidation(getCacheKeyForPolicyType(policy.getType())); + getDelegate().delete(id); - invalidateCache(resourceServer.getId()); + configureTransaction(policy.getResourceServer(), policy.getId()); } @Override @@ -106,27 +90,43 @@ public class CachedPolicyStore implements PolicyStore { return getDelegate().findById(id, null); } + if (isInvalid(getCacheKeyForPolicy(id))) { + return getDelegate().findById(id, resourceServerId); + } + String cacheKeyForPolicy = getCacheKeyForPolicy(id); - List cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForPolicy); + List cached = resolveCacheEntry(resourceServerId, cacheKeyForPolicy); if (cached == null) { Policy policy = getDelegate().findById(id, resourceServerId); if (policy != null) { - CachedPolicy cachedPolicy = new CachedPolicy(policy); - resolveResourceServerCache(resourceServerId).put(cacheKeyForPolicy, Arrays.asList(cachedPolicy)); - return createAdapter(cachedPolicy); + return createAdapter(putCacheEntry(resourceServerId, cacheKeyForPolicy, new CachedPolicy(policy))); } return null; } - return createAdapter(cached.get(0)); + return createAdapter(CachedPolicy.class.cast(cached.get(0))); } @Override public Policy findByName(String name, String resourceServerId) { - return getDelegate().findByName(name, resourceServerId); + String cacheKey = getCacheKeyForPolicyName(name); + + if (isInvalid(cacheKey)) { + return getDelegate().findByName(name, resourceServerId); + } + + return cacheResult(resourceServerId, cacheKey, () -> { + Policy policy = getDelegate().findByName(name, resourceServerId); + + if (policy == null) { + return Collections.emptyList(); + } + + return Arrays.asList(policy); + }).stream().findFirst().orElse(null); } @Override @@ -141,12 +141,24 @@ public class CachedPolicyStore implements PolicyStore { @Override public List findByResource(String resourceId, String resourceServerId) { - return cacheResult(resourceServerId, new StringBuilder("findByResource").append(resourceId).toString(), () -> getDelegate().findByResource(resourceId, resourceServerId)); + String cacheKey = getCacheKeyForResource(resourceId); + + if (isInvalid(cacheKey)) { + return getDelegate().findByResource(resourceId, resourceServerId); + } + + return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByResource(resourceId, resourceServerId)); } @Override public List findByResourceType(String resourceType, String resourceServerId) { - return cacheResult(resourceServerId, new StringBuilder("findByResourceType").append(resourceType).toString(), () -> getDelegate().findByResourceType(resourceType, resourceServerId)); + String cacheKey = getCacheKeyForResourceType(resourceType); + + if (isInvalid(cacheKey)) { + return getDelegate().findByResourceType(resourceType, resourceServerId); + } + + return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByResourceType(resourceType, resourceServerId)); } @Override @@ -154,7 +166,13 @@ public class CachedPolicyStore implements PolicyStore { List policies = new ArrayList<>(); for (String scopeId : scopeIds) { - policies.addAll(cacheResult(resourceServerId, new StringBuilder("findByScopeIds").append(scopeId).toString(), () -> getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId))); + String cacheKey = getCacheForScope(scopeId); + + if (isInvalid(cacheKey)) { + policies.addAll(getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId)); + } else { + policies.addAll(cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByScopeIds(Arrays.asList(scopeId), resourceServerId))); + } } return policies; @@ -162,7 +180,13 @@ public class CachedPolicyStore implements PolicyStore { @Override public List findByType(String type, String resourceServerId) { - return cacheResult(resourceServerId, new StringBuilder("findByType").append(type).toString(), () -> getDelegate().findByType(type, resourceServerId)); + String cacheKey = getCacheKeyForPolicyType(type); + + if (isInvalid(cacheKey)) { + return getDelegate().findByType(type, resourceServerId); + } + + return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByType(type, resourceServerId)); } @Override @@ -170,35 +194,24 @@ public class CachedPolicyStore implements PolicyStore { return getDelegate().findDependentPolicies(id, resourceServerId); } - @Override - public void notifyChange(Object cached) { - String resourceServerId; - - if (Resource.class.isInstance(cached)) { - resourceServerId = ((Resource) cached).getResourceServer().getId(); - } else if (Scope.class.isInstance(cached)){ - resourceServerId = ((Scope) cached).getResourceServer().getId(); - } else { - throw new RuntimeException("Unexpected notification [" + cached + "]"); - } - - invalidateCache(resourceServerId); + private String getCacheKeyForPolicy(String id) { + return new StringBuilder().append(POLICY_CACHE_PREFIX).append("id-").append(id).toString(); } - private String getCacheKeyForPolicy(String policyId) { - return POLICY_ID_CACHE_PREFIX + policyId; + private String getCacheKeyForPolicyType(String type) { + return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByType-").append(type).toString(); } - private StoreFactory getStoreFactory() { - return this.storeFactory; + private String getCacheKeyForPolicyName(String name) { + return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByName-").append(name).toString(); } - private PolicyStore getDelegate() { - if (this.delegate == null) { - this.delegate = getStoreFactory().getPolicyStore(); - } + private String getCacheKeyForResourceType(String resourceType) { + return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByResourceType-").append(resourceType).toString(); + } - return this.delegate; + private String getCacheForScope(String scopeId) { + return new StringBuilder().append(POLICY_CACHE_PREFIX).append("findByScopeIds-").append(scopeId).toString(); } private Policy createAdapter(CachedPolicy cached) { @@ -243,11 +256,21 @@ public class CachedPolicyStore implements PolicyStore { @Override public Map getConfig() { - return cached.getConfig(); + return new HashMap<>(cached.getConfig()); } @Override public void setConfig(Map config) { + String resourceType = config.get("defaultResourceType"); + + if (resourceType != null) { + addInvalidation(getCacheKeyForResourceType(resourceType)); + String cachedResourceType = cached.getConfig().get("defaultResourceType"); + if (cachedResourceType != null && !resourceType.equals(cachedResourceType)) { + addInvalidation(getCacheKeyForResourceType(cachedResourceType)); + } + } + getDelegateForUpdate().setConfig(config); cached.setConfig(config); } @@ -259,6 +282,8 @@ public class CachedPolicyStore implements PolicyStore { @Override public void setName(String name) { + addInvalidation(getCacheKeyForPolicyName(name)); + addInvalidation(getCacheKeyForPolicyName(cached.getName())); getDelegateForUpdate().setName(name); cached.setName(name); } @@ -281,13 +306,18 @@ public class CachedPolicyStore implements PolicyStore { @Override public void addScope(Scope scope) { - getDelegateForUpdate().addScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())); + Scope model = getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId()); + addInvalidation(getCacheForScope(model.getId())); + getDelegateForUpdate().addScope(model); cached.addScope(scope); + scopes.add(scope); } @Override public void removeScope(Scope scope) { - getDelegateForUpdate().removeScope(getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())); + Scope model = getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId()); + addInvalidation(getCacheForScope(scope.getId())); + getDelegateForUpdate().removeScope(model); cached.removeScope(scope); scopes.remove(scope); } @@ -307,20 +337,37 @@ public class CachedPolicyStore implements PolicyStore { @Override public void addResource(Resource resource) { - getDelegateForUpdate().addResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId())); + Resource model = getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId()); + + addInvalidation(getCacheKeyForResource(model.getId())); + + if (model.getType() != null) { + addInvalidation(getCacheKeyForResourceType(model.getType())); + } + + getDelegateForUpdate().addResource(model); cached.addResource(resource); + resources.add(resource); } @Override public void removeResource(Resource resource) { - getDelegateForUpdate().removeResource(getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId())); + Resource model = getStoreFactory().getResourceStore().findById(resource.getId(), cached.getResourceServerId()); + + addInvalidation(getCacheKeyForResource(model.getId())); + + if (model.getType() != null) { + addInvalidation(getCacheKeyForResourceType(model.getType())); + } + + getDelegateForUpdate().removeResource(model); cached.removeResource(resource); resources.remove(resource); } @Override public Set getAssociatedPolicies() { - if (associatedPolicies == null) { + if (associatedPolicies == null || updated != null) { associatedPolicies = new HashSet<>(); for (String id : cached.getAssociatedPoliciesIds()) { @@ -337,7 +384,7 @@ public class CachedPolicyStore implements PolicyStore { @Override public Set getResources() { - if (resources == null) { + if (resources == null || updated != null) { resources = new HashSet<>(); for (String id : cached.getResourcesIds()) { @@ -354,7 +401,7 @@ public class CachedPolicyStore implements PolicyStore { @Override public Set getScopes() { - if (scopes == null) { + if (scopes == null || updated != null) { scopes = new HashSet<>(); for (String id : cached.getScopesIds()) { @@ -394,12 +441,8 @@ public class CachedPolicyStore implements PolicyStore { if (this.updated == null) { this.updated = getDelegate().findById(getId(), cached.getResourceServerId()); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> { - invalidateCache(cached.getResourceServerId()); - }); - transaction.whenRollback(() -> { - resolveResourceServerCache(cached.getResourceServerId()).remove(getCacheKeyForPolicy(getId())); - }); + addInvalidation(getCacheKeyForPolicy(updated.getId())); + configureTransaction(updated.getResourceServer(), updated.getId()); } return this.updated; @@ -407,33 +450,51 @@ public class CachedPolicyStore implements PolicyStore { }; } - private CachedStoreFactoryProvider getCachedStoreFactory() { - return cacheStoreFactory; - } - - private void invalidateCache(String resourceServerId) { - cache.remove(resourceServerId); + private String getCacheKeyForResource(String resourceId) { + return new StringBuilder("findByResource").append(resourceId).toString(); } private List cacheResult(String resourceServerId, String key, Supplier> provider) { - List cached = resolveResourceServerCache(resourceServerId).computeIfAbsent(key, (Function>) o -> { + List cached = getCachedStoreFactory().computeIfCachedEntryAbsent(resourceServerId, key, (Function>) o -> { List result = provider.get(); if (result.isEmpty()) { return Collections.emptyList(); } - return result.stream().map(policy -> new CachedPolicy(policy)).collect(Collectors.toList()); + return result.stream().map(policy -> policy.getId()).collect(Collectors.toList()); }); if (cached == null) { return Collections.emptyList(); } - return cached.stream().map(cachedPolicy -> createAdapter(cachedPolicy)).collect(Collectors.toList()); + return cached.stream().map(id -> findById(id.toString(), resourceServerId)).collect(Collectors.toList()); } - private Map> resolveResourceServerCache(String id) { - return cache.computeIfAbsent(id, key -> new HashMap<>()); + private void configureTransaction(ResourceServer resourceServer, String id) { + getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForPolicy(id))); + getTransaction().whenCommit(() -> invalidate(resourceServer.getId())); + } + + private PolicyStore getDelegate() { + return delegate; + } + + void addInvalidations(Object object) { + if (Resource.class.isInstance(object)) { + Resource resource = (Resource) object; + addInvalidation(getCacheKeyForResource(resource.getId())); + String type = resource.getType(); + + if (type != null) { + addInvalidation(getCacheKeyForResourceType(type)); + } + } else if (Scope.class.isInstance(object)) { + Scope scope = (Scope) object; + addInvalidation(getCacheForScope(scope.getId())); + } else { + throw new RuntimeException("Unexpected notification [" + object + "]"); + } } } \ No newline at end of file diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java index a98a34e6c5..6322843fb5 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceServerStore.java @@ -18,46 +18,34 @@ package org.keycloak.models.authorization.infinispan; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import org.infinispan.Cache; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.connections.infinispan.InfinispanConnectionProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedResourceServer; import org.keycloak.representations.idm.authorization.PolicyEnforcementMode; /** * @author Pedro Igor */ -public class CachedResourceServerStore implements ResourceServerStore { +public class CachedResourceServerStore extends AbstractCachedStore implements ResourceServerStore { - private static final String RS_ID_CACHE_PREFIX = "rs-id-"; - private static final String RS_CLIENT_ID_CACHE_PREFIX = "rs-client-id-"; + private static final String RS_PREFIX = "rs-"; - private final CacheTransaction transaction; - private StoreFactory storeFactory; - private ResourceServerStore delegate; - private final Cache>> cache; + private final ResourceServerStore delegate; - public CachedResourceServerStore(KeycloakSession session, CacheTransaction transaction, StoreFactory delegate) { - this.transaction = transaction; - InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); - this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); - this.storeFactory = delegate; + public CachedResourceServerStore(InfinispanStoreFactoryProvider cachedStoreFactory, StoreFactory storeFactory) { + super(cachedStoreFactory, storeFactory); + this.delegate = storeFactory.getResourceServerStore(); } @Override public ResourceServer create(String clientId) { ResourceServer resourceServer = getDelegate().create(clientId); - this.transaction.whenRollback(() -> resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForResourceServer(resourceServer.getId()))); + getTransaction().whenCommit(() -> getCachedStoreFactory().removeEntries(resourceServer)); + getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForResourceServer(resourceServer.getId()))); return createAdapter(new CachedResourceServer(resourceServer)); } @@ -65,71 +53,71 @@ public class CachedResourceServerStore implements ResourceServerStore { @Override public void delete(String id) { ResourceServer resourceServer = getDelegate().findById(id); - getDelegate().delete(id); - this.transaction.whenCommit(() -> { - cache.remove(id); - cache.remove(resourceServer.getClientId()); - }); + + if (resourceServer != null) { + getDelegate().delete(id); + getTransaction().whenCommit(() -> getCachedStoreFactory().removeEntries(resourceServer)); + } } @Override public ResourceServer findById(String id) { - String cacheKeyForResourceServer = getCacheKeyForResourceServer(id); - List cached = resolveResourceServerCache(id).get(cacheKeyForResourceServer); + String cacheKey = getCacheKeyForResourceServer(id); + + if (isInvalid(cacheKey)) { + return getDelegate().findById(id); + } + + List cached = resolveCacheEntry(id, cacheKey); if (cached == null) { ResourceServer resourceServer = getDelegate().findById(id); if (resourceServer != null) { - CachedResourceServer cachedResourceServer = new CachedResourceServer(resourceServer); - resolveResourceServerCache(id).put(cacheKeyForResourceServer, Arrays.asList(cachedResourceServer)); - return createAdapter(cachedResourceServer); + return createAdapter(putCacheEntry(id, cacheKey, new CachedResourceServer(resourceServer))); } return null; } - return createAdapter(cached.get(0)); + return createAdapter(CachedResourceServer.class.cast(cached.get(0))); } @Override public ResourceServer findByClient(String id) { - String cacheKeyForResourceServer = getCacheKeyForResourceServerClientId(id); - List cached = resolveResourceServerCache(id).get(cacheKeyForResourceServer); + String cacheKey = getCacheKeyForResourceServerClientId(id); + + if (isInvalid(cacheKey)) { + return getDelegate().findByClient(id); + } + + List cached = resolveCacheEntry(id, cacheKey); if (cached == null) { ResourceServer resourceServer = getDelegate().findByClient(id); if (resourceServer != null) { - resolveResourceServerCache(cacheKeyForResourceServer).put(cacheKeyForResourceServer, Arrays.asList(new CachedResourceServer(resourceServer))); - return findById(resourceServer.getId()); + return findById(putCacheEntry(id, cacheKey, resourceServer.getId())); } return null; } - return createAdapter(cached.get(0)); + return findById(cached.get(0).toString()); } private String getCacheKeyForResourceServer(String id) { - return RS_ID_CACHE_PREFIX + id; + return new StringBuilder(RS_PREFIX).append("id-").append(id).toString(); } private String getCacheKeyForResourceServerClientId(String id) { - return RS_CLIENT_ID_CACHE_PREFIX + id; + return new StringBuilder(RS_PREFIX).append("findByClientId-").append(id).toString(); } private ResourceServerStore getDelegate() { - if (this.delegate == null) { - this.delegate = getStoreFactory().getResourceServerStore(); - } - return this.delegate; } - private StoreFactory getStoreFactory() { - return this.storeFactory; - } private ResourceServer createAdapter(ResourceServer cached) { return new ResourceServer() { @@ -171,9 +159,9 @@ public class CachedResourceServerStore implements ResourceServerStore { if (this.updated == null) { this.updated = getDelegate().findById(getId()); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> { - cache.remove(getId()); - cache.remove(getClientId()); + addInvalidation(getCacheKeyForResourceServer(updated.getId())); + getTransaction().whenCommit(() -> { + invalidate(updated.getId()); }); } @@ -181,8 +169,4 @@ public class CachedResourceServerStore implements ResourceServerStore { } }; } - - private Map> resolveResourceServerCache(String id) { - return cache.computeIfAbsent(id, key -> new HashMap<>()); - } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java index e820ab0ffa..d94ccd8a4c 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedResourceStore.java @@ -21,7 +21,6 @@ package org.keycloak.models.authorization.infinispan; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -29,56 +28,37 @@ import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; -import org.infinispan.Cache; import org.keycloak.authorization.model.Resource; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.connections.infinispan.InfinispanConnectionProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedResource; -import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; /** * @author Pedro Igor */ -public class CachedResourceStore implements ResourceStore { +public class CachedResourceStore extends AbstractCachedStore implements ResourceStore { - private static final String RESOURCE_ID_CACHE_PREFIX = "rsc-id-"; - private static final String RESOURCE_NAME_CACHE_PREFIX = "rsc-name-"; + private static final String RESOURCE_CACHE_PREFIX = "rs-"; - private final CachedStoreFactoryProvider cacheStoreFactory; - private final CacheTransaction transaction; - private final List cacheKeys; - private StoreFactory delegateStoreFactory; private ResourceStore delegate; - private final Cache>> cache; - public CachedResourceStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { - this.cacheStoreFactory = cacheStoreFactory; - InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); - this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); - this.transaction = transaction; - cacheKeys = new ArrayList<>(); - cacheKeys.add("findByOwner"); - cacheKeys.add("findByUri"); - cacheKeys.add("findByName"); - this.delegateStoreFactory = delegate; + public CachedResourceStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) { + super(cacheStoreFactory, storeFactory); + delegate = storeFactory.getResourceStore(); } @Override public Resource create(String name, ResourceServer resourceServer, String owner) { - Resource resource = getDelegate().create(name, getDelegateStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); + Resource resource = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId()), owner); - this.transaction.whenRollback(() -> { - resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForResource(resource.getId())); - }); + addInvalidation(getCacheKeyForResource(resource.getId())); + addInvalidation(getCacheKeyForResourceName(resource.getName())); + getCachedStoreFactory().getPolicyStore().addInvalidations(resource); - this.transaction.whenCommit(() -> { - invalidateCache(resourceServer.getId()); - }); + getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForResource(resource.getId()))); + getTransaction().whenCommit(() -> invalidate(resourceServer.getId())); return createAdapter(new CachedResource(resource)); } @@ -86,44 +66,69 @@ public class CachedResourceStore implements ResourceStore { @Override public void delete(String id) { Resource resource = getDelegate().findById(id, null); + if (resource == null) { return; } + ResourceServer resourceServer = resource.getResourceServer(); + + addInvalidation(getCacheKeyForResource(resource.getId())); + addInvalidation(getCacheKeyForResourceName(resource.getName())); + addInvalidation(getCacheKeyForOwner(resource.getOwner())); + addInvalidation(getCacheKeyForUri(resource.getUri())); + getCachedStoreFactory().getPolicyStore().addInvalidations(resource); + getDelegate().delete(id); - this.transaction.whenCommit(() -> { - invalidateCache(resourceServer.getId()); + + getTransaction().whenCommit(() -> { + invalidate(resourceServer.getId()); }); } @Override public Resource findById(String id, String resourceServerId) { String cacheKeyForResource = getCacheKeyForResource(id); - List cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForResource); + + if (isInvalid(cacheKeyForResource)) { + return getDelegate().findById(id, resourceServerId); + } + + List cached = resolveCacheEntry(resourceServerId, cacheKeyForResource); if (cached == null) { Resource resource = getDelegate().findById(id, resourceServerId); if (resource != null) { - CachedResource cachedResource = new CachedResource(resource); - resolveResourceServerCache(resourceServerId).put(cacheKeyForResource, Arrays.asList(cachedResource)); - return createAdapter(cachedResource); + return createAdapter(putCacheEntry(resourceServerId, cacheKeyForResource, new CachedResource(resource))); } return null; } - return createAdapter(cached.get(0)); + return createAdapter(CachedResource.class.cast(cached.get(0))); } @Override public List findByOwner(String ownerId, String resourceServerId) { - return cacheResult(resourceServerId, new StringBuilder("findByOwner").append(ownerId).toString(), () -> getDelegate().findByOwner(ownerId, resourceServerId)); + String cacheKey = getCacheKeyForOwner(ownerId); + + if (isInvalid(cacheKey)) { + return getDelegate().findByOwner(ownerId, resourceServerId); + } + + return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByOwner(ownerId, resourceServerId)); } @Override public List findByUri(String uri, String resourceServerId) { - return cacheResult(resourceServerId, new StringBuilder("findByUri").append(uri).toString(), () -> getDelegate().findByUri(uri, resourceServerId)); + String cacheKey = getCacheKeyForUri(uri); + + if (isInvalid(cacheKey)) { + return getDelegate().findByUri(uri, resourceServerId); + } + + return cacheResult(resourceServerId, cacheKey, () -> getDelegate().findByUri(uri, resourceServerId)); } @Override @@ -143,22 +148,21 @@ public class CachedResourceStore implements ResourceStore { @Override public Resource findByName(String name, String resourceServerId) { - String cacheKeyForResource = getCacheKeyForResourceName(name, resourceServerId); - List cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForResource); + String cacheKey = getCacheKeyForResourceName(name); - if (cached == null) { - Resource resource = getDelegate().findByName(name, resourceServerId); - - if (resource != null) { - invalidateCache(resourceServerId); - resolveResourceServerCache(resourceServerId).put(cacheKeyForResource, Arrays.asList(new CachedResource(resource))); - return findById(resource.getId(), resourceServerId); - } - - return null; + if (isInvalid(cacheKey)) { + return getDelegate().findByName(name, resourceServerId); } - return createAdapter(cached.get(0)); + return cacheResult(resourceServerId, cacheKey, () -> { + Resource resource = getDelegate().findByName(name, resourceServerId); + + if (resource == null) { + return Collections.emptyList(); + } + + return Arrays.asList(resource); + }).stream().findFirst().orElse(null); } @Override @@ -167,23 +171,41 @@ public class CachedResourceStore implements ResourceStore { } private String getCacheKeyForResource(String id) { - return RESOURCE_ID_CACHE_PREFIX + id; + return new StringBuilder(RESOURCE_CACHE_PREFIX).append("id-").append(id).toString(); } - private String getCacheKeyForResourceName(String name, String resourceServerId) { - return RESOURCE_NAME_CACHE_PREFIX + name + "-" + resourceServerId; + private String getCacheKeyForResourceName(String name) { + return new StringBuilder(RESOURCE_CACHE_PREFIX).append("findByName-").append(name).toString(); + } + + private String getCacheKeyForOwner(String name) { + return new StringBuilder(RESOURCE_CACHE_PREFIX).append("findByOwner-").append(name).toString(); + } + + private String getCacheKeyForUri(String uri) { + return new StringBuilder(RESOURCE_CACHE_PREFIX).append("findByUri-").append(uri).toString(); } private ResourceStore getDelegate() { - if (this.delegate == null) { - this.delegate = getDelegateStoreFactory().getResourceStore(); - } - return this.delegate; } - private StoreFactory getDelegateStoreFactory() { - return this.delegateStoreFactory; + private List cacheResult(String resourceServerId, String key, Supplier> provider) { + List cached = getCachedStoreFactory().computeIfCachedEntryAbsent(resourceServerId, key, (Function>) o -> { + List result = provider.get(); + + if (result.isEmpty()) { + return Collections.emptyList(); + } + + return result.stream().map(policy -> policy.getId()).collect(Collectors.toList()); + }); + + if (cached == null) { + return Collections.emptyList(); + } + + return cached.stream().map(id -> findById(id.toString(), resourceServerId)).collect(Collectors.toList()); } private Resource createAdapter(CachedResource cached) { @@ -204,6 +226,8 @@ public class CachedResourceStore implements ResourceStore { @Override public void setName(String name) { + addInvalidation(getCacheKeyForResourceName(name)); + addInvalidation(getCacheKeyForResourceName(cached.getName())); getDelegateForUpdate().setName(name); cached.setName(name); } @@ -215,6 +239,8 @@ public class CachedResourceStore implements ResourceStore { @Override public void setUri(String uri) { + addInvalidation(getCacheKeyForUri(uri)); + addInvalidation(getCacheKeyForUri(cached.getUri())); getDelegateForUpdate().setUri(uri); cached.setUri(uri); } @@ -226,6 +252,7 @@ public class CachedResourceStore implements ResourceStore { @Override public void setType(String type) { + getCachedStoreFactory().getPolicyStore().addInvalidations(cached); getDelegateForUpdate().setType(type); cached.setType(type); } @@ -270,7 +297,7 @@ public class CachedResourceStore implements ResourceStore { @Override public void updateScopes(Set scopes) { - getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getDelegateStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); + getDelegateForUpdate().updateScopes(scopes.stream().map(scope -> getStoreFactory().getScopeStore().findById(scope.getId(), cached.getResourceServerId())).collect(Collectors.toSet())); cached.updateScopes(scopes); } @@ -279,52 +306,14 @@ public class CachedResourceStore implements ResourceStore { String resourceServerId = cached.getResourceServerId(); this.updated = getDelegate().findById(getId(), resourceServerId); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> { - invalidateCache(resourceServerId); - }); - transaction.whenRollback(() -> { - resolveResourceServerCache(resourceServerId).remove(getCacheKeyForResource(cached.getId())); - }); + addInvalidation(getCacheKeyForResource(updated.getId())); + getCachedStoreFactory().getPolicyStore().addInvalidations(updated); + getTransaction().whenCommit(() -> invalidate(resourceServerId)); + getTransaction().whenRollback(() -> removeCachedEntry(resourceServerId, getCacheKeyForResource(cached.getId()))); } return this.updated; } }; } - - private CachedStoreFactoryProvider getCachedStoreFactory() { - return cacheStoreFactory; - } - - private List cacheResult(String resourceServerId, String key, Supplier> provider) { - List cached = resolveResourceServerCache(resourceServerId).computeIfAbsent(key, (Function>) o -> { - List result = provider.get(); - - if (result.isEmpty()) { - return null; - } - - return result.stream().map(resource -> new CachedResource(resource)).collect(Collectors.toList()); - }); - - if (cached == null) { - return Collections.emptyList(); - } - - List adapters = new ArrayList<>(); - - for (CachedResource resource : cached) { - adapters.add(createAdapter(resource)); - } - - return adapters; - } - - private void invalidateCache(String resourceServerId) { - cache.remove(resourceServerId); - } - - private Map> resolveResourceServerCache(String id) { - return cache.computeIfAbsent(id, key -> new HashMap<>()); - } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java index fb878d0a66..741f5f716c 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/CachedScopeStore.java @@ -19,52 +19,44 @@ package org.keycloak.models.authorization.infinispan; import java.util.Arrays; -import java.util.HashMap; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; -import org.infinispan.Cache; import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.model.Scope; import org.keycloak.authorization.store.ScopeStore; import org.keycloak.authorization.store.StoreFactory; -import org.keycloak.connections.infinispan.InfinispanConnectionProvider; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.authorization.infinispan.InfinispanStoreFactoryProvider.CacheTransaction; import org.keycloak.models.authorization.infinispan.entities.CachedScope; -import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; /** * @author Pedro Igor */ -public class CachedScopeStore implements ScopeStore { +public class CachedScopeStore extends AbstractCachedStore implements ScopeStore { - private static final String SCOPE_ID_CACHE_PREFIX = "scp-id-"; - private static final String SCOPE_NAME_CACHE_PREFIX = "scp-name-"; + private static final String SCOPE_CACHE_PREFIX = "scp-"; - private final Cache>> cache; - private final CachedStoreFactoryProvider cacheStoreFactory; - private final CacheTransaction transaction; - private ScopeStore delegate; - private StoreFactory storeFactory; + private final ScopeStore delegate; - public CachedScopeStore(KeycloakSession session, CachedStoreFactoryProvider cacheStoreFactory, CacheTransaction transaction, StoreFactory delegate) { - this.cacheStoreFactory = cacheStoreFactory; - this.transaction = transaction; - InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); - this.cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); - this.storeFactory = delegate; + public CachedScopeStore(InfinispanStoreFactoryProvider cacheStoreFactory, StoreFactory storeFactory) { + super(cacheStoreFactory, storeFactory); + this.delegate = storeFactory.getScopeStore(); } @Override public Scope create(String name, ResourceServer resourceServer) { Scope scope = getDelegate().create(name, getStoreFactory().getResourceServerStore().findById(resourceServer.getId())); - this.transaction.whenRollback(() -> resolveResourceServerCache(resourceServer.getId()).remove(getCacheKeyForScope(scope.getId()))); - this.transaction.whenCommit(() -> { - invalidateCache(resourceServer.getId()); - }); + addInvalidation(getCacheKeyForScope(scope.getId())); + addInvalidation(getCacheKeyForScopeName(scope.getName())); + getCachedStoreFactory().getPolicyStore().addInvalidations(scope); + + getTransaction().whenRollback(() -> removeCachedEntry(resourceServer.getId(), getCacheKeyForScope(scope.getId()))); + getTransaction().whenCommit(() -> invalidate(resourceServer.getId())); return createAdapter(new CachedScope(scope)); } @@ -72,53 +64,62 @@ public class CachedScopeStore implements ScopeStore { @Override public void delete(String id) { Scope scope = getDelegate().findById(id, null); + if (scope == null) { return; } + ResourceServer resourceServer = scope.getResourceServer(); + + addInvalidation(getCacheKeyForScope(scope.getId())); + addInvalidation(getCacheKeyForScopeName(scope.getName())); + getCachedStoreFactory().getPolicyStore().addInvalidations(scope); + getDelegate().delete(id); - this.transaction.whenCommit(() -> { - invalidateCache(resourceServer.getId()); - }); + + getTransaction().whenCommit(() -> invalidate(resourceServer.getId())); } @Override public Scope findById(String id, String resourceServerId) { - String cacheKeyForScope = getCacheKeyForScope(id); - List cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForScope); + String cacheKey = getCacheKeyForScope(id); + + if (isInvalid(cacheKey)) { + return getDelegate().findById(id, resourceServerId); + } + + List cached = resolveCacheEntry(resourceServerId, cacheKey); if (cached == null) { Scope scope = getDelegate().findById(id, resourceServerId); if (scope != null) { - CachedScope cachedScope = new CachedScope(scope); - resolveResourceServerCache(resourceServerId).put(cacheKeyForScope, Arrays.asList(cachedScope)); - return createAdapter(cachedScope); + return createAdapter(putCacheEntry(resourceServerId, cacheKey, new CachedScope(scope))); } return null; } - return createAdapter(cached.get(0)); + return createAdapter(CachedScope.class.cast(cached.get(0))); } @Override public Scope findByName(String name, String resourceServerId) { - String cacheKeyForScope = getCacheKeyForScopeName(name); - List cached = resolveResourceServerCache(resourceServerId).get(cacheKeyForScope); + String cacheKey = getCacheKeyForScopeName(name); - if (cached == null) { - Scope scope = getDelegate().findByName(name, resourceServerId); - - if (scope != null) { - resolveResourceServerCache(resourceServerId).put(cacheKeyForScope, Arrays.asList(new CachedScope(scope))); - return findById(scope.getId(), resourceServerId); - } - - return null; + if (isInvalid(cacheKey)) { + return getDelegate().findByName(name, resourceServerId); } - return createAdapter(cached.get(0)); + return cacheResult(resourceServerId, cacheKey, () -> { + Scope scope = getDelegate().findByName(name, resourceServerId); + + if (scope == null) { + return Collections.emptyList(); + } + + return Arrays.asList(scope); + }).stream().findFirst().orElse(null); } @Override @@ -132,23 +133,33 @@ public class CachedScopeStore implements ScopeStore { } private String getCacheKeyForScope(String id) { - return SCOPE_ID_CACHE_PREFIX + id; + return new StringBuilder(SCOPE_CACHE_PREFIX).append("id-").append(id).toString(); } private String getCacheKeyForScopeName(String name) { - return SCOPE_NAME_CACHE_PREFIX + name; + return new StringBuilder(SCOPE_CACHE_PREFIX).append("findByName-").append(name).toString(); } private ScopeStore getDelegate() { - if (this.delegate == null) { - this.delegate = getStoreFactory().getScopeStore(); - } - return this.delegate; } - private StoreFactory getStoreFactory() { - return this.storeFactory; + private List cacheResult(String resourceServerId, String key, Supplier> provider) { + List cached = getCachedStoreFactory().computeIfCachedEntryAbsent(resourceServerId, key, (Function>) o -> { + List result = provider.get(); + + if (result.isEmpty()) { + return Collections.emptyList(); + } + + return result.stream().map(policy -> policy.getId()).collect(Collectors.toList()); + }); + + if (cached == null) { + return Collections.emptyList(); + } + + return cached.stream().map(id -> findById(id.toString(), resourceServerId)).collect(Collectors.toList()); } private Scope createAdapter(CachedScope cached) { @@ -168,6 +179,8 @@ public class CachedScopeStore implements ScopeStore { @Override public void setName(String name) { + addInvalidation(getCacheKeyForScopeName(name)); + addInvalidation(getCacheKeyForScopeName(cached.getName())); getDelegateForUpdate().setName(name); cached.setName(name); } @@ -192,13 +205,10 @@ public class CachedScopeStore implements ScopeStore { if (this.updated == null) { this.updated = getDelegate().findById(getId(), cached.getResourceServerId()); if (this.updated == null) throw new IllegalStateException("Not found in database"); - transaction.whenCommit(() -> { - invalidateCache(cached.getResourceServerId()); - }); - transaction.whenRollback(() -> { - resolveResourceServerCache(cached.getResourceServerId()).remove(getCacheKeyForScope(cached.getId())); - resolveResourceServerCache(cached.getResourceServerId()).remove(getCacheKeyForScopeName(cached.getName())); - }); + addInvalidation(getCacheKeyForScope(updated.getId())); + getCachedStoreFactory().getPolicyStore().addInvalidations(updated); + getTransaction().whenCommit(() -> invalidate(cached.getResourceServerId())); + getTransaction().whenRollback(() -> removeCachedEntry(cached.getResourceServerId(), getCacheKeyForScope(cached.getId()))); } return this.updated; @@ -218,16 +228,4 @@ public class CachedScopeStore implements ScopeStore { } }; } - - private CachedStoreFactoryProvider getCachedStoreFactory() { - return cacheStoreFactory; - } - - private void invalidateCache(String resourceServerId) { - cache.remove(resourceServerId); - } - - private Map> resolveResourceServerCache(String id) { - return cache.computeIfAbsent(id, key -> new HashMap<>()); - } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java index bbc38482b1..692173c4cf 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreFactoryProvider.java @@ -19,9 +19,12 @@ package org.keycloak.models.authorization.infinispan; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.function.Function; -import org.keycloak.authorization.store.PolicyStore; +import org.keycloak.authorization.model.ResourceServer; import org.keycloak.authorization.store.ResourceServerStore; import org.keycloak.authorization.store.ResourceStore; import org.keycloak.authorization.store.ScopeStore; @@ -39,16 +42,21 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide private final CachedResourceStore resourceStore; private final CachedScopeStore scopeStore; private final CachedPolicyStore policyStore; + private final KeycloakSession session; + private final StoreFactoryCacheManager cacheManager; private ResourceServerStore resourceServerStore; + private Set invalidations = new HashSet<>(); - public InfinispanStoreFactoryProvider(KeycloakSession session) { + public InfinispanStoreFactoryProvider(KeycloakSession session, StoreFactoryCacheManager cacheManager) { + this.session = session; + this.cacheManager = cacheManager; this.transaction = new CacheTransaction(); session.getTransactionManager().enlistAfterCompletion(transaction); StoreFactory delegate = session.getProvider(StoreFactory.class); - resourceStore = new CachedResourceStore(session, this, this.transaction, delegate); - resourceServerStore = new CachedResourceServerStore(session, this.transaction, delegate); - scopeStore = new CachedScopeStore(session, this, this.transaction, delegate); - policyStore = new CachedPolicyStore(session, this, this.transaction, delegate); + resourceStore = new CachedResourceStore(this, delegate); + resourceServerStore = new CachedResourceServerStore(this, delegate); + scopeStore = new CachedScopeStore(this, delegate); + policyStore = new CachedPolicyStore(this, delegate); } @Override @@ -67,7 +75,7 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide } @Override - public PolicyStore getPolicyStore() { + public CachedPolicyStore getPolicyStore() { return policyStore; } @@ -76,6 +84,42 @@ public class InfinispanStoreFactoryProvider implements CachedStoreFactoryProvide } + void addInvalidation(String cacheKey) { + invalidations.add(cacheKey); + } + + boolean isInvalid(String cacheKeyForPolicy) { + return invalidations.contains(cacheKeyForPolicy); + } + + void invalidate(String resourceServerId) { + cacheManager.invalidate(session, resourceServerId, invalidations); + } + + List resolveCachedEntry(String resourceServerId, String cacheKeyForPolicy) { + return cacheManager.resolveResourceServerCache(resourceServerId).get(cacheKeyForPolicy); + } + + void putCacheEntry(String resourceServerId, String key, List entry) { + cacheManager.resolveResourceServerCache(resourceServerId).put(key, entry); + } + + List computeIfCachedEntryAbsent(String resourceServerId, String key, Function> function) { + return cacheManager.resolveResourceServerCache(resourceServerId).computeIfAbsent(key, function); + } + + CacheTransaction getTransaction() { + return transaction; + } + + void removeCachedEntry(String id, String key) { + cacheManager.resolveResourceServerCache(id).remove(key); + } + + void removeEntries(ResourceServer resourceServer) { + cacheManager.removeAll(session, resourceServer); + } + static class CacheTransaction implements KeycloakTransaction { private List completeTasks = new ArrayList<>(); diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java index ad58890f3a..672b565584 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/InfinispanStoreProviderFactory.java @@ -18,11 +18,16 @@ package org.keycloak.models.authorization.infinispan; +import java.util.List; +import java.util.Map; + +import org.infinispan.Cache; import org.keycloak.Config; -import org.keycloak.authorization.AuthorizationProvider; -import org.keycloak.authorization.store.StoreFactory; +import org.keycloak.cluster.ClusterProvider; +import org.keycloak.connections.infinispan.InfinispanConnectionProvider; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.authorization.infinispan.events.AuthorizationInvalidationEvent; import org.keycloak.models.cache.authorization.CachedStoreFactoryProvider; import org.keycloak.models.cache.authorization.CachedStoreProviderFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory; @@ -31,9 +36,12 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory; * @author Pedro Igor */ public class InfinispanStoreProviderFactory implements CachedStoreProviderFactory, EnvironmentDependentProviderFactory { + + private StoreFactoryCacheManager cacheManager; + @Override public CachedStoreFactoryProvider create(KeycloakSession session) { - return new InfinispanStoreFactoryProvider(session); + return new InfinispanStoreFactoryProvider(session, cacheManager); } @Override @@ -43,7 +51,25 @@ public class InfinispanStoreProviderFactory implements CachedStoreProviderFactor @Override public void postInit(KeycloakSessionFactory factory) { + KeycloakSession session = factory.create(); + try { + InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class); + Cache>> cache = provider.getCache(InfinispanConnectionProvider.AUTHORIZATION_CACHE_NAME); + ClusterProvider clusterProvider = session.getProvider(ClusterProvider.class); + + cacheManager = new StoreFactoryCacheManager(cache); + + clusterProvider.registerListener(ClusterProvider.ALL, event -> { + if (event instanceof AuthorizationInvalidationEvent) { + cacheManager.invalidate(AuthorizationInvalidationEvent.class.cast(event)); + } + }); + } finally { + if (session != null) { + session.close(); + } + } } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java new file mode 100644 index 0000000000..4fd5f6f750 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/StoreFactoryCacheManager.java @@ -0,0 +1,72 @@ +/* + * Copyright 2016 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.authorization.infinispan; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.infinispan.Cache; +import org.keycloak.authorization.model.ResourceServer; +import org.keycloak.cluster.ClusterProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.authorization.infinispan.events.AuthorizationInvalidationEvent; +import org.keycloak.models.authorization.infinispan.events.ResourceServerRemovedEvent; + +/** + * @author Pedro Igor + */ +public class StoreFactoryCacheManager { + + private static final String AUTHORIZATION_UPDATE_TASK_KEY = "authorization-update"; + + private final Cache>> cache; + + StoreFactoryCacheManager(Cache>> cache) { + this.cache = cache; + } + + void invalidate(AuthorizationInvalidationEvent event) { + if (event instanceof ResourceServerRemovedEvent) { + cache.remove(event.getId()); + cache.remove(ResourceServerRemovedEvent.class.cast(event).getClientId()); + } else { + Map> resolveResourceServerCache = resolveResourceServerCache(event.getId()); + + for (String key : event.getInvalidations()) { + resolveResourceServerCache.remove(key); + } + } + } + + public void invalidate(KeycloakSession session, String resourceServerId, Set invalidations) { + getClusterProvider(session).notify(AUTHORIZATION_UPDATE_TASK_KEY, new AuthorizationInvalidationEvent(resourceServerId, invalidations), false); + } + + public Map> resolveResourceServerCache(String id) { + return cache.computeIfAbsent(id, key -> new HashMap<>()); + } + + void removeAll(KeycloakSession session, ResourceServer id) { + getClusterProvider(session).notify(AUTHORIZATION_UPDATE_TASK_KEY, new ResourceServerRemovedEvent(id.getId(), id.getClientId()), false); + } + + private ClusterProvider getClusterProvider(KeycloakSession session) { + return session.getProvider(ClusterProvider.class); + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java new file mode 100644 index 0000000000..1d449235e3 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/AuthorizationInvalidationEvent.java @@ -0,0 +1,44 @@ +/* + * Copyright 2016 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.authorization.infinispan.events; + +import java.util.Set; + +import org.keycloak.models.cache.infinispan.events.InvalidationEvent; + +/** + * @author Pedro Igor + */ +public class AuthorizationInvalidationEvent extends InvalidationEvent { + + private final String resourceServerId; + private Set invalidations; + + public AuthorizationInvalidationEvent(String resourceServerId, Set invalidations) { + this.resourceServerId = resourceServerId; + this.invalidations = invalidations; + } + + public Set getInvalidations() { + return invalidations; + } + + @Override + public String getId() { + return resourceServerId; + } +} diff --git a/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java new file mode 100644 index 0000000000..9fde6d9bc4 --- /dev/null +++ b/model/infinispan/src/main/java/org/keycloak/models/authorization/infinispan/events/ResourceServerRemovedEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright 2016 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.authorization.infinispan.events; + +import java.util.Collections; + +/** + * @author Pedro Igor + */ +public class ResourceServerRemovedEvent extends AuthorizationInvalidationEvent { + + private final String clientId; + + public ResourceServerRemovedEvent(String id, String clientId) { + super(id, Collections.emptySet()); + this.clientId = clientId; + } + + public String getClientId() { + return clientId; + } +} diff --git a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java index 648b1b3175..0305ae50d1 100644 --- a/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java +++ b/model/jpa/src/main/java/org/keycloak/authorization/jpa/entities/PolicyEntity.java @@ -74,7 +74,7 @@ public class PolicyEntity implements Policy { @Column(name = "LOGIC") private Logic logic = Logic.POSITIVE; - @ElementCollection + @ElementCollection(fetch = FetchType.LAZY) @MapKeyColumn(name="NAME") @Column(name="VALUE", columnDefinition = "TEXT") @CollectionTable(name="POLICY_CONFIG", joinColumns={ @JoinColumn(name="POLICY_ID") }) @@ -92,7 +92,7 @@ public class PolicyEntity implements Policy { @JoinTable(name = "RESOURCE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "RESOURCE_ID")) private Set resources = new HashSet<>(); - @OneToMany(fetch = FetchType.EAGER, cascade = {}) + @OneToMany(fetch = FetchType.LAZY, cascade = {}) @JoinTable(name = "SCOPE_POLICY", joinColumns = @JoinColumn(name = "POLICY_ID"), inverseJoinColumns = @JoinColumn(name = "SCOPE_ID")) private Set scopes = new HashSet<>(); diff --git a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java index 793185e713..dbf64a4b28 100644 --- a/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java +++ b/server-spi-private/src/main/java/org/keycloak/authorization/store/PolicyStore.java @@ -128,13 +128,4 @@ public interface PolicyStore { * @return a list of policies that depends on the a policy with the given identifier */ List findDependentPolicies(String id, String resourceServerId); - - /** - * Notify this store about changes to data associated with policies. E.g.: resources and scopes.. - * - * TODO: need a better strategy to handle cross-references between stores, specially in cases where the store is caching data. Use some event-based solution here. - * - * @param cached - */ - default void notifyChange(Object cached) {} } diff --git a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml index f779bcf19f..205c1f51af 100755 --- a/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml +++ b/wildfly/server-subsystem/src/main/resources/subsystem-templates/keycloak-infinispan.xml @@ -99,7 +99,9 @@ - + + + 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 0c969049c3..95bcffd9f3 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 @@ -102,7 +102,9 @@ - + + +