keycloak-aplcache
Changes
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java 4(+2 -2)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java 134(+58 -76)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java 20(+20 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java 463(+463 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java 161(+161 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java 52(+52 -0)
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java 294(+294 -0)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory 3(+2 -1)
model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory 3(+2 -1)
model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java 88(+88 -0)
Details
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
index e5a3472..a9145b2 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java
@@ -546,7 +546,9 @@ public class ClientAdapter implements ClientModel {
public RoleModel getRole(String name) {
if (updated != null) return updated.getRole(name);
String id = cached.getRoles().get(name);
- if (id == null) return null;
+ if (id == null) {
+ return null;
+ }
return cacheSession.getRoleById(id, cachedRealm);
}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
index e4b68f6..7294db9 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
@@ -351,8 +351,8 @@ public class RevisionedCacheRealmProvider implements CacheRealmProvider {
if (cached != null && !cached.getRealm().equals(realm.getId())) {
cached = null;
}
- if (cached != null && cached.getClientId().equals("client")) {
- logger.infov("client by id cache hit: {0}", cached.getClientId());
+ if (cached != null) {
+ logger.tracev("client by id cache hit: {0}", cached.getClientId());
}
if (cached == null) {
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
index c2b6d6f..b6fb89c 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
@@ -38,11 +38,6 @@ public class RevisionedRealmCache implements RealmCache {
protected final Cache<String, Long> revisions;
protected final Cache<String, Object> cache;
- final AtomicLong realmCounter = new AtomicLong();
- final AtomicLong clientCounter = new AtomicLong();
- final AtomicLong clientTemplateCounter = new AtomicLong();
- final AtomicLong roleCounter = new AtomicLong();
- final AtomicLong groupCounter = new AtomicLong();
protected final ConcurrentHashMap<String, String> realmLookup;
@@ -57,31 +52,54 @@ public class RevisionedRealmCache implements RealmCache {
}
public Long getCurrentRevision(String id) {
- return revisions.get(id);
- }
- @Override
- public void clear() {
- cache.clear();
+ //return revisions.get(id);
+ return UpdateCounter.current();
}
- public AtomicLong getRealmCounter() {
- return realmCounter;
+ private <T> T get(String id, Class<T> type) {
+ Revisioned o = (Revisioned)cache.get(id);
+ if (o == null) {
+ return null;
+ }
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ logger.tracev("get() missing rev");
+ return null;
+ }
+ long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+ if (rev > oRev) {
+ logger.tracev("stale rev: {0} o.rev: {1}", rev.longValue(), oRev);
+ return null;
+ }
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
}
- public AtomicLong getClientCounter() {
- return clientCounter;
+ protected Object invalidateObject(String id) {
+ Object removed = cache.remove(id);
+ revisions.put(id, UpdateCounter.next());
+ return removed;
}
- public AtomicLong getClientTemplateCounter() {
- return clientTemplateCounter;
+ protected void addRevisioned(String id, Revisioned object) {
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ rev = UpdateCounter.next();
+ revisions.put(id, rev);
+ }
+ if (rev.equals(object.getRevision())) {
+ cache.putForExternalRead(id, object);
+ }
}
- public AtomicLong getRoleCounter() {
- return roleCounter;
- }
- public AtomicLong getGroupCounter() {
- return groupCounter;
+
+
+
+
+
+ @Override
+ public void clear() {
+ cache.clear();
}
@Override
@@ -92,41 +110,23 @@ public class RevisionedRealmCache implements RealmCache {
@Override
public void invalidateCachedRealm(CachedRealm realm) {
logger.tracev("Invalidating realm {0}", realm.getId());
- invalidateObject(realm.getId(), realmCounter);
+ invalidateObject(realm.getId());
realmLookup.remove(realm.getName());
}
@Override
public void invalidateCachedRealmById(String id) {
- CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
+ CachedRealm cached = (CachedRealm) invalidateObject(id);
if (cached != null) realmLookup.remove(cached.getName());
}
- protected Object invalidateObject(String id, AtomicLong counter) {
- revisions.put(id, counter.incrementAndGet());
- Object removed = cache.remove(id);
- revisions.put(id, counter.incrementAndGet());
- return removed;
- }
-
@Override
public void addCachedRealm(CachedRealm realm) {
logger.tracev("Adding realm {0}", realm.getId());
- addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
+ addRevisioned(realm.getId(), (Revisioned) realm);
realmLookup.put(realm.getName(), realm.getId());
}
- protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
- Long rev = revisions.get(id);
- if (rev == null) {
- rev = counter.incrementAndGet();
- revisions.put(id, rev);
- }
- if (rev.equals(object.getRevision())) {
- cache.putForExternalRead(id, object);
- }
- }
-
@Override
public CachedRealm getCachedRealmByName(String name) {
String id = realmLookup.get(name);
@@ -140,20 +140,20 @@ public class RevisionedRealmCache implements RealmCache {
@Override
public void invalidateApplication(CachedClient app) {
- logger.infov("Removing application {0}", app.getClientAuthenticatorType());
- invalidateObject(app.getId(), clientCounter);
+ logger.tracev("Removing application {0}", app.getId());
+ invalidateObject(app.getId());
}
@Override
public void addCachedClient(CachedClient app) {
logger.tracev("Adding application {0}", app.getId());
- addRevisioned(app.getId(), (Revisioned) app, clientCounter);
+ addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedApplicationById(String id) {
- CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
- if (client != null) logger.infov("Removing application {0}", client.getClientId());
+ CachedClient client = (CachedClient)invalidateObject(id);
+ if (client != null) logger.tracev("Removing application {0}", client.getClientId());
}
@Override
@@ -170,26 +170,26 @@ public class RevisionedRealmCache implements RealmCache {
@Override
public void invalidateGroup(CachedGroup role) {
logger.tracev("Removing group {0}", role.getId());
- invalidateObject(role.getId(), groupCounter);
+ invalidateObject(role.getId());
}
@Override
public void addCachedGroup(CachedGroup role) {
logger.tracev("Adding group {0}", role.getId());
- addRevisioned(role.getId(), (Revisioned) role, groupCounter);
+ addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedGroupById(String id) {
logger.tracev("Removing group {0}", id);
- invalidateObject(id, groupCounter);
+ invalidateObject(id);
}
@Override
public void invalidateGroupById(String id) {
logger.tracev("Removing group {0}", id);
- invalidateObject(id, groupCounter);
+ invalidateObject(id);
}
@Override
@@ -200,13 +200,13 @@ public class RevisionedRealmCache implements RealmCache {
@Override
public void invalidateRole(CachedRole role) {
logger.tracev("Removing role {0}", role.getId());
- invalidateObject(role.getId(), roleCounter);
+ invalidateObject(role.getId());
}
@Override
public void invalidateRoleById(String id) {
logger.tracev("Removing role {0}", id);
- invalidateObject(id, roleCounter);
+ invalidateObject(id);
}
@Override
@@ -218,31 +218,13 @@ public class RevisionedRealmCache implements RealmCache {
@Override
public void addCachedRole(CachedRole role) {
logger.tracev("Adding role {0}", role.getId());
- addRevisioned(role.getId(), (Revisioned) role, roleCounter);
+ addRevisioned(role.getId(), (Revisioned) role);
}
@Override
public void invalidateCachedRoleById(String id) {
logger.tracev("Removing role {0}", id);
- invalidateObject(id, roleCounter);
- }
-
- private <T> T get(String id, Class<T> type) {
- Revisioned o = (Revisioned)cache.get(id);
- if (o == null) {
- return null;
- }
- Long rev = revisions.get(id);
- if (rev == null) {
- logger.tracev("get() missing rev");
- return null;
- }
- long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
- if (rev > oRev) {
- logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
- return null;
- }
- return o != null && type.isInstance(o) ? type.cast(o) : null;
+ invalidateObject(id);
}
@Override
@@ -253,19 +235,19 @@ public class RevisionedRealmCache implements RealmCache {
@Override
public void invalidateClientTemplate(CachedClientTemplate app) {
logger.tracev("Removing client template {0}", app.getId());
- invalidateObject(app.getId(), clientTemplateCounter);
+ invalidateObject(app.getId());
}
@Override
public void addCachedClientTemplate(CachedClientTemplate app) {
logger.tracev("Adding client template {0}", app.getId());
- addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
+ addRevisioned(app.getId(), (Revisioned) app);
}
@Override
public void invalidateCachedClientTemplateById(String id) {
logger.tracev("Removing client template {0}", id);
- invalidateObject(id, clientTemplateCounter);
+ invalidateObject(id);
}
@Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
new file mode 100755
index 0000000..88d598b
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/UpdateCounter.java
@@ -0,0 +1,20 @@
+package org.keycloak.models.cache.infinispan.counter;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UpdateCounter {
+
+ private static final AtomicLong counter = new AtomicLong();
+
+ public static long current() {
+ return counter.get();
+ }
+
+ public static long next() {
+ return counter.incrementAndGet();
+ }
+
+}
\ No newline at end of file
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
new file mode 100755
index 0000000..ea451b0
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProvider.java
@@ -0,0 +1,463 @@
+/*
+ * 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.cache.infinispan.locking;
+
+import org.jboss.logging.Logger;
+import org.keycloak.migration.MigrationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientTemplateModel;
+import org.keycloak.models.GroupModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RealmProvider;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.ClientAdapter;
+import org.keycloak.models.cache.infinispan.ClientTemplateAdapter;
+import org.keycloak.models.cache.infinispan.GroupAdapter;
+import org.keycloak.models.cache.infinispan.RealmAdapter;
+import org.keycloak.models.cache.infinispan.RoleAdapter;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClient;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientRole;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedClientTemplate;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedGroup;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealm;
+import org.keycloak.models.cache.infinispan.counter.entities.RevisionedCachedRealmRole;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LockingCacheRealmProvider implements CacheRealmProvider {
+ protected static final Logger logger = Logger.getLogger(LockingCacheRealmProvider.class);
+ protected LockingRealmCache cache;
+ protected KeycloakSession session;
+ protected RealmProvider delegate;
+ protected boolean transactionActive;
+ protected boolean setRollbackOnly;
+
+ protected Set<String> realmInvalidations = new HashSet<>();
+ protected Set<String> appInvalidations = new HashSet<>();
+ protected Set<String> clientTemplateInvalidations = new HashSet<>();
+ protected Set<String> roleInvalidations = new HashSet<>();
+ protected Set<String> groupInvalidations = new HashSet<>();
+ protected Map<String, RealmModel> managedRealms = new HashMap<>();
+ protected Map<String, ClientModel> managedApplications = new HashMap<>();
+ protected Map<String, ClientTemplateModel> managedClientTemplates = new HashMap<>();
+ protected Map<String, RoleModel> managedRoles = new HashMap<>();
+ protected Map<String, GroupModel> managedGroups = new HashMap<>();
+
+ protected boolean clearAll;
+
+ public LockingCacheRealmProvider(LockingRealmCache cache, KeycloakSession session) {
+ this.cache = cache;
+ this.session = session;
+
+ session.getTransaction().enlistPrepare(getPrepareTransaction());
+ session.getTransaction().enlistAfterCompletion(getAfterTransaction());
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public MigrationModel getMigrationModel() {
+ return getDelegate().getMigrationModel();
+ }
+
+ @Override
+ public RealmProvider getDelegate() {
+ if (!transactionActive) throw new IllegalStateException("Cannot access delegate without a transaction");
+ if (delegate != null) return delegate;
+ delegate = session.getProvider(RealmProvider.class);
+ return delegate;
+ }
+
+ @Override
+ public void registerRealmInvalidation(String id) {
+ realmInvalidations.add(id);
+ }
+
+ @Override
+ public void registerApplicationInvalidation(String id) {
+ appInvalidations.add(id);
+ }
+ @Override
+ public void registerClientTemplateInvalidation(String id) {
+ clientTemplateInvalidations.add(id);
+ }
+
+ @Override
+ public void registerRoleInvalidation(String id) {
+ roleInvalidations.add(id);
+ }
+
+ @Override
+ public void registerGroupInvalidation(String id) {
+ groupInvalidations.add(id);
+
+ }
+
+ protected void runInvalidations() {
+ for (String id : realmInvalidations) {
+ cache.invalidateCachedRealmById(id);
+ }
+ for (String id : roleInvalidations) {
+ cache.invalidateRoleById(id);
+ }
+ for (String id : groupInvalidations) {
+ cache.invalidateGroupById(id);
+ }
+ for (String id : appInvalidations) {
+ cache.invalidateCachedApplicationById(id);
+ }
+ for (String id : clientTemplateInvalidations) {
+ cache.invalidateCachedClientTemplateById(id);
+ }
+ }
+
+ private KeycloakTransaction getPrepareTransaction() {
+ return new KeycloakTransaction() {
+ @Override
+ public void begin() {
+ transactionActive = true;
+ }
+
+ @Override
+ public void commit() {
+ if (delegate == null) return;
+ List<String> invalidates = new LinkedList<>();
+ for (String id : realmInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : roleInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : groupInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : appInvalidations) {
+ invalidates.add(id);
+ }
+ for (String id : clientTemplateInvalidations) {
+ invalidates.add(id);
+ }
+
+ Collections.sort(invalidates); // lock ordering
+ cache.getRevisions().startBatch();
+ for (String id : invalidates) {
+ cache.getRevisions().getAdvancedCache().lock(id);
+ }
+
+ }
+
+ @Override
+ public void rollback() {
+ setRollbackOnly = true;
+ transactionActive = false;
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ setRollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return setRollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return transactionActive;
+ }
+ };
+ }
+
+ private KeycloakTransaction getAfterTransaction() {
+ return new KeycloakTransaction() {
+ @Override
+ public void begin() {
+ transactionActive = true;
+ }
+
+ @Override
+ public void commit() {
+ try {
+ if (delegate == null) return;
+ if (clearAll) {
+ cache.clear();
+ }
+ runInvalidations();
+ transactionActive = false;
+ } finally {
+ cache.endRevisionBatch();
+ }
+ }
+
+ @Override
+ public void rollback() {
+ try {
+ setRollbackOnly = true;
+ runInvalidations();
+ transactionActive = false;
+ } finally {
+ cache.endRevisionBatch();
+ }
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ setRollbackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return setRollbackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return transactionActive;
+ }
+ };
+ }
+
+ @Override
+ public RealmModel createRealm(String name) {
+ RealmModel realm = getDelegate().createRealm(name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel createRealm(String id, String name) {
+ RealmModel realm = getDelegate().createRealm(id, name);
+ registerRealmInvalidation(realm.getId());
+ return realm;
+ }
+
+ @Override
+ public RealmModel getRealm(String id) {
+ CachedRealm cached = cache.getCachedRealm(id);
+ if (cached != null) {
+ logger.tracev("by id cache hit: {0}", cached.getName());
+ }
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ RealmModel model = getDelegate().getRealm(id);
+ if (model == null) return null;
+ if (realmInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedRealm(loaded, cache, this, model);
+ cache.addCachedRealm(cached);
+ } else if (realmInvalidations.contains(id)) {
+ return getDelegate().getRealm(id);
+ } else if (managedRealms.containsKey(id)) {
+ return managedRealms.get(id);
+ }
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public RealmModel getRealmByName(String name) {
+ CachedRealm cached = cache.getCachedRealmByName(name);
+ if (cached != null) {
+ logger.tracev("by name cache hit: {0}", cached.getName());
+ }
+ if (cached == null) {
+ RealmModel model = getDelegate().getRealmByName(name);
+ if (model == null) return null;
+ if (realmInvalidations.contains(model.getId())) return model;
+ cached = new RevisionedCachedRealm(null, cache, this, model);
+ cache.addCachedRealm(cached);
+ } else if (realmInvalidations.contains(cached.getId())) {
+ return getDelegate().getRealmByName(name);
+ } else if (managedRealms.containsKey(cached.getId())) {
+ return managedRealms.get(cached.getId());
+ }
+ RealmAdapter adapter = new RealmAdapter(cached, this);
+ managedRealms.put(cached.getId(), adapter);
+ return adapter;
+ }
+
+ @Override
+ public List<RealmModel> getRealms() {
+ // Retrieve realms from backend
+ List<RealmModel> backendRealms = getDelegate().getRealms();
+
+ // Return cache delegates to ensure cache invalidated during write operations
+ List<RealmModel> cachedRealms = new LinkedList<RealmModel>();
+ for (RealmModel realm : backendRealms) {
+ RealmModel cached = getRealm(realm.getId());
+ cachedRealms.add(cached);
+ }
+ return cachedRealms;
+ }
+
+ @Override
+ public boolean removeRealm(String id) {
+ cache.invalidateCachedRealmById(id);
+
+ RealmModel realm = getDelegate().getRealm(id);
+ Set<RoleModel> realmRoles = null;
+ if (realm != null) {
+ realmRoles = realm.getRoles();
+ }
+
+ boolean didIt = getDelegate().removeRealm(id);
+ realmInvalidations.add(id);
+
+ // TODO: Temporary workaround to invalidate cached realm roles
+ if (didIt && realmRoles != null) {
+ for (RoleModel role : realmRoles) {
+ roleInvalidations.add(role.getId());
+ }
+ }
+
+ return didIt;
+ }
+
+ @Override
+ public void close() {
+ if (delegate != null) delegate.close();
+ }
+
+ @Override
+ public RoleModel getRoleById(String id, RealmModel realm) {
+ CachedRole cached = cache.getRole(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ RoleModel model = getDelegate().getRoleById(id, realm);
+ if (model == null) return null;
+ if (roleInvalidations.contains(id)) return model;
+ if (model.getContainer() instanceof ClientModel) {
+ cached = new RevisionedCachedClientRole(loaded, ((ClientModel) model.getContainer()).getId(), model, realm);
+ } else {
+ cached = new RevisionedCachedRealmRole(loaded, model, realm);
+ }
+ cache.addCachedRole(cached);
+
+ } else if (roleInvalidations.contains(id)) {
+ return getDelegate().getRoleById(id, realm);
+ } else if (managedRoles.containsKey(id)) {
+ return managedRoles.get(id);
+ }
+ RoleAdapter adapter = new RoleAdapter(cached, cache, this, realm);
+ managedRoles.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public GroupModel getGroupById(String id, RealmModel realm) {
+ CachedGroup cached = cache.getGroup(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ GroupModel model = getDelegate().getGroupById(id, realm);
+ if (model == null) return null;
+ if (groupInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedGroup(loaded, realm, model);
+ cache.addCachedGroup(cached);
+
+ } else if (groupInvalidations.contains(id)) {
+ return getDelegate().getGroupById(id, realm);
+ } else if (managedGroups.containsKey(id)) {
+ return managedGroups.get(id);
+ }
+ GroupAdapter adapter = new GroupAdapter(cached, this, session, realm);
+ managedGroups.put(id, adapter);
+ return adapter;
+ }
+
+ @Override
+ public ClientModel getClientById(String id, RealmModel realm) {
+ CachedClient cached = cache.getApplication(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+ if (cached != null && cached.getClientId().equals("client")) {
+ logger.tracev("client by id cache hit: {0}", cached.getClientId());
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ ClientModel model = getDelegate().getClientById(id, realm);
+ if (model == null) return null;
+ if (appInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedClient(loaded, cache, getDelegate(), realm, model);
+ cache.addCachedClient(cached);
+ } else if (appInvalidations.contains(id)) {
+ return getDelegate().getClientById(id, realm);
+ } else if (managedApplications.containsKey(id)) {
+ return managedApplications.get(id);
+ }
+ ClientAdapter adapter = new ClientAdapter(realm, cached, this, cache);
+ managedApplications.put(id, adapter);
+ return adapter;
+ }
+ @Override
+ public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
+ CachedClientTemplate cached = cache.getClientTemplate(id);
+ if (cached != null && !cached.getRealm().equals(realm.getId())) {
+ cached = null;
+ }
+
+ if (cached == null) {
+ Long loaded = cache.getCurrentRevision(id);
+ ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
+ if (model == null) return null;
+ if (clientTemplateInvalidations.contains(id)) return model;
+ cached = new RevisionedCachedClientTemplate(loaded, cache, getDelegate(), realm, model);
+ cache.addCachedClientTemplate(cached);
+ } else if (clientTemplateInvalidations.contains(id)) {
+ return getDelegate().getClientTemplateById(id, realm);
+ } else if (managedClientTemplates.containsKey(id)) {
+ return managedClientTemplates.get(id);
+ }
+ ClientTemplateModel adapter = new ClientTemplateAdapter(realm, cached, this, cache);
+ managedClientTemplates.put(id, adapter);
+ return adapter;
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
new file mode 100755
index 0000000..2009b67
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingCacheRealmProviderFactory.java
@@ -0,0 +1,161 @@
+/*
+ * 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.cache.infinispan.locking;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntriesEvicted;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryInvalidated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntriesEvictedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryInvalidatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.CacheRealmProviderFactory;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedRealm;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+ private static final Logger log = Logger.getLogger(LockingCacheRealmProviderFactory.class);
+
+ protected volatile LockingRealmCache realmCache;
+
+ protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
+
+ @Override
+ public CacheRealmProvider create(KeycloakSession session) {
+ lazyInit(session);
+ return new LockingCacheRealmProvider(realmCache, session);
+ }
+
+ private void lazyInit(KeycloakSession session) {
+ if (realmCache == null) {
+ synchronized (this) {
+ if (realmCache == null) {
+ Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
+ Cache<String, Long> counterCache = session.getProvider(InfinispanConnectionProvider.class).getCache(LockingConnectionProviderFactory.COUNTER_CACHE_NAME);
+ cache.addListener(new CacheListener());
+ realmCache = new LockingRealmCache(cache, counterCache, realmLookup);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void postInit(KeycloakSessionFactory factory) {
+
+ }
+
+ @Override
+ public void close() {
+ }
+
+ @Override
+ public String getId() {
+ return "infinispan-locking";
+ }
+
+ @Listener
+ public class CacheListener {
+
+ @CacheEntryCreated
+ public void created(CacheEntryCreatedEvent<String, Object> event) {
+ if (!event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+ realmLookup.put(realm.getName(), realm.getId());
+ log.tracev("Realm added realm={0}", realm.getName());
+ }
+ }
+ }
+ }
+
+ @CacheEntryRemoved
+ public void removed(CacheEntryRemovedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntryInvalidated
+ public void removed(CacheEntryInvalidatedEvent<String, Object> event) {
+ if (event.isPre()) {
+ Object object = event.getValue();
+ if (object != null) {
+ remove(object);
+ }
+ }
+ }
+
+ @CacheEntriesEvicted
+ public void userEvicted(CacheEntriesEvictedEvent<String, Object> event) {
+ for (Object object : event.getEntries().values()) {
+ remove(object);
+ }
+ }
+
+ private void remove(Object object) {
+ if (object instanceof CachedRealm) {
+ CachedRealm realm = (CachedRealm) object;
+
+ realmLookup.remove(realm.getName());
+
+ for (String r : realm.getRealmRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ for (String c : realm.getClients().values()) {
+ realmCache.evictCachedApplicationById(c);
+ }
+
+ log.tracev("Realm removed realm={0}", realm.getName());
+ } else if (object instanceof CachedClient) {
+ CachedClient client = (CachedClient) object;
+
+ for (String r : client.getRoles().values()) {
+ realmCache.evictCachedRoleById(r);
+ }
+
+ log.tracev("Client removed client={0}", client.getId());
+ }
+ }
+ }
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java
new file mode 100755
index 0000000..f3e5f29
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingConnectionProviderFactory.java
@@ -0,0 +1,52 @@
+/*
+ * 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.cache.infinispan.locking;
+
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.jboss.logging.Logger;
+import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
+ public static final String COUNTER_CACHE_NAME = "COUNTER_CACHE";
+
+ protected static final Logger logger = Logger.getLogger(LockingConnectionProviderFactory.class);
+
+ @Override
+ public String getId() {
+ return "locking";
+ }
+
+
+ protected void initEmbedded() {
+ super.initEmbedded();
+ ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+ counterConfigBuilder.invocationBatching().enable();
+ counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+ counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+ Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+ cacheManager.defineConfiguration(COUNTER_CACHE_NAME, counterCacheConfiguration);
+ }
+
+}
diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
new file mode 100755
index 0000000..9c8e784
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/locking/LockingRealmCache.java
@@ -0,0 +1,294 @@
+/*
+ * 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.cache.infinispan.locking;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedClient;
+import org.keycloak.models.cache.entities.CachedClientTemplate;
+import org.keycloak.models.cache.entities.CachedGroup;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+import org.keycloak.models.cache.infinispan.counter.Revisioned;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LockingRealmCache implements RealmCache {
+
+ protected static final Logger logger = Logger.getLogger(LockingRealmCache.class);
+
+ protected final Cache<String, Long> revisions;
+ protected final Cache<String, Object> cache;
+ final AtomicLong realmCounter = new AtomicLong();
+ final AtomicLong clientCounter = new AtomicLong();
+ final AtomicLong clientTemplateCounter = new AtomicLong();
+ final AtomicLong roleCounter = new AtomicLong();
+ final AtomicLong groupCounter = new AtomicLong();
+
+ protected final ConcurrentHashMap<String, String> realmLookup;
+
+ public LockingRealmCache(Cache<String, Object> cache, Cache<String, Long> revisions, ConcurrentHashMap<String, String> realmLookup) {
+ this.cache = cache;
+ this.realmLookup = realmLookup;
+ this.revisions = revisions;
+ }
+
+ public Cache<String, Object> getCache() {
+ return cache;
+ }
+
+ public Cache<String, Long> getRevisions() {
+ return revisions;
+ }
+
+ public void startRevisionBatch() {
+ revisions.startBatch();
+ }
+
+ public void endRevisionBatch() {
+ try {
+ revisions.endBatch(true);
+ } catch (Exception e) {
+ }
+
+ }
+
+ private <T> T get(String id, Class<T> type) {
+ Revisioned o = (Revisioned)cache.get(id);
+ if (o == null) {
+ return null;
+ }
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ logger.tracev("get() missing rev");
+ return null;
+ }
+ long oRev = o.getRevision() == null ? -1L : o.getRevision().longValue();
+ if (rev > oRev) {
+ logger.tracev("get() rev: {0} o.rev: {1}", rev.longValue(), oRev);
+ return null;
+ }
+ return o != null && type.isInstance(o) ? type.cast(o) : null;
+ }
+
+ protected Object invalidateObject(String id, AtomicLong counter) {
+ Object removed = cache.remove(id);
+ revisions.put(id, counter.incrementAndGet());
+ return removed;
+ }
+
+ protected void addRevisioned(String id, Revisioned object, AtomicLong counter) {
+ //startRevisionBatch();
+ try {
+ //revisions.getAdvancedCache().lock(id);
+ Long rev = revisions.get(id);
+ if (rev == null) {
+ rev = counter.incrementAndGet();
+ revisions.put(id, rev);
+ return;
+ }
+ revisions.startBatch();
+ revisions.getAdvancedCache().lock(id);
+ rev = revisions.get(id);
+ if (rev == null) {
+ rev = counter.incrementAndGet();
+ revisions.put(id, rev);
+ return;
+ }
+ if (rev.equals(object.getRevision())) {
+ cache.putForExternalRead(id, object);
+ }
+ } finally {
+ endRevisionBatch();
+ }
+
+ }
+
+
+
+
+ public Long getCurrentRevision(String id) {
+ return revisions.get(id);
+ }
+ @Override
+ public void clear() {
+ cache.clear();
+ }
+
+ @Override
+ public CachedRealm getCachedRealm(String id) {
+ return get(id, CachedRealm.class);
+ }
+
+ @Override
+ public void invalidateCachedRealm(CachedRealm realm) {
+ logger.tracev("Invalidating realm {0}", realm.getId());
+ invalidateObject(realm.getId(), realmCounter);
+ realmLookup.remove(realm.getName());
+ }
+
+ @Override
+ public void invalidateCachedRealmById(String id) {
+ CachedRealm cached = (CachedRealm) invalidateObject(id, realmCounter);
+ if (cached != null) realmLookup.remove(cached.getName());
+ }
+
+ @Override
+ public void addCachedRealm(CachedRealm realm) {
+ logger.tracev("Adding realm {0}", realm.getId());
+ addRevisioned(realm.getId(), (Revisioned) realm, realmCounter);
+ realmLookup.put(realm.getName(), realm.getId());
+ }
+
+
+ @Override
+ public CachedRealm getCachedRealmByName(String name) {
+ String id = realmLookup.get(name);
+ return id != null ? getCachedRealm(id) : null;
+ }
+
+ @Override
+ public CachedClient getApplication(String id) {
+ return get(id, CachedClient.class);
+ }
+
+ @Override
+ public void invalidateApplication(CachedClient app) {
+ logger.tracev("Removing application {0}", app.getId());
+ invalidateObject(app.getId(), clientCounter);
+ }
+
+ @Override
+ public void addCachedClient(CachedClient app) {
+ logger.tracev("Adding application {0}", app.getId());
+ addRevisioned(app.getId(), (Revisioned) app, clientCounter);
+ }
+
+ @Override
+ public void invalidateCachedApplicationById(String id) {
+ CachedClient client = (CachedClient)invalidateObject(id, clientCounter);
+ if (client != null) logger.tracev("Removing application {0}", client.getClientId());
+ }
+
+ @Override
+ public void evictCachedApplicationById(String id) {
+ logger.tracev("Evicting application {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public CachedGroup getGroup(String id) {
+ return get(id, CachedGroup.class);
+ }
+
+ @Override
+ public void invalidateGroup(CachedGroup role) {
+ logger.tracev("Removing group {0}", role.getId());
+ invalidateObject(role.getId(), groupCounter);
+ }
+
+ @Override
+ public void addCachedGroup(CachedGroup role) {
+ logger.tracev("Adding group {0}", role.getId());
+ addRevisioned(role.getId(), (Revisioned) role, groupCounter);
+ }
+
+ @Override
+ public void invalidateCachedGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidateObject(id, groupCounter);
+
+ }
+
+ @Override
+ public void invalidateGroupById(String id) {
+ logger.tracev("Removing group {0}", id);
+ invalidateObject(id, groupCounter);
+ }
+
+ @Override
+ public CachedRole getRole(String id) {
+ return get(id, CachedRole.class);
+ }
+
+ @Override
+ public void invalidateRole(CachedRole role) {
+ logger.tracev("Removing role {0}", role.getId());
+ invalidateObject(role.getId(), roleCounter);
+ }
+
+ @Override
+ public void invalidateRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidateObject(id, roleCounter);
+ }
+
+ @Override
+ public void evictCachedRoleById(String id) {
+ logger.tracev("Evicting role {0}", id);
+ cache.evict(id);
+ }
+
+ @Override
+ public void addCachedRole(CachedRole role) {
+ logger.tracev("Adding role {0}", role.getId());
+ addRevisioned(role.getId(), (Revisioned) role, roleCounter);
+ }
+
+ @Override
+ public void invalidateCachedRoleById(String id) {
+ logger.tracev("Removing role {0}", id);
+ invalidateObject(id, roleCounter);
+ }
+
+ @Override
+ public CachedClientTemplate getClientTemplate(String id) {
+ return get(id, CachedClientTemplate.class);
+ }
+
+ @Override
+ public void invalidateClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Removing client template {0}", app.getId());
+ invalidateObject(app.getId(), clientTemplateCounter);
+ }
+
+ @Override
+ public void addCachedClientTemplate(CachedClientTemplate app) {
+ logger.tracev("Adding client template {0}", app.getId());
+ addRevisioned(app.getId(), (Revisioned) app, clientTemplateCounter);
+ }
+
+ @Override
+ public void invalidateCachedClientTemplateById(String id) {
+ logger.tracev("Removing client template {0}", id);
+ invalidateObject(id, clientTemplateCounter);
+ }
+
+ @Override
+ public void evictCachedClientTemplateById(String id) {
+ logger.tracev("Evicting client template {0}", id);
+ cache.evict(id);
+ }
+
+
+}
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
index cff9ef1..ef880ec 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
@@ -16,4 +16,5 @@
#
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
\ No newline at end of file
+org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
+org.keycloak.models.cache.infinispan.locking.LockingConnectionProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
index 2d264cf..6554765 100755
--- a/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
+++ b/model/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
@@ -16,4 +16,5 @@
#
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
-org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
\ No newline at end of file
+org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
+org.keycloak.models.cache.infinispan.locking.LockingCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java
new file mode 100755
index 0000000..aa26b5f
--- /dev/null
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyLockingTest.java
@@ -0,0 +1,88 @@
+package org.keycloak.models.sessions.infinispan.initializer;
+
+import org.infinispan.Cache;
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.cache.VersioningScheme;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.infinispan.transaction.LockingMode;
+import org.infinispan.transaction.TransactionMode;
+import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
+import org.infinispan.util.concurrent.IsolationLevel;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Ignore
+public class ConcurrencyLockingTest {
+
+ @Test
+ public void testLocking() throws Exception {
+ final DefaultCacheManager cacheManager = getVersionedCacheManager();
+ Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
+ cache.put("key", "init");
+ ExecutorService executor = Executors.newSingleThreadExecutor();
+ executor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Cache<String, String> cache = cacheManager.getCache("COUNTER_CACHE");
+ cache.startBatch();
+ System.out.println("thread lock");
+ cache.getAdvancedCache().lock("key");
+ try {
+ Thread.sleep(100000);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ cache.endBatch(true);
+
+ }
+ });
+ Thread.sleep(10);
+ cache.startBatch();
+ cache.getAdvancedCache().lock("key");
+ cache.put("key", "1234");
+ System.out.println("after put");
+ cache.endBatch(true);
+
+ Thread.sleep(1000000);
+
+
+
+ }
+
+ protected DefaultCacheManager getVersionedCacheManager() {
+ GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
+
+
+ boolean allowDuplicateJMXDomains = true;
+
+ gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
+
+ final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
+ ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
+ Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
+ cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
+
+ ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
+ counterConfigBuilder.invocationBatching().enable();
+ counterConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
+ counterConfigBuilder.transaction().lockingMode(LockingMode.PESSIMISTIC);
+ Configuration counterCacheConfiguration = counterConfigBuilder.build();
+
+ cacheManager.defineConfiguration("COUNTER_CACHE", counterCacheConfiguration);
+ return cacheManager;
+ }
+
+}
diff --git a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
index b35ce24..48a0e36 100755
--- a/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
+++ b/model/infinispan/src/test/java/org/keycloak/models/sessions/infinispan/initializer/ConcurrencyVersioningTest.java
@@ -14,6 +14,7 @@ import org.infinispan.transaction.TransactionProtocol;
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.junit.Assert;
+import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
@@ -30,6 +31,7 @@ import java.util.concurrent.Executors;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
+@Ignore
public class ConcurrencyVersioningTest {
public static abstract class AbstractThread implements Runnable {
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
index 5453164..d4207d1 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java
@@ -633,8 +633,8 @@ public class ClientAdapter implements ClientModel {
roleEntity.setClient(entity);
roleEntity.setClientRole(true);
roleEntity.setRealmId(realm.getId());
+ //entity.getRoles().add(roleEntity);
em.persist(roleEntity);
- entity.getRoles().add(roleEntity);
em.flush();
return new RoleAdapter(realm, em, roleEntity);
}
@@ -650,7 +650,7 @@ public class ClientAdapter implements ClientModel {
RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
if (!role.isClientRole()) return false;
- entity.getRoles().remove(role);
+ //entity.getRoles().remove(role);
entity.getDefaultRoles().remove(role);
String compositeRoleTable = JpaUtils.getTableNameForNativeQuery("COMPOSITE_ROLE", em);
em.createNativeQuery("delete from " + compositeRoleTable + " where CHILD_ROLE = :role").setParameter("role", role).executeUpdate();
@@ -667,12 +667,22 @@ public class ClientAdapter implements ClientModel {
@Override
public Set<RoleModel> getRoles() {
Set<RoleModel> list = new HashSet<RoleModel>();
+ /*
Collection<RoleEntity> roles = entity.getRoles();
if (roles == null) return list;
for (RoleEntity entity : roles) {
list.add(new RoleAdapter(realm, em, entity));
}
return list;
+ */
+ TypedQuery<RoleEntity> query = em.createNamedQuery("getClientRoles", RoleEntity.class);
+ query.setParameter("client", entity);
+ List<RoleEntity> roles = query.getResultList();
+ for (RoleEntity roleEntity : roles) {
+ list.add(new RoleAdapter(realm, em, roleEntity));
+ }
+ return list;
+
}
@Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
index 848401a..cc9b36a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java
@@ -28,6 +28,8 @@ import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
@@ -44,6 +46,9 @@ import java.util.Set;
*/
@Entity
@Table(name="CLIENT", uniqueConstraints = {@UniqueConstraint(columnNames = {"REALM_ID", "CLIENT_ID"})})
+@NamedQueries({
+ @NamedQuery(name="getClientsByRealm", query="select client from ClientEntity client where client.realm = :realm"),
+})
public class ClientEntity {
@Id
@@ -146,8 +151,8 @@ public class ClientEntity {
@Column(name="NODE_REREG_TIMEOUT")
private int nodeReRegistrationTimeout;
- @OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "client")
- Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
+ //@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "client")
+ //Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="CLIENT_DEFAULT_ROLES", joinColumns = { @JoinColumn(name="CLIENT_ID")}, inverseJoinColumns = { @JoinColumn(name="ROLE_ID")})
@@ -343,6 +348,7 @@ public class ClientEntity {
this.managementUrl = managementUrl;
}
+ /*
public Collection<RoleEntity> getRoles() {
return roles;
}
@@ -350,6 +356,7 @@ public class ClientEntity {
public void setRoles(Collection<RoleEntity> roles) {
this.roles = roles;
}
+ */
public Collection<RoleEntity> getDefaultRoles() {
return defaultRoles;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index 53b9f68..87cc7e5 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -145,16 +145,16 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
- @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
+ //@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
//@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
//@JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
- Collection<ClientEntity> clients = new ArrayList<>();
+ //Collection<ClientEntity> clients = new ArrayList<>();
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="REALM_CLIENT_TEMPLATE", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_TEMPLATE_ID") })
Collection<ClientTemplateEntity> clientTemplates = new ArrayList<>();
- @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, mappedBy = "realm")
Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
@ElementCollection
@@ -422,7 +422,7 @@ public class RealmEntity {
public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
this.requiredCredentials = requiredCredentials;
}
-
+ /*
public Collection<ClientEntity> getClients() {
return clients;
}
@@ -430,6 +430,7 @@ public class RealmEntity {
public void setClients(Collection<ClientEntity> clients) {
this.clients = clients;
}
+ */
public Collection<RoleEntity> getRoles() {
return roles;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 1b96cae..6a6ca5f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -41,6 +41,7 @@ import java.util.Collection;
@UniqueConstraint(columnNames = { "NAME", "CLIENT_REALM_CONSTRAINT" })
})
@NamedQueries({
+ @NamedQuery(name="getClientRoles", query="select role from RoleEntity role where role.client = :client"),
@NamedQuery(name="getClientRoleByName", query="select role from RoleEntity role where role.name = :name and role.client = :client"),
@NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.clientRole = false and role.name = :name and role.realm = :realm")
})
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
index be5cd9d..86bc5a2 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaRealmProvider.java
@@ -124,9 +124,18 @@ public class JpaRealmProvider implements RealmProvider {
.setParameter("realm", realm).executeUpdate();
num = em.createNamedQuery("deleteGroupsByRealm")
.setParameter("realm", realm).executeUpdate();
+
+ TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
+ query.setParameter("realm", realm);
+ List<ClientEntity> clients = query.getResultList();
+ for (ClientEntity a : clients) {
+ adapter.removeClient(a.getId());
+ }
+ /*
for (ClientEntity a : new LinkedList<>(realm.getClients())) {
adapter.removeClient(a.getId());
}
+ */
for (ClientTemplateEntity a : new LinkedList<>(realm.getClientTemplates())) {
adapter.removeClientTemplate(a.getId());
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index d93c44c..09e574f 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -729,12 +729,14 @@ public class RealmAdapter implements RealmModel {
@Override
public List<ClientModel> getClients() {
- List<ClientModel> list = new ArrayList<ClientModel>();
- if (realm.getClients() == null) return list;
- for (ClientEntity entity : realm.getClients()) {
+ List<ClientModel> list = new LinkedList<>();
+ TypedQuery<ClientEntity> query = em.createNamedQuery("getClientsByRealm", ClientEntity.class);
+ query.setParameter("realm", realm);
+ List<ClientEntity> clients = query.getResultList();
+ for (ClientEntity entity : clients) {
list.add(new ClientAdapter(this, em, session, entity));
}
- return list;
+ return list;
}
@Override
@@ -753,7 +755,7 @@ public class RealmAdapter implements RealmModel {
entity.setEnabled(true);
entity.setStandardFlowEnabled(true);
entity.setRealm(realm);
- realm.getClients().add(entity);
+ //realm.getClients().add(entity);
em.persist(entity);
em.flush();
final ClientModel resource = new ClientAdapter(this, em, session, entity);
@@ -779,6 +781,7 @@ public class RealmAdapter implements RealmModel {
client.removeRole(role);
}
+ /*
ClientEntity clientEntity = null;
Iterator<ClientEntity> it = realm.getClients().iterator();
while (it.hasNext()) {
@@ -794,11 +797,11 @@ public class RealmAdapter implements RealmModel {
clientEntity = a;
}
}
- if (client == null) {
- return false;
- }
- em.remove(clientEntity);
+ */
+ ClientEntity clientEntity = em.find(ClientEntity.class, id);
+ if (clientEntity == null) return false;
em.createNamedQuery("deleteScopeMappingByClient").setParameter("client", clientEntity).executeUpdate();
+ em.remove(clientEntity);
em.flush();
return true;
diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
index 2b2a783..0e2dcbe 100755
--- a/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
+++ b/server-spi/src/main/java/org/keycloak/models/KeycloakTransactionManager.java
@@ -26,4 +26,5 @@ public interface KeycloakTransactionManager extends KeycloakTransaction {
void enlist(KeycloakTransaction transaction);
void enlistAfterCompletion(KeycloakTransaction transaction);
+ void enlistPrepare(KeycloakTransaction transaction);
}
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
index 23ae6fa..fca6a9e 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakTransactionManager.java
@@ -30,6 +30,7 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
public static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
+ private List<KeycloakTransaction> prepare = new LinkedList<KeycloakTransaction>();
private List<KeycloakTransaction> transactions = new LinkedList<KeycloakTransaction>();
private List<KeycloakTransaction> afterCompletion = new LinkedList<KeycloakTransaction>();
private boolean active;
@@ -54,6 +55,15 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
}
@Override
+ public void enlistPrepare(KeycloakTransaction transaction) {
+ if (active && !transaction.isActive()) {
+ transaction.begin();
+ }
+
+ prepare.add(transaction);
+ }
+
+ @Override
public void begin() {
if (active) {
throw new IllegalStateException("Transaction already active");
@@ -69,6 +79,17 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
@Override
public void commit() {
RuntimeException exception = null;
+ for (KeycloakTransaction tx : prepare) {
+ try {
+ tx.commit();
+ } catch (RuntimeException e) {
+ exception = exception == null ? e : exception;
+ }
+ }
+ if (exception != null) {
+ rollback(exception);
+ return;
+ }
for (KeycloakTransaction tx : transactions) {
try {
tx.commit();
@@ -105,6 +126,10 @@ public class DefaultKeycloakTransactionManager implements KeycloakTransactionMan
@Override
public void rollback() {
RuntimeException exception = null;
+ rollback(exception);
+ }
+
+ protected void rollback(RuntimeException exception) {
for (KeycloakTransaction tx : transactions) {
try {
tx.rollback();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
index 6584b40..f3bacba 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ConcurrencyTest.java
@@ -18,6 +18,7 @@
package org.keycloak.testsuite.admin;
import org.jboss.logging.Logger;
+import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.admin.client.Keycloak;
@@ -30,6 +31,8 @@ import javax.ws.rs.core.Response;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -44,14 +47,51 @@ public class ConcurrencyTest extends AbstractClientTest {
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
- private static final int DEFAULT_THREADS = 5;
- private static final int DEFAULT_ITERATIONS = 30;
+ private static final int DEFAULT_THREADS = 10;
+ private static final int DEFAULT_ITERATIONS = 100;
// If enabled only one request is allowed at the time. Useful for checking that test is working.
private static final boolean SYNCHRONIZED = false;
+ boolean passedCreateClient = false;
+ boolean passedCreateRole = false;
+
+ //@Test
+ public void testAllConcurrently() throws Throwable {
+ Thread client = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ createClient();
+ passedCreateClient = true;
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ });
+ Thread role = new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ createRole();
+ passedCreateRole = true;
+ } catch (Throwable throwable) {
+ throw new RuntimeException(throwable);
+ }
+ }
+ });
+
+ client.start();
+ role.start();
+ client.join();
+ role.join();
+ Assert.assertTrue(passedCreateClient);
+ Assert.assertTrue(passedCreateRole);
+ }
+
@Test
public void createClient() throws Throwable {
+ long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -75,10 +115,14 @@ public class ConcurrencyTest extends AbstractClientTest {
}
}
});
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createClient took " + end);
+
}
@Test
public void createRole() throws Throwable {
+ long start = System.currentTimeMillis();
run(new KeycloakRunnable() {
@Override
public void run(Keycloak keycloak, RealmResource realm, int threadNum, int iterationNum) {
@@ -88,10 +132,14 @@ public class ConcurrencyTest extends AbstractClientTest {
assertNotNull(realm.roles().get(name).toRepresentation());
}
});
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createRole took " + end);
+
}
@Test
public void createClientRole() throws Throwable {
+ long start = System.currentTimeMillis();
ClientRepresentation c = new ClientRepresentation();
c.setClientId("client");
Response response = realm.clients().create(c);
@@ -110,6 +158,9 @@ public class ConcurrencyTest extends AbstractClientTest {
assertNotNull(client.roles().get(name).toRepresentation());
}
});
+ long end = System.currentTimeMillis() - start;
+ System.out.println("createClientRole took " + end);
+
}
private void run(final KeycloakRunnable runnable) throws Throwable {
diff --git a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
index 401fc6b..155c8d0 100755
--- a/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/test/resources/META-INF/keycloak-server.json
@@ -33,7 +33,8 @@
},
"realmCache": {
- "infinispan" : {
+ "provider": "infinispan-revisioned",
+ "infinispan-locking" : {
"enabled": true
}
},
@@ -85,7 +86,8 @@
},
"connectionsInfinispan": {
- "default": {
+ "provider": "revisioned",
+ "locking": {
"clustered": "${keycloak.connectionsInfinispan.clustered:false}",
"async": "${keycloak.connectionsInfinispan.async:true}",
"sessionsOwners": "${keycloak.connectionsInfinispan.sessionsOwners:2}"