concurrency
This commit is contained in:
parent
ffc0e4f005
commit
347cdcbb28
25 changed files with 2649 additions and 141 deletions
|
@ -37,11 +37,11 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
|
|
||||||
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
|
protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.class);
|
||||||
|
|
||||||
private Config.Scope config;
|
protected Config.Scope config;
|
||||||
|
|
||||||
private EmbeddedCacheManager cacheManager;
|
protected EmbeddedCacheManager cacheManager;
|
||||||
|
|
||||||
private boolean containerManaged;
|
protected boolean containerManaged;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InfinispanConnectionProvider create(KeycloakSession session) {
|
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||||
|
@ -73,7 +73,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void lazyInit() {
|
protected void lazyInit() {
|
||||||
if (cacheManager == null) {
|
if (cacheManager == null) {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (cacheManager == null) {
|
if (cacheManager == null) {
|
||||||
|
@ -88,7 +88,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initContainerManaged(String cacheContainerLookup) {
|
protected void initContainerManaged(String cacheContainerLookup) {
|
||||||
try {
|
try {
|
||||||
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||||
containerManaged = true;
|
containerManaged = true;
|
||||||
|
@ -99,7 +99,7 @@ public class DefaultInfinispanConnectionProviderFactory implements InfinispanCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initEmbedded() {
|
protected void initEmbedded() {
|
||||||
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||||
|
|
||||||
boolean clustered = config.getBoolean("clustered", false);
|
boolean clustered = config.getBoolean("clustered", false);
|
||||||
|
|
10
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
vendored
Executable file
10
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/Revisioned.java
vendored
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public interface Revisioned {
|
||||||
|
Long getRevision();
|
||||||
|
void setRevision(Long revision);
|
||||||
|
}
|
398
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
vendored
Executable file
398
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProvider.java
vendored
Executable file
|
@ -0,0 +1,398 @@
|
||||||
|
/*
|
||||||
|
* 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.counter;
|
||||||
|
|
||||||
|
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.RealmCache;
|
||||||
|
import org.keycloak.models.cache.entities.CachedClient;
|
||||||
|
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||||
|
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.CachedRealmRole;
|
||||||
|
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.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 RevisionedCacheRealmProvider implements CacheRealmProvider {
|
||||||
|
protected static final Logger logger = Logger.getLogger(RevisionedCacheRealmProvider.class);
|
||||||
|
protected RevisionedRealmCache 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 RevisionedCacheRealmProvider(RevisionedRealmCache cache, KeycloakSession session) {
|
||||||
|
this.cache = cache;
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
session.getTransaction().enlistAfterCompletion(getTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 getTransaction() {
|
||||||
|
return new KeycloakTransaction() {
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
transactionActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
if (delegate == null) return;
|
||||||
|
if (clearAll) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
runInvalidations();
|
||||||
|
transactionActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
setRollbackOnly = true;
|
||||||
|
runInvalidations();
|
||||||
|
transactionActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.infov("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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
163
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
vendored
Executable file
163
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedCacheRealmProviderFactory.java
vendored
Executable file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* 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.counter;
|
||||||
|
|
||||||
|
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 org.keycloak.models.cache.infinispan.DefaultCacheRealmProvider;
|
||||||
|
import org.keycloak.models.cache.infinispan.InfinispanRealmCache;
|
||||||
|
|
||||||
|
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 RevisionedCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(RevisionedCacheRealmProviderFactory.class);
|
||||||
|
|
||||||
|
protected volatile RevisionedRealmCache realmCache;
|
||||||
|
|
||||||
|
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheRealmProvider create(KeycloakSession session) {
|
||||||
|
lazyInit(session);
|
||||||
|
return new RevisionedCacheRealmProvider(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(RevisionedConnectionProviderFactory.COUNTER_CACHE_NAME);
|
||||||
|
cache.addListener(new CacheListener());
|
||||||
|
realmCache = new RevisionedRealmCache(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-revisioned";
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* 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.counter;
|
||||||
|
|
||||||
|
import org.infinispan.configuration.cache.Configuration;
|
||||||
|
import org.infinispan.configuration.cache.ConfigurationBuilder;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RevisionedConnectionProviderFactory extends DefaultInfinispanConnectionProviderFactory {
|
||||||
|
public static final String COUNTER_CACHE_NAME = "COUNTER_CACHE";
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(RevisionedConnectionProviderFactory.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "revisioned";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void initEmbedded() {
|
||||||
|
super.initEmbedded();
|
||||||
|
ConfigurationBuilder counterConfigBuilder = new ConfigurationBuilder();
|
||||||
|
Configuration counterCacheConfiguration = counterConfigBuilder.build();
|
||||||
|
|
||||||
|
cacheManager.defineConfiguration(COUNTER_CACHE_NAME, counterCacheConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
278
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
vendored
Executable file
278
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/RevisionedRealmCache.java
vendored
Executable file
|
@ -0,0 +1,278 @@
|
||||||
|
/*
|
||||||
|
* 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.counter;
|
||||||
|
|
||||||
|
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 java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RevisionedRealmCache implements RealmCache {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(RevisionedRealmCache.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 RevisionedRealmCache(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 Long getCurrentRevision(String id) {
|
||||||
|
return revisions.get(id);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomicLong getRealmCounter() {
|
||||||
|
return realmCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomicLong getClientCounter() {
|
||||||
|
return clientCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomicLong getClientTemplateCounter() {
|
||||||
|
return clientTemplateCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomicLong getRoleCounter() {
|
||||||
|
return roleCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AtomicLong getGroupCounter() {
|
||||||
|
return groupCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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);
|
||||||
|
return id != null ? getCachedRealm(id) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedClient getApplication(String id) {
|
||||||
|
return get(id, CachedClient.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateApplication(CachedClient app) {
|
||||||
|
logger.infov("Removing application {0}", app.getClientAuthenticatorType());
|
||||||
|
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.infov("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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
41
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
vendored
Executable file
41
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedClient.java
vendored
Executable file
|
@ -0,0 +1,41 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.cache.RealmCache;
|
||||||
|
import org.keycloak.models.cache.entities.CachedClient;
|
||||||
|
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedClient extends CachedClient implements Revisioned {
|
||||||
|
|
||||||
|
public RevisionedCachedClient(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
|
||||||
|
super(cache, delegate, realm, model);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
|
||||||
|
for (RoleModel role : model.getRoles()) {
|
||||||
|
roles.put(role.getName(), role.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.cache.entities.CachedClientRole;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedClientRole extends CachedClientRole implements Revisioned {
|
||||||
|
|
||||||
|
public RevisionedCachedClientRole(Long revision, String idClient, RoleModel model, RealmModel realm) {
|
||||||
|
super(idClient, model, realm);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.cache.RealmCache;
|
||||||
|
import org.keycloak.models.cache.entities.CachedClientTemplate;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedClientTemplate extends CachedClientTemplate implements Revisioned {
|
||||||
|
|
||||||
|
public RevisionedCachedClientTemplate(Long revision, RealmCache cache, RealmProvider delegate, RealmModel realm, ClientTemplateModel model) {
|
||||||
|
super(cache, delegate, realm, model);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
31
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
vendored
Executable file
31
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedGroup.java
vendored
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.GroupModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.cache.entities.CachedGroup;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedGroup extends CachedGroup implements Revisioned {
|
||||||
|
public RevisionedCachedGroup(Long revision, RealmModel realm, GroupModel group) {
|
||||||
|
super(realm, group);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
59
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
vendored
Executable file
59
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedRealm.java
vendored
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.ClientTemplateModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RealmProvider;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
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.CachedRealm;
|
||||||
|
import org.keycloak.models.cache.entities.CachedRealmRole;
|
||||||
|
import org.keycloak.models.cache.entities.CachedRole;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedRealm extends CachedRealm implements Revisioned {
|
||||||
|
|
||||||
|
public RevisionedCachedRealm(Long revision, RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||||
|
super(cache, delegate, model);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||||
|
for (ClientTemplateModel template : model.getClientTemplates()) {
|
||||||
|
clientTemplates.add(template.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||||
|
for (ClientModel client : model.getClients()) {
|
||||||
|
clients.put(client.getClientId(), client.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
|
||||||
|
for (RoleModel role : model.getRoles()) {
|
||||||
|
realmRoles.put(role.getName(), role.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.cache.entities.CachedRealmRole;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedRealmRole extends CachedRealmRole implements Revisioned {
|
||||||
|
|
||||||
|
public RevisionedCachedRealmRole(Long revision, RoleModel model, RealmModel realm) {
|
||||||
|
super(model, realm);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
vendored
Executable file
30
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/counter/entities/RevisionedCachedUser.java
vendored
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
package org.keycloak.models.cache.infinispan.counter.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.cache.entities.CachedUser;
|
||||||
|
import org.keycloak.models.cache.infinispan.counter.Revisioned;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RevisionedCachedUser extends CachedUser implements Revisioned {
|
||||||
|
public RevisionedCachedUser(Long revision, RealmModel realm, UserModel user) {
|
||||||
|
super(realm, user);
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Long revision;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long getRevision() {
|
||||||
|
return revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setRevision(Long revision) {
|
||||||
|
this.revision = revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
/*
|
||||||
|
* 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.skewed;
|
||||||
|
|
||||||
|
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 RepeatableReadWriteSkewCacheRealmProviderFactory implements CacheRealmProviderFactory {
|
||||||
|
|
||||||
|
private static final Logger log = Logger.getLogger(RepeatableReadWriteSkewCacheRealmProviderFactory.class);
|
||||||
|
|
||||||
|
protected volatile RepeatableReadWriteSkewRealmCache realmCache;
|
||||||
|
|
||||||
|
protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CacheRealmProvider create(KeycloakSession session) {
|
||||||
|
lazyInit(session);
|
||||||
|
return new RepeatableReadWriteSkewRealmCacheProvider(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.addListener(new CacheListener());
|
||||||
|
realmCache = new RepeatableReadWriteSkewRealmCache(cache, realmLookup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "infinispan-versioned";
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
/*
|
||||||
|
* 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.skewed;
|
||||||
|
|
||||||
|
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.lookup.DummyTransactionManagerLookup;
|
||||||
|
import org.infinispan.util.concurrent.IsolationLevel;
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.Config;
|
||||||
|
import org.keycloak.connections.infinispan.DefaultInfinispanConnectionProvider;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
|
||||||
|
import javax.naming.InitialContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RepeatableReadWriteSkewConnectionProviderFactory implements InfinispanConnectionProviderFactory {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewConnectionProviderFactory.class);
|
||||||
|
|
||||||
|
private Config.Scope config;
|
||||||
|
|
||||||
|
private EmbeddedCacheManager cacheManager;
|
||||||
|
|
||||||
|
private boolean containerManaged;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InfinispanConnectionProvider create(KeycloakSession session) {
|
||||||
|
lazyInit();
|
||||||
|
|
||||||
|
return new DefaultInfinispanConnectionProvider(cacheManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (cacheManager != null && !containerManaged) {
|
||||||
|
cacheManager.stop();
|
||||||
|
}
|
||||||
|
cacheManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getId() {
|
||||||
|
return "versioned";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Config.Scope config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postInit(KeycloakSessionFactory factory) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lazyInit() {
|
||||||
|
if (cacheManager == null) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (cacheManager == null) {
|
||||||
|
String cacheContainer = config.get("cacheContainer");
|
||||||
|
if (cacheContainer != null) {
|
||||||
|
initContainerManaged(cacheContainer);
|
||||||
|
} else {
|
||||||
|
initEmbedded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initContainerManaged(String cacheContainerLookup) {
|
||||||
|
try {
|
||||||
|
cacheManager = (EmbeddedCacheManager) new InitialContext().lookup(cacheContainerLookup);
|
||||||
|
containerManaged = true;
|
||||||
|
|
||||||
|
logger.debugv("Using container managed Infinispan cache container, lookup={1}", cacheContainerLookup);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to retrieve cache container", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initEmbedded() {
|
||||||
|
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||||
|
|
||||||
|
boolean clustered = config.getBoolean("clustered", false);
|
||||||
|
boolean async = config.getBoolean("async", true);
|
||||||
|
boolean allowDuplicateJMXDomains = config.getBoolean("allowDuplicateJMXDomains", true);
|
||||||
|
|
||||||
|
if (clustered) {
|
||||||
|
gcb.transport().defaultTransport();
|
||||||
|
}
|
||||||
|
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||||
|
|
||||||
|
cacheManager = new DefaultCacheManager(gcb.build());
|
||||||
|
containerManaged = false;
|
||||||
|
|
||||||
|
logger.debug("Started embedded Infinispan cache container");
|
||||||
|
|
||||||
|
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||||
|
if (clustered) {
|
||||||
|
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidationConfigBuilder.transaction().transactionManagerLookup(new DummyTransactionManagerLookup());
|
||||||
|
invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationConfigBuilder.build());
|
||||||
|
|
||||||
|
ConfigurationBuilder userConfigBuilder = new ConfigurationBuilder();
|
||||||
|
if (clustered) {
|
||||||
|
userConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||||
|
}
|
||||||
|
Configuration userCacheConfiguration = userConfigBuilder.build();
|
||||||
|
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.USER_CACHE_NAME, userCacheConfiguration);
|
||||||
|
|
||||||
|
ConfigurationBuilder sessionConfigBuilder = new ConfigurationBuilder();
|
||||||
|
if (clustered) {
|
||||||
|
String sessionsMode = config.get("sessionsMode", "distributed");
|
||||||
|
if (sessionsMode.equalsIgnoreCase("replicated")) {
|
||||||
|
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
|
||||||
|
} else if (sessionsMode.equalsIgnoreCase("distributed")) {
|
||||||
|
sessionConfigBuilder.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("Invalid value for sessionsMode");
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionConfigBuilder.clustering().hash()
|
||||||
|
.numOwners(config.getInt("sessionsOwners", 2))
|
||||||
|
.numSegments(config.getInt("sessionsSegments", 60)).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Configuration sessionCacheConfiguration = sessionConfigBuilder.build();
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME, sessionCacheConfiguration);
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.LOGIN_FAILURE_CACHE_NAME, sessionCacheConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
269
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
vendored
Executable file
269
model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/skewed/RepeatableReadWriteSkewRealmCache.java
vendored
Executable file
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* 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.skewed;
|
||||||
|
|
||||||
|
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 javax.transaction.NotSupportedException;
|
||||||
|
import javax.transaction.Status;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class RepeatableReadWriteSkewRealmCache implements RealmCache {
|
||||||
|
|
||||||
|
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCache.class);
|
||||||
|
|
||||||
|
protected final Cache<String, Object> cache;
|
||||||
|
protected final ConcurrentHashMap<String, String> realmLookup;
|
||||||
|
|
||||||
|
public RepeatableReadWriteSkewRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
|
||||||
|
this.cache = cache;
|
||||||
|
this.realmLookup = realmLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cache<String, Object> getCache() {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startBatch() {
|
||||||
|
logger.trace("*** START BATCH ***");
|
||||||
|
try {
|
||||||
|
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().begin();
|
||||||
|
}
|
||||||
|
} catch (NotSupportedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void endBatch(boolean commit) {
|
||||||
|
logger.trace("*** END BATCH ***");
|
||||||
|
try {
|
||||||
|
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
|
||||||
|
if (commit) {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().commit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().rollback();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
//throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
invalidate(realm.getId());
|
||||||
|
realmLookup.remove(realm.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object invalidate(String id) {
|
||||||
|
startBatch();
|
||||||
|
Object rtn = cache.remove(id);
|
||||||
|
logger.trace("*** END BATCH ***");
|
||||||
|
try {
|
||||||
|
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
|
||||||
|
if (true) {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().commit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().rollback();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.info("Failed to commit invalidate");
|
||||||
|
}
|
||||||
|
return rtn;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedRealmById(String id) {
|
||||||
|
CachedRealm cached = (CachedRealm) invalidate(id);
|
||||||
|
if (cached != null) realmLookup.remove(cached.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedRealm(CachedRealm realm) {
|
||||||
|
logger.tracev("Adding realm {0}", realm.getId());
|
||||||
|
cache.putForExternalRead(realm.getId(), realm);
|
||||||
|
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());
|
||||||
|
invalidate(app.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedClient(CachedClient app) {
|
||||||
|
logger.tracev("Adding application {0}", app.getId());
|
||||||
|
cache.putForExternalRead(app.getId(), app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedApplicationById(String id) {
|
||||||
|
logger.tracev("Removing application {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
invalidate(role.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedGroup(CachedGroup role) {
|
||||||
|
logger.tracev("Adding group {0}", role.getId());
|
||||||
|
cache.putForExternalRead(role.getId(), role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedGroupById(String id) {
|
||||||
|
logger.tracev("Removing group {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateGroupById(String id) {
|
||||||
|
logger.tracev("Removing group {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CachedRole getRole(String id) {
|
||||||
|
return get(id, CachedRole.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateRole(CachedRole role) {
|
||||||
|
logger.tracev("Removing role {0}", role.getId());
|
||||||
|
invalidate(role.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateRoleById(String id) {
|
||||||
|
logger.tracev("Removing role {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
cache.putForExternalRead(role.getId(), role);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedRoleById(String id) {
|
||||||
|
logger.tracev("Removing role {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T get(String id, Class<T> type) {
|
||||||
|
Object o = cache.get(id);
|
||||||
|
return o != null && type.isInstance(o) ? type.cast(o) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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());
|
||||||
|
invalidate(app.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addCachedClientTemplate(CachedClientTemplate app) {
|
||||||
|
logger.tracev("Adding client template {0}", app.getId());
|
||||||
|
cache.putForExternalRead(app.getId(), app);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void invalidateCachedClientTemplateById(String id) {
|
||||||
|
logger.tracev("Removing client template {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void evictCachedClientTemplateById(String id) {
|
||||||
|
logger.tracev("Evicting client template {0}", id);
|
||||||
|
invalidate(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,472 @@
|
||||||
|
/*
|
||||||
|
* 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.skewed;
|
||||||
|
|
||||||
|
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.CachedClientRole;
|
||||||
|
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.CachedRealmRole;
|
||||||
|
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 java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DO NOT USE THIS!!
|
||||||
|
*
|
||||||
|
* Tries unsuccessfully to use Infinispan with REPEATABLE_READ, write-skew-checking
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class RepeatableReadWriteSkewRealmCacheProvider implements CacheRealmProvider {
|
||||||
|
protected static final Logger logger = Logger.getLogger(RepeatableReadWriteSkewRealmCacheProvider.class);
|
||||||
|
|
||||||
|
protected RepeatableReadWriteSkewRealmCache 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 RepeatableReadWriteSkewRealmCacheProvider(RepeatableReadWriteSkewRealmCache cache, KeycloakSession session) {
|
||||||
|
this.cache = cache;
|
||||||
|
this.session = session;
|
||||||
|
|
||||||
|
session.getTransaction().enlistAfterCompletion(getTransaction());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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 getTransaction() {
|
||||||
|
return new KeycloakTransaction() {
|
||||||
|
@Override
|
||||||
|
public void begin() {
|
||||||
|
transactionActive = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void commit() {
|
||||||
|
if (delegate == null) return;
|
||||||
|
if (clearAll) {
|
||||||
|
cache.clear();
|
||||||
|
}
|
||||||
|
runInvalidations();
|
||||||
|
transactionActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void rollback() {
|
||||||
|
setRollbackOnly = true;
|
||||||
|
runInvalidations();
|
||||||
|
transactionActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
//cache.startBatch();
|
||||||
|
cache.startBatch();
|
||||||
|
boolean batchEnded = false;
|
||||||
|
try {
|
||||||
|
CachedRealm cached = cache.getCachedRealm(id);
|
||||||
|
boolean wasNull = cached == null;
|
||||||
|
if (cached == null) {
|
||||||
|
RealmModel model = getDelegate().getRealm(id);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (realmInvalidations.contains(id)) return model;
|
||||||
|
cached = new CachedRealm(cache, this, model);
|
||||||
|
cache.addCachedRealm(cached);
|
||||||
|
try {
|
||||||
|
batchEnded = true;
|
||||||
|
cache.endBatch(true);
|
||||||
|
logger.trace("returning new cached realm");
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.info("failed to add to cache", exception);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
} else if (realmInvalidations.contains(id)) {
|
||||||
|
return getDelegate().getRealm(id);
|
||||||
|
} else if (managedRealms.containsKey(id)) {
|
||||||
|
return managedRealms.get(id);
|
||||||
|
}
|
||||||
|
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
|
||||||
|
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||||
|
managedRealms.put(id, adapter);
|
||||||
|
return adapter;
|
||||||
|
} finally {
|
||||||
|
if (!batchEnded) cache.endBatch(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealmByName(String name) {
|
||||||
|
cache.startBatch();
|
||||||
|
boolean batchEnded = false;
|
||||||
|
try {
|
||||||
|
CachedRealm cached = cache.getCachedRealmByName(name);
|
||||||
|
boolean wasNull = cached == null;
|
||||||
|
if (cached == null) {
|
||||||
|
RealmModel model = getDelegate().getRealmByName(name);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (realmInvalidations.contains(model.getId())) return model;
|
||||||
|
cached = new CachedRealm(cache, this, model);
|
||||||
|
cache.addCachedRealm(cached);
|
||||||
|
try {
|
||||||
|
batchEnded = true;
|
||||||
|
cache.endBatch(true);
|
||||||
|
logger.trace("returning new cached realm: " + cached.getName());
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.info("failed to add to cache", exception);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
} else if (realmInvalidations.contains(cached.getId())) {
|
||||||
|
return getDelegate().getRealmByName(name);
|
||||||
|
} else if (managedRealms.containsKey(cached.getId())) {
|
||||||
|
return managedRealms.get(cached.getId());
|
||||||
|
}
|
||||||
|
if (!wasNull) logger.trace("returning cached realm: " + cached.getName());
|
||||||
|
RealmAdapter adapter = new RealmAdapter(cached, this);
|
||||||
|
managedRealms.put(cached.getId(), adapter);
|
||||||
|
return adapter;
|
||||||
|
} finally {
|
||||||
|
if (!batchEnded) cache.endBatch(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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) {
|
||||||
|
cache.startBatch();
|
||||||
|
boolean batchEnded = false;
|
||||||
|
try {
|
||||||
|
CachedRole cached = cache.getRole(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
RoleModel model = getDelegate().getRoleById(id, realm);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (roleInvalidations.contains(id)) return model;
|
||||||
|
if (model.getContainer() instanceof ClientModel) {
|
||||||
|
cached = new CachedClientRole(((ClientModel) model.getContainer()).getId(), model, realm);
|
||||||
|
} else {
|
||||||
|
cached = new CachedRealmRole(model, realm);
|
||||||
|
}
|
||||||
|
cache.addCachedRole(cached);
|
||||||
|
try {
|
||||||
|
batchEnded = true;
|
||||||
|
cache.endBatch(true);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.info("failed to add to cache", exception);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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;
|
||||||
|
} finally {
|
||||||
|
if (!batchEnded) cache.endBatch(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GroupModel getGroupById(String id, RealmModel realm) {
|
||||||
|
cache.startBatch();
|
||||||
|
boolean batchEnded = false;
|
||||||
|
try {
|
||||||
|
CachedGroup cached = cache.getGroup(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
GroupModel model = getDelegate().getGroupById(id, realm);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (groupInvalidations.contains(id)) return model;
|
||||||
|
cached = new CachedGroup(realm, model);
|
||||||
|
cache.addCachedGroup(cached);
|
||||||
|
try {
|
||||||
|
batchEnded = true;
|
||||||
|
cache.endBatch(true);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.info("failed to add to cache", exception);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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;
|
||||||
|
} finally {
|
||||||
|
if (!batchEnded) cache.endBatch(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientModel getClientById(String id, RealmModel realm) {
|
||||||
|
cache.startBatch();
|
||||||
|
boolean batchEnded = false;
|
||||||
|
CachedClient cached = cache.getApplication(id);
|
||||||
|
try {
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
ClientModel model = getDelegate().getClientById(id, realm);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (appInvalidations.contains(id)) return model;
|
||||||
|
cached = new CachedClient(cache, getDelegate(), realm, model);
|
||||||
|
cache.addCachedClient(cached);
|
||||||
|
try {
|
||||||
|
batchEnded = true;
|
||||||
|
cache.endBatch(true);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.info("failed to add to cache", exception);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
} finally {
|
||||||
|
if (!batchEnded) cache.endBatch(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ClientTemplateModel getClientTemplateById(String id, RealmModel realm) {
|
||||||
|
cache.startBatch();
|
||||||
|
boolean batchEnded = false;
|
||||||
|
try {
|
||||||
|
CachedClientTemplate cached = cache.getClientTemplate(id);
|
||||||
|
if (cached != null && !cached.getRealm().equals(realm.getId())) {
|
||||||
|
cached = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
ClientTemplateModel model = getDelegate().getClientTemplateById(id, realm);
|
||||||
|
if (model == null) return null;
|
||||||
|
if (clientTemplateInvalidations.contains(id)) return model;
|
||||||
|
cached = new CachedClientTemplate(cache, getDelegate(), realm, model);
|
||||||
|
cache.addCachedClientTemplate(cached);
|
||||||
|
try {
|
||||||
|
batchEnded = true;
|
||||||
|
cache.endBatch(true);
|
||||||
|
} catch (Exception exception) {
|
||||||
|
logger.info("failed to add to cache", exception);
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
} 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;
|
||||||
|
} finally {
|
||||||
|
if (!batchEnded) cache.endBatch(true);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
3
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
Normal file → Executable file
3
model/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
Normal file → Executable file
|
@ -15,4 +15,5 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
|
||||||
|
org.keycloak.models.cache.infinispan.counter.RevisionedConnectionProviderFactory
|
|
@ -15,4 +15,5 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
|
org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
|
||||||
|
org.keycloak.models.cache.infinispan.counter.RevisionedCacheRealmProviderFactory
|
|
@ -0,0 +1,267 @@
|
||||||
|
package org.keycloak.models.sessions.infinispan.initializer;
|
||||||
|
|
||||||
|
import org.infinispan.Cache;
|
||||||
|
import org.infinispan.commons.CacheException;
|
||||||
|
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.TransactionMode;
|
||||||
|
import org.infinispan.transaction.TransactionProtocol;
|
||||||
|
import org.infinispan.transaction.lookup.DummyTransactionManagerLookup;
|
||||||
|
import org.infinispan.util.concurrent.IsolationLevel;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
|
||||||
|
|
||||||
|
import javax.transaction.NotSupportedException;
|
||||||
|
import javax.transaction.Status;
|
||||||
|
import javax.transaction.SystemException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests to make sure our model caching concurrency model will work.
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
|
* @version $Revision: 1 $
|
||||||
|
*/
|
||||||
|
public class ConcurrencyVersioningTest {
|
||||||
|
|
||||||
|
public static abstract class AbstractThread implements Runnable {
|
||||||
|
EmbeddedCacheManager cacheManager;
|
||||||
|
boolean success;
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
|
||||||
|
public AbstractThread(EmbeddedCacheManager cacheManager) {
|
||||||
|
this.cacheManager = cacheManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSuccess() {
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CountDownLatch getLatch() {
|
||||||
|
return latch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RemoveThread extends AbstractThread {
|
||||||
|
public RemoveThread(EmbeddedCacheManager cacheManager) {
|
||||||
|
super(cacheManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
try {
|
||||||
|
startBatch(cache);
|
||||||
|
cache.remove("key");
|
||||||
|
//cache.getAdvancedCache().getTransactionManager().commit();
|
||||||
|
endBatch(cache);
|
||||||
|
success = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class UpdateThread extends AbstractThread {
|
||||||
|
public UpdateThread(EmbeddedCacheManager cacheManager) {
|
||||||
|
super(cacheManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void run() {
|
||||||
|
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
try {
|
||||||
|
startBatch(cache);
|
||||||
|
cache.putForExternalRead("key", "value2");
|
||||||
|
//cache.getAdvancedCache().getTransactionManager().commit();
|
||||||
|
endBatch(cache);
|
||||||
|
success = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that if remove executes before put, then put still succeeds.
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetRemovePutOnNonExisting() throws Exception {
|
||||||
|
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||||
|
|
||||||
|
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
cache.remove("key");
|
||||||
|
startBatch(cache);
|
||||||
|
cache.get("key");
|
||||||
|
executor.execute(removeThread);
|
||||||
|
removeThread.getLatch().await();
|
||||||
|
cache.putForExternalRead("key", "value1");
|
||||||
|
endBatch(cache);
|
||||||
|
Assert.assertEquals(cache.get("key"), "value1");
|
||||||
|
Assert.assertTrue(removeThread.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetRemovePutOnExisting() throws Exception {
|
||||||
|
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||||
|
|
||||||
|
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
cache.put("key", "value0");
|
||||||
|
startBatch(cache);
|
||||||
|
cache.get("key");
|
||||||
|
executor.execute(removeThread);
|
||||||
|
removeThread.getLatch().await();
|
||||||
|
cache.put("key", "value1");
|
||||||
|
try {
|
||||||
|
endBatch(cache);
|
||||||
|
Assert.fail("Write skew should be detected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertNull(cache.get("key"));
|
||||||
|
Assert.assertTrue(removeThread.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that if a put of an existing key is removed after the put and before tx commit, it is evicted
|
||||||
|
*
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testGetRemovePutEternalOnExisting() throws Exception {
|
||||||
|
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||||
|
|
||||||
|
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
cache.put("key", "value0");
|
||||||
|
startBatch(cache);
|
||||||
|
cache.get("key");
|
||||||
|
executor.execute(removeThread);
|
||||||
|
cache.putForExternalRead("key", "value1");
|
||||||
|
removeThread.getLatch().await();
|
||||||
|
try {
|
||||||
|
endBatch(cache);
|
||||||
|
// Assert.fail("Write skew should be detected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertNull(cache.get("key"));
|
||||||
|
Assert.assertTrue(removeThread.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPutExternalRemoveOnExisting() throws Exception {
|
||||||
|
final DefaultCacheManager cacheManager = getVersionedCacheManager();
|
||||||
|
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
RemoveThread removeThread = new RemoveThread(cacheManager);
|
||||||
|
|
||||||
|
Cache<String, String> cache = cacheManager.getCache(InfinispanConnectionProvider.REALM_CACHE_NAME);
|
||||||
|
cache.put("key", "value0");
|
||||||
|
startBatch(cache);
|
||||||
|
cache.putForExternalRead("key", "value1");
|
||||||
|
executor.execute(removeThread);
|
||||||
|
removeThread.getLatch().await();
|
||||||
|
try {
|
||||||
|
endBatch(cache);
|
||||||
|
// Assert.fail("Write skew should be detected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
Assert.assertNull(cache.get("key"));
|
||||||
|
Assert.assertTrue(removeThread.isSuccess());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void startBatch(Cache<String, String> cache) {
|
||||||
|
try {
|
||||||
|
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_NO_TRANSACTION) {
|
||||||
|
System.out.println("begin");
|
||||||
|
cache.getAdvancedCache().getTransactionManager().begin();
|
||||||
|
}
|
||||||
|
} catch (NotSupportedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
} catch (SystemException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void endBatch(Cache<String, String> cache) {
|
||||||
|
boolean commit = true;
|
||||||
|
try {
|
||||||
|
if (cache.getAdvancedCache().getTransactionManager().getStatus() == Status.STATUS_ACTIVE) {
|
||||||
|
if (commit) {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().commit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
cache.getAdvancedCache().getTransactionManager().rollback();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected DefaultCacheManager getVersionedCacheManager() {
|
||||||
|
GlobalConfigurationBuilder gcb = new GlobalConfigurationBuilder();
|
||||||
|
|
||||||
|
|
||||||
|
boolean clustered = false;
|
||||||
|
boolean async = false;
|
||||||
|
boolean allowDuplicateJMXDomains = true;
|
||||||
|
|
||||||
|
if (clustered) {
|
||||||
|
gcb.transport().defaultTransport();
|
||||||
|
}
|
||||||
|
gcb.globalJmxStatistics().allowDuplicateDomains(allowDuplicateJMXDomains);
|
||||||
|
|
||||||
|
final DefaultCacheManager cacheManager = new DefaultCacheManager(gcb.build());
|
||||||
|
ConfigurationBuilder invalidationConfigBuilder = new ConfigurationBuilder();
|
||||||
|
invalidationConfigBuilder
|
||||||
|
//.invocationBatching().enable()
|
||||||
|
.transaction().transactionMode(TransactionMode.TRANSACTIONAL)
|
||||||
|
.transaction().transactionManagerLookup(new DummyTransactionManagerLookup())
|
||||||
|
.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
|
||||||
|
|
||||||
|
|
||||||
|
//invalidationConfigBuilder.locking().isolationLevel(IsolationLevel.REPEATABLE_READ).writeSkewCheck(true).versioning().enable().scheme(VersioningScheme.SIMPLE);
|
||||||
|
|
||||||
|
if (clustered) {
|
||||||
|
invalidationConfigBuilder.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
|
||||||
|
}
|
||||||
|
Configuration invalidationCacheConfiguration = invalidationConfigBuilder.build();
|
||||||
|
cacheManager.defineConfiguration(InfinispanConnectionProvider.REALM_CACHE_NAME, invalidationCacheConfiguration);
|
||||||
|
return cacheManager;
|
||||||
|
}
|
||||||
|
}
|
|
@ -145,8 +145,9 @@ public class RealmEntity {
|
||||||
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
|
Collection<UserFederationMapperEntity> userFederationMappers = new ArrayList<UserFederationMapperEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="realm")
|
||||||
@JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") })
|
//@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)
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
|
|
|
@ -39,43 +39,43 @@ import java.util.TreeMap;
|
||||||
*/
|
*/
|
||||||
public class CachedClient implements Serializable {
|
public class CachedClient implements Serializable {
|
||||||
|
|
||||||
private String id;
|
protected String id;
|
||||||
private String clientId;
|
protected String clientId;
|
||||||
private String name;
|
protected String name;
|
||||||
private String description;
|
protected String description;
|
||||||
private String realm;
|
protected String realm;
|
||||||
private Set<String> redirectUris = new HashSet<String>();
|
protected Set<String> redirectUris = new HashSet<String>();
|
||||||
private boolean enabled;
|
protected boolean enabled;
|
||||||
private String clientAuthenticatorType;
|
protected String clientAuthenticatorType;
|
||||||
private String secret;
|
protected String secret;
|
||||||
private String registrationToken;
|
protected String registrationToken;
|
||||||
private String protocol;
|
protected String protocol;
|
||||||
private Map<String, String> attributes = new HashMap<String, String>();
|
protected Map<String, String> attributes = new HashMap<String, String>();
|
||||||
private boolean publicClient;
|
protected boolean publicClient;
|
||||||
private boolean fullScopeAllowed;
|
protected boolean fullScopeAllowed;
|
||||||
private boolean frontchannelLogout;
|
protected boolean frontchannelLogout;
|
||||||
private int notBefore;
|
protected int notBefore;
|
||||||
private Set<String> scope = new HashSet<String>();
|
protected Set<String> scope = new HashSet<String>();
|
||||||
private Set<String> webOrigins = new HashSet<String>();
|
protected Set<String> webOrigins = new HashSet<String>();
|
||||||
private Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
|
protected Set<ProtocolMapperModel> protocolMappers = new HashSet<ProtocolMapperModel>();
|
||||||
private boolean surrogateAuthRequired;
|
protected boolean surrogateAuthRequired;
|
||||||
private String managementUrl;
|
protected String managementUrl;
|
||||||
private String rootUrl;
|
protected String rootUrl;
|
||||||
private String baseUrl;
|
protected String baseUrl;
|
||||||
private List<String> defaultRoles = new LinkedList<String>();
|
protected List<String> defaultRoles = new LinkedList<String>();
|
||||||
private boolean bearerOnly;
|
protected boolean bearerOnly;
|
||||||
private boolean consentRequired;
|
protected boolean consentRequired;
|
||||||
private boolean standardFlowEnabled;
|
protected boolean standardFlowEnabled;
|
||||||
private boolean implicitFlowEnabled;
|
protected boolean implicitFlowEnabled;
|
||||||
private boolean directAccessGrantsEnabled;
|
protected boolean directAccessGrantsEnabled;
|
||||||
private boolean serviceAccountsEnabled;
|
protected boolean serviceAccountsEnabled;
|
||||||
private Map<String, String> roles = new HashMap<String, String>();
|
protected Map<String, String> roles = new HashMap<String, String>();
|
||||||
private int nodeReRegistrationTimeout;
|
protected int nodeReRegistrationTimeout;
|
||||||
private Map<String, Integer> registeredNodes;
|
protected Map<String, Integer> registeredNodes;
|
||||||
private String clientTemplate;
|
protected String clientTemplate;
|
||||||
private boolean useTemplateScope;
|
protected boolean useTemplateScope;
|
||||||
private boolean useTemplateConfig;
|
protected boolean useTemplateConfig;
|
||||||
private boolean useTemplateMappers;
|
protected boolean useTemplateMappers;
|
||||||
|
|
||||||
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
|
public CachedClient(RealmCache cache, RealmProvider delegate, RealmModel realm, ClientModel model) {
|
||||||
id = model.getId();
|
id = model.getId();
|
||||||
|
@ -112,10 +112,7 @@ public class CachedClient implements Serializable {
|
||||||
implicitFlowEnabled = model.isImplicitFlowEnabled();
|
implicitFlowEnabled = model.isImplicitFlowEnabled();
|
||||||
directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
|
directAccessGrantsEnabled = model.isDirectAccessGrantsEnabled();
|
||||||
serviceAccountsEnabled = model.isServiceAccountsEnabled();
|
serviceAccountsEnabled = model.isServiceAccountsEnabled();
|
||||||
for (RoleModel role : model.getRoles()) {
|
cacheRoles(cache, realm, model);
|
||||||
roles.put(role.getName(), role.getId());
|
|
||||||
cache.addCachedRole(new CachedClientRole(id, role, realm));
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();
|
nodeReRegistrationTimeout = model.getNodeReRegistrationTimeout();
|
||||||
registeredNodes = new TreeMap<String, Integer>(model.getRegisteredNodes());
|
registeredNodes = new TreeMap<String, Integer>(model.getRegisteredNodes());
|
||||||
|
@ -126,6 +123,14 @@ public class CachedClient implements Serializable {
|
||||||
useTemplateMappers = model.useTemplateMappers();
|
useTemplateMappers = model.useTemplateMappers();
|
||||||
useTemplateScope = model.useTemplateScope();
|
useTemplateScope = model.useTemplateScope();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void cacheRoles(RealmCache cache, RealmModel realm, ClientModel model) {
|
||||||
|
for (RoleModel role : model.getRoles()) {
|
||||||
|
roles.put(role.getName(), role.getId());
|
||||||
|
cache.addCachedRole(new CachedClientRole(id, role, realm));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,90 +53,90 @@ import java.util.Set;
|
||||||
*/
|
*/
|
||||||
public class CachedRealm implements Serializable {
|
public class CachedRealm implements Serializable {
|
||||||
|
|
||||||
private String id;
|
protected String id;
|
||||||
private String name;
|
protected String name;
|
||||||
private String displayName;
|
protected String displayName;
|
||||||
private String displayNameHtml;
|
protected String displayNameHtml;
|
||||||
private boolean enabled;
|
protected boolean enabled;
|
||||||
private SslRequired sslRequired;
|
protected SslRequired sslRequired;
|
||||||
private boolean registrationAllowed;
|
protected boolean registrationAllowed;
|
||||||
private boolean registrationEmailAsUsername;
|
protected boolean registrationEmailAsUsername;
|
||||||
private boolean rememberMe;
|
protected boolean rememberMe;
|
||||||
private boolean verifyEmail;
|
protected boolean verifyEmail;
|
||||||
private boolean resetPasswordAllowed;
|
protected boolean resetPasswordAllowed;
|
||||||
private boolean identityFederationEnabled;
|
protected boolean identityFederationEnabled;
|
||||||
private boolean editUsernameAllowed;
|
protected boolean editUsernameAllowed;
|
||||||
//--- brute force settings
|
//--- brute force settings
|
||||||
private boolean bruteForceProtected;
|
protected boolean bruteForceProtected;
|
||||||
private int maxFailureWaitSeconds;
|
protected int maxFailureWaitSeconds;
|
||||||
private int minimumQuickLoginWaitSeconds;
|
protected int minimumQuickLoginWaitSeconds;
|
||||||
private int waitIncrementSeconds;
|
protected int waitIncrementSeconds;
|
||||||
private long quickLoginCheckMilliSeconds;
|
protected long quickLoginCheckMilliSeconds;
|
||||||
private int maxDeltaTimeSeconds;
|
protected int maxDeltaTimeSeconds;
|
||||||
private int failureFactor;
|
protected int failureFactor;
|
||||||
//--- end brute force settings
|
//--- end brute force settings
|
||||||
|
|
||||||
private boolean revokeRefreshToken;
|
protected boolean revokeRefreshToken;
|
||||||
private int ssoSessionIdleTimeout;
|
protected int ssoSessionIdleTimeout;
|
||||||
private int ssoSessionMaxLifespan;
|
protected int ssoSessionMaxLifespan;
|
||||||
private int offlineSessionIdleTimeout;
|
protected int offlineSessionIdleTimeout;
|
||||||
private int accessTokenLifespan;
|
protected int accessTokenLifespan;
|
||||||
private int accessTokenLifespanForImplicitFlow;
|
protected int accessTokenLifespanForImplicitFlow;
|
||||||
private int accessCodeLifespan;
|
protected int accessCodeLifespan;
|
||||||
private int accessCodeLifespanUserAction;
|
protected int accessCodeLifespanUserAction;
|
||||||
private int accessCodeLifespanLogin;
|
protected int accessCodeLifespanLogin;
|
||||||
private int notBefore;
|
protected int notBefore;
|
||||||
private PasswordPolicy passwordPolicy;
|
protected PasswordPolicy passwordPolicy;
|
||||||
private OTPPolicy otpPolicy;
|
protected OTPPolicy otpPolicy;
|
||||||
|
|
||||||
private String publicKeyPem;
|
protected String publicKeyPem;
|
||||||
private String privateKeyPem;
|
protected String privateKeyPem;
|
||||||
private String certificatePem;
|
protected String certificatePem;
|
||||||
private String codeSecret;
|
protected String codeSecret;
|
||||||
|
|
||||||
private String loginTheme;
|
protected String loginTheme;
|
||||||
private String accountTheme;
|
protected String accountTheme;
|
||||||
private String adminTheme;
|
protected String adminTheme;
|
||||||
private String emailTheme;
|
protected String emailTheme;
|
||||||
private String masterAdminClient;
|
protected String masterAdminClient;
|
||||||
|
|
||||||
private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
|
protected List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
|
||||||
private List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
|
protected List<UserFederationProviderModel> userFederationProviders = new ArrayList<UserFederationProviderModel>();
|
||||||
private MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
|
protected MultivaluedHashMap<String, UserFederationMapperModel> userFederationMappers = new MultivaluedHashMap<String, UserFederationMapperModel>();
|
||||||
private List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
|
protected List<IdentityProviderModel> identityProviders = new ArrayList<IdentityProviderModel>();
|
||||||
|
|
||||||
private Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
|
protected Map<String, String> browserSecurityHeaders = new HashMap<String, String>();
|
||||||
private Map<String, String> smtpConfig = new HashMap<String, String>();
|
protected Map<String, String> smtpConfig = new HashMap<String, String>();
|
||||||
private Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
|
protected Map<String, AuthenticationFlowModel> authenticationFlows = new HashMap<>();
|
||||||
private Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
|
protected Map<String, AuthenticatorConfigModel> authenticatorConfigs = new HashMap<>();
|
||||||
private Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
|
protected Map<String, RequiredActionProviderModel> requiredActionProviders = new HashMap<>();
|
||||||
private Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
|
protected Map<String, RequiredActionProviderModel> requiredActionProvidersByAlias = new HashMap<>();
|
||||||
private MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
|
protected MultivaluedHashMap<String, AuthenticationExecutionModel> authenticationExecutions = new MultivaluedHashMap<>();
|
||||||
private Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
|
protected Map<String, AuthenticationExecutionModel> executionsById = new HashMap<>();
|
||||||
|
|
||||||
private AuthenticationFlowModel browserFlow;
|
protected AuthenticationFlowModel browserFlow;
|
||||||
private AuthenticationFlowModel registrationFlow;
|
protected AuthenticationFlowModel registrationFlow;
|
||||||
private AuthenticationFlowModel directGrantFlow;
|
protected AuthenticationFlowModel directGrantFlow;
|
||||||
private AuthenticationFlowModel resetCredentialsFlow;
|
protected AuthenticationFlowModel resetCredentialsFlow;
|
||||||
private AuthenticationFlowModel clientAuthenticationFlow;
|
protected AuthenticationFlowModel clientAuthenticationFlow;
|
||||||
|
|
||||||
private boolean eventsEnabled;
|
protected boolean eventsEnabled;
|
||||||
private long eventsExpiration;
|
protected long eventsExpiration;
|
||||||
private Set<String> eventsListeners = new HashSet<String>();
|
protected Set<String> eventsListeners = new HashSet<String>();
|
||||||
private Set<String> enabledEventTypes = new HashSet<String>();
|
protected Set<String> enabledEventTypes = new HashSet<String>();
|
||||||
protected boolean adminEventsEnabled;
|
protected boolean adminEventsEnabled;
|
||||||
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
|
protected Set<String> adminEnabledEventOperations = new HashSet<String>();
|
||||||
protected boolean adminEventsDetailsEnabled;
|
protected boolean adminEventsDetailsEnabled;
|
||||||
private List<String> defaultRoles = new LinkedList<String>();
|
protected List<String> defaultRoles = new LinkedList<String>();
|
||||||
private List<String> defaultGroups = new LinkedList<String>();
|
protected List<String> defaultGroups = new LinkedList<String>();
|
||||||
private Set<String> groups = new HashSet<String>();
|
protected Set<String> groups = new HashSet<String>();
|
||||||
private Map<String, String> realmRoles = new HashMap<String, String>();
|
protected Map<String, String> realmRoles = new HashMap<String, String>();
|
||||||
private Map<String, String> clients = new HashMap<String, String>();
|
protected Map<String, String> clients = new HashMap<String, String>();
|
||||||
private List<String> clientTemplates= new LinkedList<>();
|
protected List<String> clientTemplates= new LinkedList<>();
|
||||||
private boolean internationalizationEnabled;
|
protected boolean internationalizationEnabled;
|
||||||
private Set<String> supportedLocales = new HashSet<String>();
|
protected Set<String> supportedLocales = new HashSet<String>();
|
||||||
private String defaultLocale;
|
protected String defaultLocale;
|
||||||
private MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
|
protected MultivaluedHashMap<String, IdentityProviderMapperModel> identityProviderMappers = new MultivaluedHashMap<>();
|
||||||
|
|
||||||
public CachedRealm() {
|
public CachedRealm() {
|
||||||
}
|
}
|
||||||
|
@ -221,23 +221,11 @@ public class CachedRealm implements Serializable {
|
||||||
ClientModel masterAdminClient = model.getMasterAdminClient();
|
ClientModel masterAdminClient = model.getMasterAdminClient();
|
||||||
this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null;
|
this.masterAdminClient = (masterAdminClient != null) ? masterAdminClient.getId() : null;
|
||||||
|
|
||||||
for (RoleModel role : model.getRoles()) {
|
cacheRealmRoles(cache, model);
|
||||||
realmRoles.put(role.getName(), role.getId());
|
|
||||||
CachedRole cachedRole = new CachedRealmRole(role, model);
|
|
||||||
cache.addCachedRole(cachedRole);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ClientModel client : model.getClients()) {
|
cacheClients(cache, delegate, model);
|
||||||
clients.put(client.getClientId(), client.getId());
|
|
||||||
CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
|
|
||||||
cache.addCachedClient(cachedClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (ClientTemplateModel template : model.getClientTemplates()) {
|
cacheClientTemplates(cache, delegate, model);
|
||||||
clientTemplates.add(template.getId());
|
|
||||||
CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
|
|
||||||
cache.addCachedClientTemplate(cachedClient);
|
|
||||||
}
|
|
||||||
|
|
||||||
internationalizationEnabled = model.isInternationalizationEnabled();
|
internationalizationEnabled = model.isInternationalizationEnabled();
|
||||||
supportedLocales.addAll(model.getSupportedLocales());
|
supportedLocales.addAll(model.getSupportedLocales());
|
||||||
|
@ -273,6 +261,30 @@ public class CachedRealm implements Serializable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void cacheClientTemplates(RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||||
|
for (ClientTemplateModel template : model.getClientTemplates()) {
|
||||||
|
clientTemplates.add(template.getId());
|
||||||
|
CachedClientTemplate cachedClient = new CachedClientTemplate(cache, delegate, model, template);
|
||||||
|
cache.addCachedClientTemplate(cachedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cacheClients(RealmCache cache, RealmProvider delegate, RealmModel model) {
|
||||||
|
for (ClientModel client : model.getClients()) {
|
||||||
|
clients.put(client.getClientId(), client.getId());
|
||||||
|
CachedClient cachedClient = new CachedClient(cache, delegate, model, client);
|
||||||
|
cache.addCachedClient(cachedClient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void cacheRealmRoles(RealmCache cache, RealmModel model) {
|
||||||
|
for (RoleModel role : model.getRoles()) {
|
||||||
|
realmRoles.put(role.getName(), role.getId());
|
||||||
|
CachedRole cachedRole = new CachedRealmRole(role, model);
|
||||||
|
cache.addCachedRole(cachedRole);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return id;
|
return id;
|
||||||
|
|
|
@ -44,8 +44,8 @@ public class ConcurrencyTest extends AbstractClientTest {
|
||||||
|
|
||||||
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
|
private static final Logger log = Logger.getLogger(ConcurrencyTest.class);
|
||||||
|
|
||||||
private static final int DEFAULT_THREADS = 3;
|
private static final int DEFAULT_THREADS = 5;
|
||||||
private static final int DEFAULT_ITERATIONS = 10;
|
private static final int DEFAULT_ITERATIONS = 30;
|
||||||
|
|
||||||
// If enabled only one request is allowed at the time. Useful for checking that test is working.
|
// If enabled only one request is allowed at the time. Useful for checking that test is working.
|
||||||
private static final boolean SYNCHRONIZED = false;
|
private static final boolean SYNCHRONIZED = false;
|
||||||
|
|
|
@ -30,7 +30,9 @@ log4j.logger.org.keycloak=info
|
||||||
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
|
# log4j.logger.org.keycloak.services.DefaultKeycloakSessionFactory=debug
|
||||||
# log4j.logger.org.keycloak.provider.ProviderManager=debug
|
# log4j.logger.org.keycloak.provider.ProviderManager=debug
|
||||||
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
|
# log4j.logger.org.keycloak.provider.FileSystemProviderLoaderFactory=debug
|
||||||
|
#log4j.logger.org.infinispan.transaction.impl.TransactionCoordinator=OFF
|
||||||
|
#log4j.logger.org.infinispan.transaction.tm.DummyTransaction=OFF
|
||||||
|
#log4j.logger.org.infinispan.container.entries.RepeatableReadEntry=OFF
|
||||||
# Broker logging
|
# Broker logging
|
||||||
keycloak.testsuite.logging.level=info
|
keycloak.testsuite.logging.level=info
|
||||||
log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
|
log4j.logger.org.keycloak.testsuite=${keycloak.testsuite.logging.level}
|
||||||
|
|
Loading…
Reference in a new issue