keycloak-aplcache

Changes

model/pom.xml 1(+1 -0)

pom.xml 6(+6 -0)

Details

diff --git a/connections/infinispan/pom.xml b/connections/infinispan/pom.xml
new file mode 100755
index 0000000..788a53d
--- /dev/null
+++ b/connections/infinispan/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0-Alpha1-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-connections-infinispan</artifactId>
+    <name>Keycloak Connections Infinispan</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.infinispan</groupId>
+            <artifactId>infinispan-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java
new file mode 100644
index 0000000..28c947b
--- /dev/null
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProvider.java
@@ -0,0 +1,26 @@
+package org.keycloak.connections.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.manager.EmbeddedCacheManager;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultInfinispanConnectionProvider implements InfinispanConnectionProvider {
+
+    private EmbeddedCacheManager cacheManager;
+
+    public DefaultInfinispanConnectionProvider(EmbeddedCacheManager cacheManager) {
+        this.cacheManager = cacheManager;
+    }
+
+    @Override
+    public <K, V> Cache<K, V> getCache(String name) {
+        return cacheManager.getCache(name);
+    }
+
+    @Override
+    public void close() {
+    }
+
+}
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
new file mode 100755
index 0000000..3d0aab8
--- /dev/null
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/DefaultInfinispanConnectionProviderFactory.java
@@ -0,0 +1,120 @@
+package org.keycloak.connections.infinispan;
+
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.Configuration;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.jboss.logging.Logger;
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+
+import javax.naming.InitialContext;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class DefaultInfinispanConnectionProviderFactory implements InfinispanConnectionProviderFactory {
+
+    protected static final Logger logger = Logger.getLogger(DefaultInfinispanConnectionProviderFactory.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 "default";
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+        this.config = config;
+    }
+
+    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();
+        if (config.getBoolean("transport", false)) {
+            gcb.transport().defaultTransport();
+        }
+        cacheManager = new DefaultCacheManager(gcb.build());
+        containerManaged = false;
+
+        logger.debug("Started embedded Infinispan cache container");
+
+        cacheManager.defineConfiguration("sessions", createConfiguration("sessions"));
+        cacheManager.defineConfiguration("realms", createConfiguration("realms"));
+    }
+
+    private Configuration createConfiguration(String cacheName) {
+        Config.Scope cacheConfig = config.scope("caches", cacheName);
+        ConfigurationBuilder cb = new ConfigurationBuilder();
+
+        String cacheMode = cacheConfig.get("cacheMode", "local");
+        boolean async = cacheConfig.getBoolean("async", false);
+
+        if (cacheMode.equalsIgnoreCase("replicated")) {
+            cb.clustering().cacheMode(async ? CacheMode.REPL_ASYNC : CacheMode.REPL_SYNC);
+        } else if (cacheMode.equalsIgnoreCase("distributed")) {
+            cb.clustering().cacheMode(async ? CacheMode.DIST_ASYNC : CacheMode.DIST_SYNC);
+
+            int owners = cacheConfig.getInt("owners", 2);
+            int segments = cacheConfig.getInt("segments", 60);
+
+            cb.clustering().hash().numOwners(owners).numSegments(segments);
+        } else if (cacheMode.equalsIgnoreCase("invalidation")) {
+            cb.clustering().cacheMode(async ? CacheMode.INVALIDATION_ASYNC : CacheMode.INVALIDATION_SYNC);
+        } else if (!cacheMode.equalsIgnoreCase("local")) {
+            throw new RuntimeException("Invalid cache mode " + cacheMode);
+        }
+
+        logger.debugv("Configured cache {0} with mode={1}, async={2}", cacheName, cacheMode, async);
+
+        return cb.build();
+    }
+
+}
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
new file mode 100644
index 0000000..098d00a
--- /dev/null
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProvider.java
@@ -0,0 +1,13 @@
+package org.keycloak.connections.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.provider.Provider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface InfinispanConnectionProvider extends Provider {
+
+    <K, V> Cache<K, V> getCache(String name);
+
+}
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProviderFactory.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProviderFactory.java
new file mode 100644
index 0000000..8d411b4
--- /dev/null
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionProviderFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.connections.infinispan;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface InfinispanConnectionProviderFactory extends ProviderFactory<InfinispanConnectionProvider> {
+}
diff --git a/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionSpi.java b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionSpi.java
new file mode 100644
index 0000000..71f915d
--- /dev/null
+++ b/connections/infinispan/src/main/java/org/keycloak/connections/infinispan/InfinispanConnectionSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.connections.infinispan;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InfinispanConnectionSpi implements Spi {
+
+    @Override
+    public String getName() {
+        return "connectionsInfinispan";
+    }
+
+    @Override
+    public Class<? extends Provider> getProviderClass() {
+        return InfinispanConnectionProvider.class;
+    }
+
+    @Override
+    public Class<? extends ProviderFactory> getProviderFactoryClass() {
+        return InfinispanConnectionProviderFactory.class;
+    }
+
+}
diff --git a/connections/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory b/connections/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
new file mode 100644
index 0000000..f14e990
--- /dev/null
+++ b/connections/infinispan/src/main/resources/META-INF/services/org.keycloak.connections.infinispan.InfinispanConnectionProviderFactory
@@ -0,0 +1 @@
+org.keycloak.connections.infinispan.DefaultInfinispanConnectionProviderFactory
\ No newline at end of file
diff --git a/connections/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/connections/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100644
index 0000000..6db36cc
--- /dev/null
+++ b/connections/infinispan/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1 @@
+org.keycloak.connections.infinispan.InfinispanConnectionSpi
\ No newline at end of file
diff --git a/connections/pom.xml b/connections/pom.xml
index 5ea6c39..a5b3bb2 100755
--- a/connections/pom.xml
+++ b/connections/pom.xml
@@ -14,6 +14,7 @@
 
     <modules>
         <module>jpa</module>
+        <module>infinispan</module>
         <module>mongo</module>
     </modules>
 
diff --git a/core/src/main/java/org/keycloak/Config.java b/core/src/main/java/org/keycloak/Config.java
index f26c851..1ce8fd0 100755
--- a/core/src/main/java/org/keycloak/Config.java
+++ b/core/src/main/java/org/keycloak/Config.java
@@ -121,6 +121,17 @@ public class Config {
             return v != null ? Boolean.parseBoolean(v) : defaultValue;
         }
 
+        @Override
+        public Scope scope(String... scope) {
+            StringBuilder sb = new StringBuilder();
+            sb.append(prefix + ".");
+            for (String s : scope) {
+                sb.append(s);
+                sb.append(".");
+            }
+            return new SystemPropertiesScope(sb.toString());
+        }
+
     }
 
     /**
@@ -146,5 +157,7 @@ public class Config {
 
         Boolean getBoolean(String key, Boolean defaultValue);
 
+        Scope scope(String... scope);
+
     }
 }
diff --git a/core/src/main/java/org/keycloak/util/Time.java b/core/src/main/java/org/keycloak/util/Time.java
index a7dc0fb..7da54f1 100644
--- a/core/src/main/java/org/keycloak/util/Time.java
+++ b/core/src/main/java/org/keycloak/util/Time.java
@@ -7,12 +7,18 @@ import java.util.Date;
  */
 public class Time {
 
+    private static int offset;
+
     public static int currentTime() {
-        return (int) (System.currentTimeMillis() / 1000);
+        return ((int) (System.currentTimeMillis() / 1000)) + offset;
     }
 
     public static Date toDate(int time) {
         return new Date(((long) time ) * 1000);
     }
 
+    public static void setOffset(int offset) {
+        Time.offset = offset;
+    }
+
 }
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index acf732f..0f7fed8 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -28,6 +28,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-connections-infinispan</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-model-jpa</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -48,6 +53,16 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-sessions-infinispan</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-invalidation-cache-infinispan</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-events-jpa</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
index d299a99..03a93d6 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionModel.java
@@ -9,32 +9,18 @@ public interface UserSessionModel {
 
     String getId();
 
-    void setId(String id);
-
     UserModel getUser();
 
-    void setUser(UserModel user);
-
     String getLoginUsername();
 
-    void setLoginUsername(String loginUsername);
-
     String getIpAddress();
 
-    void setIpAddress(String ipAddress);
-
     String getAuthMethod();
 
-    void setAuthMethod(String authMethod);
-
     boolean isRememberMe();
 
-    void setRememberMe(boolean rememberMe);
-
     int getStarted();
 
-    void setStarted(int started);
-
     int getLastSessionRefresh();
 
     void setLastSessionRefresh(int seconds);
diff --git a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
index 466717d..bf2c22d 100755
--- a/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -28,7 +28,6 @@ public interface UserSessionProvider extends Provider {
 
     UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username);
     UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username);
-    List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm);
 
     void onRealmRemoved(RealmModel realm);
     void onClientRemoved(RealmModel realm, ClientModel client);
diff --git a/model/invalidation-cache/infinispan/pom.xml b/model/invalidation-cache/infinispan/pom.xml
new file mode 100755
index 0000000..f03fd7a
--- /dev/null
+++ b/model/invalidation-cache/infinispan/pom.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0-Alpha1-SNAPSHOT</version>
+        <relativePath>../../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-invalidation-cache-infinispan</artifactId>
+    <name>Keycloak Invalidation Cache Infinispan</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-invalidation-cache-model</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-connections-infinispan</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.infinispan</groupId>
+            <artifactId>infinispan-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
new file mode 100755
index 0000000..67b1927
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheRealmProviderFactory.java
@@ -0,0 +1,42 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.cache.CacheRealmProvider;
+import org.keycloak.models.cache.CacheRealmProviderFactory;
+import org.keycloak.models.cache.DefaultCacheRealmProvider;
+import org.keycloak.models.cache.RealmCache;
+
+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 InfinispanCacheRealmProviderFactory implements CacheRealmProviderFactory {
+
+    protected final ConcurrentHashMap<String, String> realmLookup = new ConcurrentHashMap<String, String>();
+
+    @Override
+    public CacheRealmProvider create(KeycloakSession session) {
+        Cache<String, Object> cache = session.getProvider(InfinispanConnectionProvider.class).getCache("realms");
+        RealmCache realmCache = new InfinispanRealmCache(cache, realmLookup);
+        return new DefaultCacheRealmProvider(realmCache, session);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan";
+    }
+
+}
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
new file mode 100755
index 0000000..efbefca
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanCacheUserProviderFactory.java
@@ -0,0 +1,123 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.notifications.Listener;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
+import org.infinispan.notifications.cachelistener.annotation.CacheEntryRemoved;
+import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
+import org.infinispan.notifications.cachelistener.event.CacheEntryRemovedEvent;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.cache.CacheUserProvider;
+import org.keycloak.models.cache.CacheUserProviderFactory;
+import org.keycloak.models.cache.DefaultCacheUserProvider;
+import org.keycloak.models.cache.entities.CachedUser;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InfinispanCacheUserProviderFactory implements CacheUserProviderFactory {
+
+    protected InfinispanUserCache userCache;
+
+    protected final RealmLookup usernameLookup = new RealmLookup();
+
+    protected final RealmLookup emailLookup = new RealmLookup();
+
+    @Override
+    public CacheUserProvider create(KeycloakSession session) {
+        lazyInit(session);
+        return new DefaultCacheUserProvider(userCache, session);
+    }
+
+    private void lazyInit(KeycloakSession session) {
+        if (userCache == null) {
+            synchronized (this) {
+                if (userCache == null) {
+                    Cache<String, CachedUser> cache = session.getProvider(InfinispanConnectionProvider.class).getCache("users");
+                    cache.addListener(new CacheListener());
+                    userCache = new InfinispanUserCache(cache, usernameLookup, emailLookup);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan";
+    }
+
+    @Listener
+    private class CacheListener {
+
+        @CacheEntryCreated
+        public void userCreated(CacheEntryCreatedEvent<String, CachedUser> event) {
+            if (!event.isPre() && event.getValue() != null) {
+                CachedUser cachedUser = event.getValue();
+                String realm = cachedUser.getRealm();
+                usernameLookup.put(realm, cachedUser.getUsername(), cachedUser.getId());
+                if (cachedUser.getEmail() != null) {
+                    emailLookup.put(realm, cachedUser.getEmail(), cachedUser.getId());
+                }
+            }
+        }
+
+        @CacheEntryRemoved
+        public void userRemoved(CacheEntryRemovedEvent<String, CachedUser> event) {
+            if (event.isPre() && event.getValue() != null) {
+                CachedUser cachedUser = event.getValue();
+                String realm = cachedUser.getRealm();
+                usernameLookup.remove(realm, cachedUser.getUsername());
+                if (cachedUser.getEmail() != null) {
+                    emailLookup.remove(realm, cachedUser.getEmail());
+                }
+            }
+        }
+
+    }
+
+    static class RealmLookup {
+
+        protected final ConcurrentHashMap<String, ConcurrentHashMap<String, String>> lookup = new ConcurrentHashMap<String, ConcurrentHashMap<String, String>>();
+
+        public void put(String realm, String key, String value) {
+            ConcurrentHashMap<String, String> map = lookup.get(realm);
+            if(map == null) {
+                map = new ConcurrentHashMap<String, String>();
+                ConcurrentHashMap<String, String> p = lookup.putIfAbsent(realm, map);
+                if (p != null) {
+                    map = p;
+                }
+            }
+            map.put(key, value);
+        }
+
+        public String get(String realm, String key) {
+            ConcurrentHashMap<String, String> map = lookup.get(realm);
+            return map != null ? map.get(key) : null;
+        }
+
+        public void remove(String realm, String key) {
+            ConcurrentHashMap<String, String> map = lookup.get(realm);
+            if (map != null) {
+                map.remove(key);
+                if (map.isEmpty()) {
+                    lookup.remove(realm);
+                }
+            }
+        }
+
+    }
+
+}
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
new file mode 100755
index 0000000..a01a135
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanRealmCache.java
@@ -0,0 +1,166 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.RealmCache;
+import org.keycloak.models.cache.entities.CachedApplication;
+import org.keycloak.models.cache.entities.CachedOAuthClient;
+import org.keycloak.models.cache.entities.CachedRealm;
+import org.keycloak.models.cache.entities.CachedRole;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InfinispanRealmCache implements RealmCache {
+
+    protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class);
+
+    protected final Cache<String, Object> cache;
+    protected final ConcurrentHashMap<String, String> realmLookup;
+    protected volatile boolean enabled = true;
+
+    public InfinispanRealmCache(Cache<String, Object> cache, ConcurrentHashMap<String, String> realmLookup) {
+        this.cache = cache;
+        this.realmLookup = realmLookup;
+    }
+
+    @Override
+    public void clear() {
+        cache.clear();
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        clear();
+        this.enabled = enabled;
+        clear();
+    }
+
+    @Override
+    public CachedRealm getCachedRealm(String id) {
+        if (!enabled) return null;
+        return get(id, CachedRealm.class);
+    }
+
+    @Override
+    public void invalidateCachedRealm(CachedRealm realm) {
+        logger.tracev("Invalidating realm {0}", realm.getId());
+        cache.remove(realm.getId());
+        realmLookup.remove(realm.getName());
+    }
+
+    @Override
+    public void invalidateCachedRealmById(String id) {
+        CachedRealm cached = (CachedRealm) cache.remove(id);
+        if (cached != null) realmLookup.remove(cached.getName());
+    }
+
+    @Override
+    public void addCachedRealm(CachedRealm realm) {
+        if (!enabled) return;
+        logger.tracev("Adding realm {0}", realm.getId());
+        cache.put(realm.getId(), realm);
+        realmLookup.put(realm.getName(), realm.getId());
+    }
+
+    @Override
+    public CachedRealm getCachedRealmByName(String name) {
+        if (!enabled) return null;
+        String id = realmLookup.get(name);
+        return id != null ? getCachedRealm(id) : null;
+    }
+
+    @Override
+    public CachedApplication getApplication(String id) {
+        if (!enabled) return null;
+        return get(id, CachedApplication.class);
+    }
+
+    @Override
+    public void invalidateApplication(CachedApplication app) {
+        logger.tracev("Removing application {0}", app.getId());
+        cache.remove(app.getId());
+    }
+
+    @Override
+    public void addCachedApplication(CachedApplication app) {
+        if (!enabled) return;
+        logger.tracev("Adding application {0}", app.getId());
+        cache.put(app.getId(), app);
+    }
+
+    @Override
+    public void invalidateCachedApplicationById(String id) {
+        logger.tracev("Removing application {0}", id);
+        cache.remove(id);
+    }
+
+    @Override
+    public CachedOAuthClient getOAuthClient(String id) {
+        if (!enabled) return null;
+        return get(id, CachedOAuthClient.class);
+    }
+
+    @Override
+    public void invalidateOAuthClient(CachedOAuthClient client) {
+        logger.tracev("Removing oauth client {0}", client.getId());
+        cache.remove(client.getId());
+    }
+
+    @Override
+    public void addCachedOAuthClient(CachedOAuthClient client) {
+        if (!enabled) return;
+        logger.tracev("Adding oauth client {0}", client.getId());
+        cache.put(client.getId(), client);
+    }
+
+    @Override
+    public void invalidateCachedOAuthClientById(String id) {
+        logger.tracev("Removing oauth client {0}", id);
+        cache.remove(id);
+    }
+
+    @Override
+    public CachedRole getRole(String id) {
+        if (!enabled) return null;
+        return get(id, CachedRole.class);
+    }
+
+    @Override
+    public void invalidateRole(CachedRole role) {
+        logger.tracev("Removing role {0}", role.getId());
+        cache.remove(role);
+    }
+
+    @Override
+    public void invalidateRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        cache.remove(id);
+    }
+
+    @Override
+    public void addCachedRole(CachedRole role) {
+        if (!enabled) return;
+        logger.tracev("Adding role {0}", role.getId());
+        cache.put(role.getId(), role);
+    }
+
+    @Override
+    public void invalidateCachedRoleById(String id) {
+        logger.tracev("Removing role {0}", id);
+        cache.remove(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;
+    }
+
+}
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
new file mode 100755
index 0000000..7efa174
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/InfinispanUserCache.java
@@ -0,0 +1,95 @@
+package org.keycloak.models.cache.infinispan;
+
+import org.infinispan.Cache;
+import org.jboss.logging.Logger;
+import org.keycloak.models.cache.UserCache;
+import org.keycloak.models.cache.entities.CachedUser;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InfinispanUserCache implements UserCache {
+
+    protected static final Logger logger = Logger.getLogger(InfinispanRealmCache.class);
+
+    protected volatile boolean enabled = true;
+
+    protected final Cache<String, CachedUser> cache;
+
+    protected final InfinispanCacheUserProviderFactory.RealmLookup usernameLookup;
+
+    protected final InfinispanCacheUserProviderFactory.RealmLookup emailLookup;
+
+    public InfinispanUserCache(Cache<String, CachedUser> cache, InfinispanCacheUserProviderFactory.RealmLookup usernameLookup, InfinispanCacheUserProviderFactory.RealmLookup emailLookup) {
+        this.cache = cache;
+        this.usernameLookup = usernameLookup;
+        this.emailLookup = emailLookup;
+    }
+
+    @Override
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    @Override
+    public void setEnabled(boolean enabled) {
+        clear();
+        this.enabled = enabled;
+        clear();
+    }
+
+    @Override
+    public CachedUser getCachedUser(String realmId, String id) {
+        if (realmId == null || id == null) return null;
+        CachedUser user = cache.get(id);
+        return user != null && realmId.equals(user.getRealm()) ? user : null;
+    }
+
+    @Override
+    public void invalidateCachedUser(String realmId, CachedUser user) {
+        logger.tracev("Invalidating user {0}", user.getId());
+        cache.remove(user.getId());
+    }
+
+    @Override
+    public void invalidateCachedUserById(String realmId, String id) {
+        logger.tracev("Invalidating user {0}", id);
+        cache.remove(id);
+    }
+
+    @Override
+    public void addCachedUser(String realmId, CachedUser user) {
+        logger.tracev("Adding user {0}", user.getId());
+        cache.put(user.getId(), user);
+    }
+
+    @Override
+    public CachedUser getCachedUserByUsername(String realmId, String name) {
+        String id = usernameLookup.get(realmId, name);
+        return id != null ? getCachedUser(realmId, id) : null;
+    }
+
+    @Override
+    public CachedUser getCachedUserByEmail(String realmId, String email) {
+        String id = emailLookup.get(realmId, email);
+        return id != null ? getCachedUser(realmId, id) : null;
+    }
+
+    @Override
+    public void invalidateRealmUsers(String realmId) {
+        logger.tracev("Invalidating users for realm {0}", realmId);
+        for (Map.Entry<String, CachedUser> u : cache.entrySet()) {
+            if (u.getValue().getRealm().equals(realmId)) {
+                cache.remove(u.getKey());
+            }
+        }
+    }
+
+    @Override
+    public void clear() {
+        cache.clear();
+    }
+
+}
diff --git a/model/invalidation-cache/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory b/model/invalidation-cache/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
new file mode 100755
index 0000000..d354ddf
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheRealmProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.cache.infinispan.InfinispanCacheRealmProviderFactory
\ No newline at end of file
diff --git a/model/invalidation-cache/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory b/model/invalidation-cache/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory
new file mode 100755
index 0000000..499bb20
--- /dev/null
+++ b/model/invalidation-cache/infinispan/src/main/resources/META-INF/services/org.keycloak.models.cache.CacheUserProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.cache.infinispan.InfinispanCacheUserProviderFactory
\ No newline at end of file
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index 1e247a7..ca9a24c 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -18,6 +18,7 @@ import java.util.Set;
  */
 public class CachedUser {
     private String id;
+    private String realm;
     private String username;
     private String firstName;
     private String lastName;
@@ -34,6 +35,7 @@ public class CachedUser {
 
     public CachedUser(RealmModel realm, UserModel user) {
         this.id = user.getId();
+        this.realm = realm.getId();
         this.username = user.getUsername();
         this.firstName = user.getFirstName();
         this.lastName = user.getLastName();
@@ -54,6 +56,10 @@ public class CachedUser {
         return id;
     }
 
+    public String getRealm() {
+        return realm;
+    }
+
     public String getUsername() {
         return username;
     }
diff --git a/model/invalidation-cache/pom.xml b/model/invalidation-cache/pom.xml
index 09495fc..e24a15d 100755
--- a/model/invalidation-cache/pom.xml
+++ b/model/invalidation-cache/pom.xml
@@ -26,5 +26,6 @@
     </build>
     <modules>
         <module>model-adapters</module>
+        <module>infinispan</module>
     </modules>
 </project>

model/pom.xml 1(+1 -0)

diff --git a/model/pom.xml b/model/pom.xml
index ec81136..6296e15 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -32,5 +32,6 @@
         <module>sessions-jpa</module>
         <module>sessions-mem</module>
         <module>sessions-mongo</module>
+        <module>sessions-infinispan</module>
     </modules>
 </project>
diff --git a/model/sessions-infinispan/pom.xml b/model/sessions-infinispan/pom.xml
new file mode 100755
index 0000000..9462f7a
--- /dev/null
+++ b/model/sessions-infinispan/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0-Alpha1-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-model-sessions-infinispan</artifactId>
+    <name>Keycloak Model Sessions Infinispan</name>
+    <description/>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-model-api</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-connections-infinispan</artifactId>
+            <version>${project.version}</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.infinispan</groupId>
+            <artifactId>infinispan-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
new file mode 100644
index 0000000..2c6addc
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/ClientSessionAdapter.java
@@ -0,0 +1,151 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+import java.util.HashMap;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientSessionAdapter implements ClientSessionModel {
+
+    private KeycloakSession session;
+    private InfinispanUserSessionProvider provider;
+    private Cache<String, SessionEntity> cache;
+    private RealmModel realm;
+    private ClientSessionEntity entity;
+
+    public ClientSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, ClientSessionEntity entity) {
+        this.session = session;
+        this.provider = provider;
+        this.cache = cache;
+        this.realm = realm;
+        this.entity = entity;
+    }
+
+    @Override
+    public String getId() {
+        return entity.getId();
+    }
+
+    @Override
+    public RealmModel getRealm() {
+        return realm;
+    }
+
+    @Override
+    public ClientModel getClient() {
+        return realm.findClientById(entity.getClient());
+    }
+
+    @Override
+    public UserSessionModel getUserSession() {
+        return entity.getUserSession() != null ? provider.getUserSession(realm, entity.getUserSession()) : null;
+    }
+
+    @Override
+    public void setUserSession(UserSessionModel userSession) {
+        if (entity.getUserSession() != null) {
+            if (entity.getUserSession().equals(userSession.getId())) {
+                return;
+            } else {
+                provider.dettachSession(userSession, this);
+            }
+        } else {
+            provider.attachSession(userSession, this);
+        }
+
+        entity.setUserSession(userSession.getId());
+        update();
+    }
+
+    @Override
+    public String getRedirectUri() {
+        return entity.getRedirectUri();
+    }
+
+    @Override
+    public void setRedirectUri(String uri) {
+        entity.setRedirectUri(uri);
+        update();
+    }
+
+    @Override
+    public int getTimestamp() {
+        return entity.getTimestamp();
+    }
+
+    @Override
+    public void setTimestamp(int timestamp) {
+        entity.setTimestamp(timestamp);
+        update();
+    }
+
+    @Override
+    public Action getAction() {
+        return entity.getAction();
+    }
+
+    @Override
+    public void setAction(Action action) {
+        entity.setAction(action);
+        update();
+    }
+
+    @Override
+    public Set<String> getRoles() {
+        return entity.getRoles();
+    }
+
+    @Override
+    public void setRoles(Set<String> roles) {
+        entity.setRoles(roles);
+        update();
+    }
+
+    @Override
+    public String getAuthMethod() {
+        return entity.getAuthMethod();
+    }
+
+    @Override
+    public void setAuthMethod(String authMethod) {
+        entity.setAuthMethod(authMethod);
+        update();
+    }
+
+    @Override
+    public String getNote(String name) {
+        return entity.getNotes() != null ? entity.getNotes().get(name) : null;
+    }
+
+    @Override
+    public void setNote(String name, String value) {
+        if (entity.getNotes() == null) {
+            entity.setNotes(new HashMap<String, String>());
+        }
+        entity.getNotes().put(name, value);
+        update();
+    }
+
+    @Override
+    public void removeNote(String name) {
+        if (entity.getNotes() != null) {
+            entity.getNotes().remove(name);
+            update();
+        }
+    }
+
+    void update() {
+        provider.getTx().replace(cache, entity.getId(), entity);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
new file mode 100644
index 0000000..bfcf1c6
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/ClientSessionEntity.java
@@ -0,0 +1,102 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+import org.keycloak.models.ClientSessionModel;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientSessionEntity extends SessionEntity {
+
+    private String client;
+
+    private String userSession;
+
+    private String authMethod;
+
+    private String redirectUri;
+
+    private String state;
+
+    private int timestamp;
+
+    private ClientSessionModel.Action action;
+
+    private Set<String> roles;
+    private Map<String, String> notes;
+
+    public String getClient() {
+        return client;
+    }
+
+    public void setClient(String client) {
+        this.client = client;
+    }
+
+    public String getUserSession() {
+        return userSession;
+    }
+
+    public void setUserSession(String userSession) {
+        this.userSession = userSession;
+    }
+
+    public String getAuthMethod() {
+        return authMethod;
+    }
+
+    public void setAuthMethod(String authMethod) {
+        this.authMethod = authMethod;
+    }
+
+    public String getRedirectUri() {
+        return redirectUri;
+    }
+
+    public void setRedirectUri(String redirectUri) {
+        this.redirectUri = redirectUri;
+    }
+
+    public String getState() {
+        return state;
+    }
+
+    public void setState(String state) {
+        this.state = state;
+    }
+
+    public int getTimestamp() {
+        return timestamp;
+    }
+
+    public void setTimestamp(int timestamp) {
+        this.timestamp = timestamp;
+    }
+
+    public ClientSessionModel.Action getAction() {
+        return action;
+    }
+
+    public void setAction(ClientSessionModel.Action action) {
+        this.action = action;
+    }
+
+    public Set<String> getRoles() {
+        return roles;
+    }
+
+    public void setRoles(Set<String> roles) {
+        this.roles = roles;
+    }
+
+    public Map<String, String> getNotes() {
+        return notes;
+    }
+
+    public void setNotes(Map<String, String> notes) {
+        this.notes = notes;
+    }
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
new file mode 100644
index 0000000..09bfca6
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/LoginFailureEntity.java
@@ -0,0 +1,67 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginFailureEntity {
+
+    private String username;
+    private String realm;
+    private int failedLoginNotBefore;
+    private int numFailures;
+    private long lastFailure;
+    private String lastIPFailure;
+
+    public String getId() {
+        return realm + ":" + username;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(String realm) {
+        this.realm = realm;
+    }
+
+    public int getFailedLoginNotBefore() {
+        return failedLoginNotBefore;
+    }
+
+    public void setFailedLoginNotBefore(int failedLoginNotBefore) {
+        this.failedLoginNotBefore = failedLoginNotBefore;
+    }
+
+    public int getNumFailures() {
+        return numFailures;
+    }
+
+    public void setNumFailures(int numFailures) {
+        this.numFailures = numFailures;
+    }
+
+    public long getLastFailure() {
+        return lastFailure;
+    }
+
+    public void setLastFailure(long lastFailure) {
+        this.lastFailure = lastFailure;
+    }
+
+    public String getLastIPFailure() {
+        return lastIPFailure;
+    }
+
+    public void setLastIPFailure(String lastIPFailure) {
+        this.lastIPFailure = lastIPFailure;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java
new file mode 100644
index 0000000..5d6fc3c
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/SessionEntity.java
@@ -0,0 +1,29 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SessionEntity implements Serializable {
+
+    private String id;
+
+    private String realm;
+
+    public String getId() {
+        return id;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getRealm() {
+        return realm;
+    }
+
+    public void setRealm(String realm) {
+        this.realm = realm;
+    }
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
new file mode 100644
index 0000000..3300ae6
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/entities/UserSessionEntity.java
@@ -0,0 +1,89 @@
+package org.keycloak.models.sessions.infinispan.entities;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSessionEntity extends SessionEntity {
+
+    private String user;
+
+    private String loginUsername;
+
+    private String ipAddress;
+
+    private String authMethod;
+
+    private boolean rememberMe;
+
+    private int started;
+
+    private int lastSessionRefresh;
+
+    private Set<String> clientSessions;
+
+    public String getUser() {
+        return user;
+    }
+
+    public void setUser(String user) {
+        this.user = user;
+    }
+
+    public String getLoginUsername() {
+        return loginUsername;
+    }
+
+    public void setLoginUsername(String loginUsername) {
+        this.loginUsername = loginUsername;
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public void setIpAddress(String ipAddress) {
+        this.ipAddress = ipAddress;
+    }
+
+    public String getAuthMethod() {
+        return authMethod;
+    }
+
+    public void setAuthMethod(String authMethod) {
+        this.authMethod = authMethod;
+    }
+
+    public boolean isRememberMe() {
+        return rememberMe;
+    }
+
+    public void setRememberMe(boolean rememberMe) {
+        this.rememberMe = rememberMe;
+    }
+
+    public int getStarted() {
+        return started;
+    }
+
+    public void setStarted(int started) {
+        this.started = started;
+    }
+
+    public int getLastSessionRefresh() {
+        return lastSessionRefresh;
+    }
+
+    public void setLastSessionRefresh(int lastSessionRefresh) {
+        this.lastSessionRefresh = lastSessionRefresh;
+    }
+
+    public Set<String> getClientSessions() {
+        return clientSessions;
+    }
+
+    public void setClientSessions(Set<String> clientSessions) {
+        this.clientSessions = clientSessions;
+    }
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
new file mode 100644
index 0000000..033fe53
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -0,0 +1,439 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.distexec.mapreduce.MapReduceTask;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UserSessionProvider;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
+import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
+import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
+import org.keycloak.models.sessions.infinispan.mapreduce.LargestResultReducer;
+import org.keycloak.models.sessions.infinispan.mapreduce.SessionMapper;
+import org.keycloak.models.sessions.infinispan.mapreduce.UserSessionMapper;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.util.Time;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InfinispanUserSessionProvider implements UserSessionProvider {
+
+    private final KeycloakSession session;
+    private final Cache<String, SessionEntity> sessionCache;
+    private final Cache<String, LoginFailureEntity> loginFailureCache;
+    private final InfinispanKeycloakTransaction tx;
+
+    public InfinispanUserSessionProvider(KeycloakSession session, Cache<String, SessionEntity> sessionCache, Cache<String, LoginFailureEntity> loginFailureCache) {
+        this.session = session;
+        this.sessionCache = sessionCache;
+        this.loginFailureCache = loginFailureCache;
+        this.tx = new InfinispanKeycloakTransaction();
+
+        session.getTransaction().enlistAfterCompletion(tx);
+    }
+
+    @Override
+    public ClientSessionModel createClientSession(RealmModel realm, ClientModel client) {
+        String id = KeycloakModelUtils.generateId();
+
+        ClientSessionEntity entity = new ClientSessionEntity();
+        entity.setId(id);
+        entity.setRealm(realm.getId());
+        entity.setTimestamp(Time.currentTime());
+        entity.setClient(client.getId());
+
+        tx.put(sessionCache, id, entity);
+
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
+        String id = KeycloakModelUtils.generateId();
+
+        UserSessionEntity entity = new UserSessionEntity();
+        entity.setId(id);
+        entity.setRealm(realm.getId());
+        entity.setUser(user.getId());
+        entity.setLoginUsername(loginUsername);
+        entity.setIpAddress(ipAddress);
+        entity.setAuthMethod(authMethod);
+        entity.setRememberMe(rememberMe);
+
+        int currentTime = Time.currentTime();
+
+        entity.setStarted(currentTime);
+        entity.setLastSessionRefresh(currentTime);
+
+        tx.put(sessionCache, id, entity);
+
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public ClientSessionModel getClientSession(RealmModel realm, String id) {
+        ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public ClientSessionModel getClientSession(String id) {
+        ClientSessionEntity entity = (ClientSessionEntity) sessionCache.get(id);
+        if (entity != null) {
+            RealmModel realm = session.realms().getRealm(entity.getRealm());
+            return wrap(realm, entity);
+        }
+        return null;
+    }
+
+    @Override
+    public UserSessionModel getUserSession(RealmModel realm, String id) {
+        UserSessionEntity entity = (UserSessionEntity) sessionCache.get(id);
+        return wrap(realm, entity);
+    }
+
+    @Override
+    public List<UserSessionModel> getUserSessions(RealmModel realm, UserModel user) {
+        Map<String, UserSessionEntity> sessions = new MapReduceTask(sessionCache)
+                .mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()))
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        return wrapUserSessions(realm, sessions.values());
+    }
+
+    @Override
+    public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
+        return getUserSessions(realm, client, -1, -1);
+    }
+
+    @Override
+    public List<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client, int firstResult, int maxResults) {
+        Map<String, Integer> map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
+                .reducedWith(new LargestResultReducer())
+                .execute();
+
+        List<Map.Entry<String, Integer>> sessionTimestamps = new LinkedList<Map.Entry<String, Integer>>(map.entrySet());
+
+        Collections.sort(sessionTimestamps, new Comparator<Map.Entry<String, Integer>>() {
+            @Override
+            public int compare(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
+                return e1.getValue().compareTo(e2.getValue());
+            }
+        });
+
+        if (firstResult != -1 || maxResults == -1) {
+            if (firstResult == -1) {
+                firstResult = 0;
+            }
+
+            if (maxResults == -1) {
+                maxResults = Integer.MAX_VALUE;
+            }
+
+            if (firstResult > sessionTimestamps.size()) {
+                return Collections.emptyList();
+            }
+
+            int toIndex = (firstResult + maxResults) < sessionTimestamps.size() ? firstResult + maxResults : sessionTimestamps.size();
+            sessionTimestamps = sessionTimestamps.subList(firstResult, toIndex);
+        }
+
+        List<UserSessionModel> userSessions = new LinkedList<UserSessionModel>();
+        for (Map.Entry<String, Integer> e : sessionTimestamps) {
+            UserSessionEntity userSessionEntity = (UserSessionEntity) sessionCache.get(e.getKey());
+            if (userSessionEntity != null) {
+                userSessions.add(wrap(realm, userSessionEntity));
+            }
+        }
+
+        return userSessions;
+    }
+
+    @Override
+    public int getActiveUserSessions(RealmModel realm, ClientModel client) {
+        Map map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitUserSessionAndTimestamp())
+                .reducedWith(new LargestResultReducer()).execute();
+
+        return map.size();
+    }
+
+    @Override
+    public void removeUserSession(RealmModel realm, UserSessionModel session) {
+        removeUserSession(realm, session.getId());
+    }
+
+    @Override
+    public void removeUserSessions(RealmModel realm, UserModel user) {
+        Map<String, String> sessions = new MapReduceTask(sessionCache)
+                .mappedWith(UserSessionMapper.create(realm.getId()).user(user.getId()).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : sessions.keySet()) {
+            removeUserSession(realm, id);
+        }
+    }
+
+    @Override
+    public void removeExpiredUserSessions(RealmModel realm) {
+        int expired = Time.currentTime() - realm.getSsoSessionMaxLifespan();
+        int expiredRefresh = Time.currentTime() - realm.getSsoSessionIdleTimeout();
+
+        Map<String, String> map = new MapReduceTask(sessionCache)
+                .mappedWith(UserSessionMapper.create(realm.getId()).expired(expired, expiredRefresh).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : map.keySet()) {
+            removeUserSession(realm, id);
+        }
+    }
+
+    @Override
+    public void removeUserSessions(RealmModel realm) {
+        Map<String, String> ids = new MapReduceTask(sessionCache)
+                .mappedWith(SessionMapper.create(realm.getId()).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : ids.keySet()) {
+            sessionCache.remove(id);
+        }
+    }
+
+    @Override
+    public UsernameLoginFailureModel getUserLoginFailure(RealmModel realm, String username) {
+        return wrap(loginFailureCache.get(realm.getId() + ":" + username));
+    }
+
+    @Override
+    public UsernameLoginFailureModel addUserLoginFailure(RealmModel realm, String username) {
+        LoginFailureEntity entity = new LoginFailureEntity();
+        entity.setRealm(realm.getId());
+        entity.setUsername(username);
+        tx.put(loginFailureCache, entity.getId(), entity);
+        return wrap(entity);
+    }
+
+    @Override
+    public void onRealmRemoved(RealmModel realm) {
+        removeUserSessions(realm);
+    }
+
+    @Override
+    public void onClientRemoved(RealmModel realm, ClientModel client) {
+        Map<String, String> map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientSessionMapper.create(realm.getId()).client(client.getId()).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : map.keySet()) {
+            tx.remove(sessionCache, id);
+        }
+    }
+
+    @Override
+    public void onUserRemoved(RealmModel realm, UserModel user) {
+        removeUserSessions(realm, user);
+    }
+
+    @Override
+    public void close() {
+    }
+
+    void attachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
+        UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
+        String clientSessionId = clientSession.getId();
+        if (entity.getClientSessions() == null) {
+            entity.setClientSessions(new HashSet<String>());
+        }
+        if (!entity.getClientSessions().contains(clientSessionId)) {
+            entity.getClientSessions().add(clientSessionId);
+            tx.replace(sessionCache, entity.getId(), entity);
+        }
+    }
+
+    void dettachSession(UserSessionModel userSession, ClientSessionModel clientSession) {
+        UserSessionEntity entity = ((UserSessionAdapter) userSession).getEntity();
+        String clientSessionId = clientSession.getId();
+        if (entity.getClientSessions() != null && entity.getClientSessions().contains(clientSessionId)) {
+            entity.getClientSessions().remove(clientSessionId);
+            if (entity.getClientSessions().isEmpty()) {
+                entity.setClientSessions(null);
+            }
+            tx.replace(sessionCache, entity.getId(), entity);
+        }
+    }
+
+    void removeUserSession(RealmModel realm, String userSessionId) {
+        tx.remove(sessionCache, userSessionId);
+
+        Map<String, String> map = new MapReduceTask(sessionCache)
+                .mappedWith(ClientSessionMapper.create(realm.getId()).userSession(userSessionId).emitKey())
+                .reducedWith(new FirstResultReducer())
+                .execute();
+
+        for (String id : map.keySet()) {
+            tx.remove(sessionCache, id);
+        }
+    }
+
+    InfinispanKeycloakTransaction getTx() {
+        return tx;
+    }
+
+    UserSessionModel wrap(RealmModel realm, UserSessionEntity entity) {
+        return entity != null ? new UserSessionAdapter(session, this, sessionCache, realm, entity) : null;
+    }
+
+    List<UserSessionModel> wrapUserSessions(RealmModel realm, Collection<UserSessionEntity> entities) {
+        List<UserSessionModel> models = new LinkedList<UserSessionModel>();
+        for (UserSessionEntity e : entities) {
+            models.add(wrap(realm, e));
+        }
+        return models;
+    }
+
+    ClientSessionModel wrap(RealmModel realm, ClientSessionEntity entity) {
+        return entity != null ? new ClientSessionAdapter(session, this, sessionCache, realm, entity) : null;
+    }
+
+
+    UsernameLoginFailureModel wrap(LoginFailureEntity entity) {
+        return entity != null ? new UsernameLoginFailureAdapter(this, loginFailureCache, entity) : null;
+    }
+
+    List<ClientSessionModel> wrapClientSessions(RealmModel realm, Collection<ClientSessionEntity> entities) {
+        List<ClientSessionModel> models = new LinkedList<ClientSessionModel>();
+        for (ClientSessionEntity e : entities) {
+            models.add(wrap(realm, e));
+        }
+        return models;
+    }
+
+    class InfinispanKeycloakTransaction implements KeycloakTransaction {
+
+        private boolean active;
+        private boolean rollback;
+        private Map<String, CacheTask> tasks = new HashMap<String, CacheTask>();
+
+        @Override
+        public void begin() {
+            active = true;
+        }
+
+        @Override
+        public void commit() {
+            if (rollback) {
+                throw new RuntimeException("Rollback only!");
+            }
+
+            for (CacheTask task : tasks.values()) {
+                task.execute();
+            }
+        }
+
+        @Override
+        public void rollback() {
+            tasks.clear();
+        }
+
+        @Override
+        public void setRollbackOnly() {
+            rollback = true;
+        }
+
+        @Override
+        public boolean getRollbackOnly() {
+            return rollback;
+        }
+
+        @Override
+        public boolean isActive() {
+            return active;
+        }
+
+        public void put(Cache cache, String key, Object value) {
+            if (tasks.containsKey(key)) {
+                throw new IllegalStateException("Can't add session: task in progress for session");
+            } else {
+                tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
+            }
+        }
+
+        public void replace(Cache cache, String key, Object value) {
+            CacheTask current = tasks.get(key);
+            if (current != null) {
+                switch (current.operation) {
+                    case ADD:
+                    case REPLACE:
+                        current.value = value;
+                        return;
+                    case REMOVE:
+                        throw new IllegalStateException("Can't remove session: task in progress for session");
+                }
+            } else {
+                tasks.put(key, new CacheTask(cache, CacheOperation.ADD, key, value));
+            }
+        }
+
+        public void remove(Cache cache, String key) {
+            tasks.put(key, new CacheTask(cache, CacheOperation.REMOVE, key, null));
+        }
+
+        public class CacheTask {
+            private Cache cache;
+            private CacheOperation operation;
+            private String key;
+            private Object value;
+
+            public CacheTask(Cache cache, CacheOperation operation, String key, Object value) {
+                this.cache = cache;
+                this.operation = operation;
+                this.key = key;
+                this.value = value;
+            }
+
+            public void execute() {
+                switch (operation) {
+                    case ADD:
+                        cache.put(key, value);
+                        break;
+                    case REMOVE:
+                        cache.remove(key);
+                        break;
+                    case REPLACE:
+                        cache.replace(key, value);
+                }
+            }
+        }
+
+    }
+
+    public enum CacheOperation {
+        ADD, REMOVE, REPLACE
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
new file mode 100644
index 0000000..40287af
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -0,0 +1,49 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.configuration.cache.CacheMode;
+import org.infinispan.configuration.cache.ConfigurationBuilder;
+import org.infinispan.configuration.global.GlobalConfigurationBuilder;
+import org.infinispan.manager.DefaultCacheManager;
+import org.infinispan.manager.EmbeddedCacheManager;
+import org.keycloak.Config;
+import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.UserSessionProvider;
+import org.keycloak.models.UserSessionProviderFactory;
+import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+import javax.naming.InitialContext;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
+
+    private static final String SESSION_CACHE_NAME = "sessions";
+    private static final String LOGIN_FAILURE_CACHE_NAME = "loginFailures";
+
+    @Override
+    public UserSessionProvider create(KeycloakSession session) {
+        InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
+        Cache<String, SessionEntity> cache = connections.getCache(SESSION_CACHE_NAME);
+        Cache<String, LoginFailureEntity> loginFailures = connections.getCache(LOGIN_FAILURE_CACHE_NAME);
+        return new InfinispanUserSessionProvider(session, cache, loginFailures);
+    }
+
+    @Override
+    public void init(Config.Scope config) {
+    }
+
+    @Override
+    public void close() {
+    }
+
+    @Override
+    public String getId() {
+        return "infinispan";
+    }
+
+}
+
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
new file mode 100644
index 0000000..7329d3f
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/ClientSessionMapper.java
@@ -0,0 +1,88 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ClientSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+    public ClientSessionMapper(String realm) {
+        this.realm = realm;
+    }
+
+    private enum EmitValue {
+        KEY, ENTITY, USER_SESSION_AND_TIMESTAMP
+    }
+
+    private String realm;
+
+    private EmitValue emit = EmitValue.ENTITY;
+
+    private String client;
+
+    private String userSession;
+
+    public static ClientSessionMapper create(String realm) {
+        return new ClientSessionMapper(realm);
+    }
+
+    public ClientSessionMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
+    public ClientSessionMapper emitUserSessionAndTimestamp() {
+        emit = EmitValue.USER_SESSION_AND_TIMESTAMP;
+        return this;
+    }
+
+    public ClientSessionMapper client(String client) {
+        this.client = client;
+        return this;
+    }
+
+    public ClientSessionMapper userSession(String userSession) {
+        this.userSession = userSession;
+        return this;
+    }
+
+    @Override
+    public void map(String key, SessionEntity e, Collector collector) {
+        if (!realm.equals(e.getRealm())) {
+            return;
+        }
+
+        if (!(e instanceof ClientSessionEntity)) {
+            return;
+        }
+
+        ClientSessionEntity entity = (ClientSessionEntity) e;
+
+        if (client != null && !entity.getClient().equals(client)) {
+            return;
+        }
+
+        if (userSession != null && !userSession.equals(entity.getUserSession())) {
+            return;
+        }
+
+        switch (emit) {
+            case KEY:
+                collector.emit(key, key);
+                break;
+            case ENTITY:
+                collector.emit(key, entity);
+                break;
+            case USER_SESSION_AND_TIMESTAMP:
+                collector.emit(entity.getUserSession(), entity.getTimestamp());
+                break;
+        }
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/FirstResultReducer.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/FirstResultReducer.java
new file mode 100644
index 0000000..622d98f
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/FirstResultReducer.java
@@ -0,0 +1,18 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Reducer;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FirstResultReducer implements Reducer<Object, Object>, Serializable {
+
+    @Override
+    public Object reduce(Object reducedKey, Iterator<Object> itr) {
+        return itr.next();
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/LargestResultReducer.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/LargestResultReducer.java
new file mode 100644
index 0000000..33b0d07
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/LargestResultReducer.java
@@ -0,0 +1,24 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Reducer;
+
+import java.util.Iterator;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LargestResultReducer implements Reducer<String, Integer> {
+
+    @Override
+    public Integer reduce(String reducedKey, Iterator<Integer> itr) {
+        Integer largest = itr.next();
+        while (itr.hasNext()) {
+            Integer next = itr.next();
+            if (next > largest) {
+                largest = next;
+            }
+        }
+        return largest;
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/SessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/SessionMapper.java
new file mode 100644
index 0000000..747d094
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/SessionMapper.java
@@ -0,0 +1,52 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+    public SessionMapper(String realm) {
+        this.realm = realm;
+    }
+
+    private enum EmitValue {
+        KEY, ENTITY
+    }
+
+    private String realm;
+
+    private EmitValue emit = EmitValue.ENTITY;
+
+    public static SessionMapper create(String realm) {
+        return new SessionMapper(realm);
+    }
+
+    public SessionMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
+    @Override
+    public void map(String key, SessionEntity e, Collector collector) {
+        if (!realm.equals(e.getRealm())) {
+            return;
+        }
+
+        switch (emit) {
+            case KEY:
+                collector.emit(key, key);
+                break;
+            case ENTITY:
+                collector.emit(key, e);
+                break;
+        }
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
new file mode 100644
index 0000000..3c28284
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/mapreduce/UserSessionMapper.java
@@ -0,0 +1,84 @@
+package org.keycloak.models.sessions.infinispan.mapreduce;
+
+import org.infinispan.distexec.mapreduce.Collector;
+import org.infinispan.distexec.mapreduce.Mapper;
+import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSessionMapper implements Mapper<String, SessionEntity, String, Object>, Serializable {
+
+    public UserSessionMapper(String realm) {
+        this.realm = realm;
+    }
+
+    private enum EmitValue {
+        KEY, ENTITY
+    }
+
+    private String realm;
+
+    private EmitValue emit = EmitValue.ENTITY;
+
+    private String user;
+
+    private Long expired;
+
+    private Long expiredRefresh;
+
+    public static UserSessionMapper create(String realm) {
+        return new UserSessionMapper(realm);
+    }
+
+    public UserSessionMapper emitKey() {
+        emit = EmitValue.KEY;
+        return this;
+    }
+
+    public UserSessionMapper user(String user) {
+        this.user = user;
+        return this;
+    }
+
+    public UserSessionMapper expired(long expired, long expiredRefresh) {
+        this.expired = expired;
+        this.expiredRefresh = expiredRefresh;
+        return this;
+    }
+
+    @Override
+    public void map(String key, SessionEntity e, Collector collector) {
+        if (!(e instanceof UserSessionEntity)) {
+            return;
+        }
+
+        UserSessionEntity entity = (UserSessionEntity) e;
+
+        if (!realm.equals(entity.getRealm())) {
+            return;
+        }
+
+        if (user != null && !entity.getUser().equals(user)) {
+            return;
+        }
+
+        if (expired != null && expiredRefresh != null && entity.getStarted() > expired && entity.getLastSessionRefresh() > expiredRefresh) {
+            return;
+        }
+
+        switch (emit) {
+            case KEY:
+                collector.emit(key, key);
+                break;
+            case ENTITY:
+                collector.emit(key, entity);
+                break;
+        }
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java
new file mode 100755
index 0000000..2de7089
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UsernameLoginFailureAdapter.java
@@ -0,0 +1,81 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.infinispan.entities.LoginFailureEntity;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
+
+    private InfinispanUserSessionProvider provider;
+    private Cache<String, LoginFailureEntity> cache;
+    private LoginFailureEntity entity;
+
+    public UsernameLoginFailureAdapter(InfinispanUserSessionProvider provider, Cache<String, LoginFailureEntity> cache, LoginFailureEntity entity) {
+        this.provider = provider;
+        this.cache = cache;
+        this.entity = entity;
+    }
+
+    @Override
+    public String getUsername() {
+        return entity.getUsername();
+    }
+
+    @Override
+    public int getFailedLoginNotBefore() {
+        return entity.getFailedLoginNotBefore();
+    }
+
+    @Override
+    public void setFailedLoginNotBefore(int notBefore) {
+        entity.setFailedLoginNotBefore(notBefore);
+        update();
+    }
+
+    @Override
+    public int getNumFailures() {
+        return entity.getNumFailures();
+    }
+
+    @Override
+    public void incrementFailures() {
+        entity.setNumFailures(getNumFailures() + 1);
+        update();
+    }
+
+    @Override
+    public void clearFailures() {
+        entity.setNumFailures(0);
+        update();
+    }
+
+    @Override
+    public long getLastFailure() {
+        return entity.getLastFailure();
+    }
+
+    @Override
+    public void setLastFailure(long lastFailure) {
+        entity.setLastFailure(lastFailure);
+        update();
+    }
+
+    @Override
+    public String getLastIPFailure() {
+        return entity.getLastIPFailure();
+    }
+
+    @Override
+    public void setLastIPFailure(String ip) {
+        entity.setLastIPFailure(ip);
+        update();
+    }
+
+    void update() {
+        provider.getTx().replace(cache, entity.getId(), entity);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
new file mode 100755
index 0000000..7b3000e
--- /dev/null
+++ b/model/sessions-infinispan/src/main/java/org/keycloak/models/sessions/infinispan/UserSessionAdapter.java
@@ -0,0 +1,122 @@
+package org.keycloak.models.sessions.infinispan;
+
+import org.infinispan.Cache;
+import org.infinispan.distexec.mapreduce.MapReduceTask;
+import org.keycloak.models.ClientSessionModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.infinispan.entities.ClientSessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
+import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;
+import org.keycloak.models.sessions.infinispan.mapreduce.ClientSessionMapper;
+import org.keycloak.models.sessions.infinispan.mapreduce.FirstResultReducer;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSessionAdapter implements UserSessionModel {
+
+    private final KeycloakSession session;
+
+    private final InfinispanUserSessionProvider provider;
+
+    private final Cache<String, SessionEntity> cache;
+
+    private final RealmModel realm;
+
+    private final UserSessionEntity entity;
+
+    public UserSessionAdapter(KeycloakSession session, InfinispanUserSessionProvider provider, Cache<String, SessionEntity> cache, RealmModel realm, UserSessionEntity entity) {
+        this.session = session;
+        this.provider = provider;
+        this.cache = cache;
+        this.realm = realm;
+        this.entity = entity;
+    }
+
+    public String getId() {
+        return entity.getId();
+    }
+
+    public UserModel getUser() {
+        return session.users().getUserById(entity.getUser(), realm);
+    }
+
+    @Override
+    public String getLoginUsername() {
+        return entity.getLoginUsername();
+    }
+
+    public String getIpAddress() {
+        return entity.getIpAddress();
+    }
+
+    @Override
+    public String getAuthMethod() {
+        return entity.getAuthMethod();
+    }
+
+    @Override
+    public boolean isRememberMe() {
+        return entity.isRememberMe();
+    }
+
+    public int getStarted() {
+        return entity.getStarted();
+    }
+
+    public int getLastSessionRefresh() {
+        return entity.getLastSessionRefresh();
+    }
+
+    public void setLastSessionRefresh(int lastSessionRefresh) {
+        entity.setLastSessionRefresh(lastSessionRefresh);
+        update();
+    }
+
+    @Override
+    public List<ClientSessionModel> getClientSessions() {
+        if (entity.getClientSessions() != null) {
+            List<ClientSessionEntity> clientSessions = new LinkedList<ClientSessionEntity>();
+            for (String c : entity.getClientSessions()) {
+                ClientSessionEntity clientSession = (ClientSessionEntity) cache.get(c);
+                if (clientSession != null) {
+                    clientSessions.add(clientSession);
+                }
+            }
+            return provider.wrapClientSessions(realm, clientSessions);
+        } else {
+            return Collections.emptyList();
+        }
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !(o instanceof UserSessionModel)) return false;
+
+        UserSessionModel that = (UserSessionModel) o;
+        return that.getId().equals(getId());
+    }
+
+    @Override
+    public int hashCode() {
+        return getId().hashCode();
+    }
+
+    UserSessionEntity getEntity() {
+        return entity;
+    }
+
+    void update() {
+        provider.getTx().replace(cache, entity.getId(), entity);
+    }
+
+}
diff --git a/model/sessions-infinispan/src/main/resources/META-INF/services/org.keycloak.models.UserSessionProviderFactory b/model/sessions-infinispan/src/main/resources/META-INF/services/org.keycloak.models.UserSessionProviderFactory
new file mode 100644
index 0000000..2be2c79
--- /dev/null
+++ b/model/sessions-infinispan/src/main/resources/META-INF/services/org.keycloak.models.UserSessionProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.sessions.infinispan.InfinispanUserSessionProviderFactory
\ No newline at end of file
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
index 335a48f..a3e7297 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaUserSessionProvider.java
@@ -87,17 +87,6 @@ public class JpaUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
-        TypedQuery<UsernameLoginFailureEntity> query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class);
-        List<UsernameLoginFailureEntity> entities = query.getResultList();
-        List<UsernameLoginFailureModel> models = new ArrayList<UsernameLoginFailureModel>();
-        for (UsernameLoginFailureEntity entity : entities) {
-            models.add(new UsernameLoginFailureAdapter(entity));
-        }
-        return models;
-    }
-
-    @Override
     public UserSessionModel createUserSession(RealmModel realm, UserModel user, String loginUsername, String ipAddress, String authMethod, boolean rememberMe) {
         UserSessionEntity entity = new UserSessionEntity();
         entity.setId(KeycloakModelUtils.generateId());
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
index 3261b21..2110453 100755
--- a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
@@ -39,71 +39,36 @@ public class UserSessionAdapter implements UserSessionModel {
     }
 
     @Override
-    public void setId(String id) {
-        entity.setId(id);
-    }
-
-    @Override
     public UserModel getUser() {
         return session.users().getUserById(entity.getUserId(), realm);
     }
 
     @Override
-    public void setUser(UserModel user) {
-        entity.setUserId(user.getId());
-    }
-
-    @Override
     public String getLoginUsername() {
         return entity.getLoginUsername();
     }
 
     @Override
-    public void setLoginUsername(String loginUsername) {
-        entity.setLoginUsername(loginUsername);
-    }
-
-    @Override
     public String getIpAddress() {
         return entity.getIpAddress();
     }
 
     @Override
-    public void setIpAddress(String ipAddress) {
-        entity.setIpAddress(ipAddress);
-    }
-
-    @Override
     public String getAuthMethod() {
         return entity.getAuthMethod();
     }
 
     @Override
-    public void setAuthMethod(String authMethod) {
-        entity.setAuthMethod(authMethod);
-    }
-
-    @Override
     public boolean isRememberMe() {
         return entity.isRememberMe();
     }
 
     @Override
-    public void setRememberMe(boolean rememberMe) {
-        entity.setRememberMe(rememberMe);
-    }
-
-    @Override
     public int getStarted() {
         return entity.getStarted();
     }
 
     @Override
-    public void setStarted(int started) {
-        entity.setStarted(started);
-    }
-
-    @Override
     public int getLastSessionRefresh() {
         return entity.getLastSessionRefresh();
     }
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
index 7ef7e77..27f9757 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemUserSessionProvider.java
@@ -240,17 +240,6 @@ public class MemUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
-        List<UsernameLoginFailureModel> failures = new LinkedList<UsernameLoginFailureModel>();
-        for (UsernameLoginFailureEntity entity : loginFailures.values()) {
-            if (entity.getRealm().equals(realm.getId())) {
-                failures.add(new UsernameLoginFailureAdapter(entity));
-            }
-        }
-        return failures;
-    }
-
-    @Override
     public void onRealmRemoved(RealmModel realm) {
         removeUserSessions(realm);
 
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
index 3b8e867..e2da268 100755
--- a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/UserSessionAdapter.java
@@ -55,47 +55,24 @@ public class UserSessionAdapter implements UserSessionModel {
         return entity.getLoginUsername();
     }
 
-    @Override
-    public void setLoginUsername(String loginUsername) {
-        entity.setLoginUsername(loginUsername);
-    }
-
     public String getIpAddress() {
         return entity.getIpAddress();
     }
 
-    public void setIpAddress(String ipAddress) {
-        entity.setIpAddress(ipAddress);
-    }
-
     @Override
     public String getAuthMethod() {
         return entity.getAuthMethod();
     }
 
     @Override
-    public void setAuthMethod(String authMethod) {
-        entity.setAuthMethod(authMethod);
-    }
-
-    @Override
     public boolean isRememberMe() {
         return entity.isRememberMe();
     }
 
-    @Override
-    public void setRememberMe(boolean rememberMe) {
-        entity.setRememberMe(rememberMe);
-    }
-
     public int getStarted() {
         return entity.getStarted();
     }
 
-    public void setStarted(int started) {
-        entity.setStarted(started);
-    }
-
     public int getLastSessionRefresh() {
         return entity.getLastSessionRefresh();
     }
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
index a508c34..9c5f6fd 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/MongoUserSessionProvider.java
@@ -222,22 +222,6 @@ public class MongoUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
-    public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
-        DBObject query = new QueryBuilder()
-                .and("realmId").is(realm.getId())
-                .get();
-        List<MongoUsernameLoginFailureEntity> failures = mongoStore.loadEntities(MongoUsernameLoginFailureEntity.class, query, invocationContext);
-
-        List<UsernameLoginFailureModel> result = new LinkedList<UsernameLoginFailureModel>();
-        if (failures == null) return result;
-        for (MongoUsernameLoginFailureEntity failure : failures) {
-            result.add(new UsernameLoginFailureAdapter(invocationContext, failure));
-        }
-
-        return result;
-    }
-
-    @Override
     public void onRealmRemoved(RealmModel realm) {
         removeUserSessions(realm);
     }
diff --git a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
index e47833d..70ba85a 100755
--- a/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
+++ b/model/sessions-mongo/src/main/java/org/keycloak/models/sessions/mongo/UserSessionAdapter.java
@@ -18,8 +18,6 @@ import java.util.List;
  */
 public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEntity> implements UserSessionModel {
 
-    private static final Logger logger = Logger.getLogger(UserSessionAdapter.class);
-
     private final MongoUserSessionProvider provider;
     private MongoUserSessionEntity entity;
     private RealmModel realm;
@@ -46,78 +44,36 @@ public class UserSessionAdapter extends AbstractMongoAdapter<MongoUserSessionEnt
     }
 
     @Override
-    public void setId(String id) {
-        entity.setId(id);
-        updateMongoEntity();
-    }
-
-    @Override
     public UserModel getUser() {
         return keycloakSession.users().getUserById(entity.getUser(), realm);
     }
 
     @Override
-    public void setUser(UserModel user) {
-        entity.setUser(user.getId());
-        updateMongoEntity();
-    }
-
-    @Override
     public String getLoginUsername() {
         return entity.getLoginUsername();
     }
 
     @Override
-    public void setLoginUsername(String loginUsername) {
-        entity.setLoginUsername(loginUsername);
-        updateMongoEntity();
-    }
-
-    @Override
     public String getIpAddress() {
         return entity.getIpAddress();
     }
 
     @Override
-    public void setIpAddress(String ipAddress) {
-        entity.setIpAddress(ipAddress);
-        updateMongoEntity();
-    }
-
-    @Override
     public String getAuthMethod() {
         return entity.getAuthMethod();
     }
 
     @Override
-    public void setAuthMethod(String authMethod) {
-        entity.setAuthMethod(authMethod);
-        updateMongoEntity();
-    }
-
-    @Override
     public boolean isRememberMe() {
         return entity.isRememberMe();
     }
 
     @Override
-    public void setRememberMe(boolean rememberMe) {
-        entity.setRememberMe(rememberMe);
-        updateMongoEntity();
-    }
-
-    @Override
     public int getStarted() {
         return entity.getStarted();
     }
 
     @Override
-    public void setStarted(int started) {
-        entity.setStarted(started);
-        updateMongoEntity();
-    }
-
-    @Override
     public int getLastSessionRefresh() {
         return entity.getLastSessionRefresh();
     }

pom.xml 6(+6 -0)

diff --git a/pom.xml b/pom.xml
index d6a86f6..55f5369 100755
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,7 @@
         <twitter4j.version>3.0.5</twitter4j.version>
         <selenium.version>2.35.0</selenium.version>
         <javax.mail.version>1.4.5</javax.mail.version>
+        <infinispan.version>6.0.2.Final</infinispan.version>
 
         <!-- maven-compiler-plugin -->
         <maven.compiler.target>1.6</maven.compiler.target>
@@ -437,6 +438,11 @@
                 <artifactId>jboss-logging-processor</artifactId>
                 <version>${jboss-logging-tools.version}</version>
             </dependency>
+            <dependency>
+                <groupId>org.infinispan</groupId>
+                <artifactId>infinispan-core</artifactId>
+                <version>${infinispan.version}</version>
+            </dependency>
         </dependencies>
     </dependencyManagement>
 
diff --git a/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
index 6760f41..513bf8c 100755
--- a/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
+++ b/server/src/main/webapp/WEB-INF/jboss-deployment-structure.xml
@@ -5,6 +5,7 @@
             <module name="org.jboss.resteasy.resteasy-jackson-provider" services="import"/>
             <module name="org.codehaus.jackson.jackson-core-asl"/>
             <module name="org.codehaus.jackson.jackson-mapper-asl"/>
+            <module name="org.infinispan"/>
         </dependencies>
         <exclusions>
             <module name="org.jboss.resteasy.resteasy-jackson2-provider"/>
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index f64fc0a..2457705 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -82,13 +82,12 @@ public class AuthenticationManager {
         UserModel user = userSession.getUser();
 
         logger.debugv("Logging out: {0} ({1})", user.getUsername(), userSession.getId());
-
-        session.sessions().removeUserSession(realm, userSession);
         expireIdentityCookie(realm, uriInfo, connection);
         expireRememberMeCookie(realm, uriInfo, connection);
 
         new ResourceAdminManager().logoutUser(uriInfo.getRequestUri(), realm, user.getId(), userSession);
 
+        session.sessions().removeUserSession(realm, userSession);
     }
 
 
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index bf2656a..79bc142 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -142,7 +142,7 @@ public class UsersResource {
         if (session.users().getUserByUsername(rep.getUsername(), realm) != null) {
             return Flows.errors().exists("User exists with same username");
         }
-        if (session.users().getUserByEmail(rep.getEmail(), realm) != null) {
+        if (rep.getEmail() != null && session.users().getUserByEmail(rep.getEmail(), realm) != null) {
             return Flows.errors().exists("User exists with same email");
         }
 
diff --git a/services/src/main/java/org/keycloak/services/util/JsonConfigProvider.java b/services/src/main/java/org/keycloak/services/util/JsonConfigProvider.java
index 785cc81..bf91944 100755
--- a/services/src/main/java/org/keycloak/services/util/JsonConfigProvider.java
+++ b/services/src/main/java/org/keycloak/services/util/JsonConfigProvider.java
@@ -17,17 +17,20 @@ public class JsonConfigProvider implements Config.ConfigProvider {
 
     @Override
     public String getProvider(String spi) {
-        JsonNode n = getNode(spi, "provider");
+        JsonNode n = getNode(config, spi, "provider");
         return n != null ? StringPropertyReplacer.replaceProperties(n.getTextValue()) : null;
     }
 
     @Override
     public Config.Scope scope(String... path) {
-        return new JsonScope(getNode(path));
+        return new JsonScope(getNode(config, path));
     }
 
-    private JsonNode getNode(String... path) {
-        JsonNode n = config;
+    private static JsonNode getNode(JsonNode root, String... path) {
+        if (root == null) {
+            return null;
+        }
+        JsonNode n = root;
         for (String p : path) {
             n = n.get(p);
             if (n == null) {
@@ -144,6 +147,11 @@ public class JsonConfigProvider implements Config.ConfigProvider {
                 return n.getBooleanValue();
             }
         }
+
+        @Override
+        public Config.Scope scope(String... path) {
+            return new JsonScope(getNode(config, path));
+        }
     }
 
 }
diff --git a/testsuite/docker-cluster/assembly.xml b/testsuite/docker-cluster/assembly.xml
new file mode 100644
index 0000000..d7c98cf
--- /dev/null
+++ b/testsuite/docker-cluster/assembly.xml
@@ -0,0 +1,30 @@
+<assembly>
+    <id>docker-cluster</id>
+
+    <formats>
+        <format>dir</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+
+    <fileSets>
+        <fileSet>
+            <directory>../../examples/demo-template</directory>
+            <outputDirectory>examples</outputDirectory>
+            <includes>
+                <include>**/target/*.war</include>
+                <include>**/testrealm.json</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>target/deployments</directory>
+            <outputDirectory>deployments</outputDirectory>
+            <excludes>
+                <exclude>**/keycloak-ds.xml</exclude>
+            </excludes>
+        </fileSet>
+        <fileSet>
+            <directory>target/modules</directory>
+            <outputDirectory>modules</outputDirectory>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/testsuite/docker-cluster/fig.yml b/testsuite/docker-cluster/fig.yml
new file mode 100644
index 0000000..046d73b
--- /dev/null
+++ b/testsuite/docker-cluster/fig.yml
@@ -0,0 +1,31 @@
+httpd:
+   build: httpd
+   ports:
+      - "8000:80"
+      - "10001:10001"
+   volumes_from:
+      - mysql
+mysql:
+   image: mysql:5.6.20
+   environment:
+      - MYSQL_ROOT_PASSWORD=mysecretpassword
+      - MYSQL_DATABASE=keycloak_db
+   volumes:
+      - /keycloak-docker-shared
+      - /apachelogs
+   ports:
+      - "33306:3306"
+node:
+   build: wildfly
+   command: /keycloak-run-node.sh
+   volumes:
+      - target/keycloak-docker-cluster:/keycloak-docker-cluster
+   volumes_from:
+      - mysql
+   links:
+      - httpd:httpd
+      - mysql:mysql
+   ports:
+      - "8787"
+      - "8080"
+      - "9990"
diff --git a/testsuite/docker-cluster/httpd/Dockerfile b/testsuite/docker-cluster/httpd/Dockerfile
new file mode 100644
index 0000000..8a0d79a
--- /dev/null
+++ b/testsuite/docker-cluster/httpd/Dockerfile
@@ -0,0 +1,16 @@
+FROM fedora:20
+
+RUN cd /
+RUN yum -y install wget && yum -y install unzip
+RUN yum clean all
+
+RUN wget https://dl.dropboxusercontent.com/u/5525920/apache24-modcluster131.zip
+RUN unzip -q apache24-modcluster131.zip
+
+ADD httpd-run /bin/httpd-run
+RUN chmod u+x /bin/httpd-run
+
+ADD httpd.conf  /opt/jboss/httpd/httpd/conf/httpd.conf
+
+EXPOSE 80 10001
+CMD ["/bin/httpd-run"]
diff --git a/testsuite/docker-cluster/httpd/httpd.conf b/testsuite/docker-cluster/httpd/httpd.conf
new file mode 100644
index 0000000..8d3758e
--- /dev/null
+++ b/testsuite/docker-cluster/httpd/httpd.conf
@@ -0,0 +1,550 @@
+#
+# This is the main Apache HTTP server configuration file.  It contains the
+# configuration directives that give the server its instructions.
+# See <URL:http://httpd.apache.org/docs/2.4/> for detailed information.
+# In particular, see 
+# <URL:http://httpd.apache.org/docs/2.4/mod/directives.html>
+# for a discussion of each configuration directive.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do.  They're here only as hints or reminders.  If you are unsure
+# consult the online docs. You have been warned.  
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path.  If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so "logs/access_log"
+# with ServerRoot set to "/usr/local/apache2" will be interpreted by the
+# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log" 
+# will be interpreted as '/logs/access_log'.
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# Do not add a slash at the end of the directory path.  If you point
+# ServerRoot at a non-local disk, be sure to specify a local disk on the
+# Mutex directive, if file-based mutexes are used.  If you wish to share the
+# same ServerRoot for multiple httpd daemons, you will need to change at
+# least PidFile.
+#
+ServerRoot "/opt/jboss/httpd/httpd"
+
+#
+# Mutex: Allows you to set the mutex mechanism and mutex file directory
+# for individual mutexes, or change the global defaults
+#
+# Uncomment and change the directory if mutexes are file-based and the default
+# mutex file directory is not on a local disk or is not appropriate for some
+# other reason.
+#
+# Mutex default:logs
+
+#
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the <VirtualHost>
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to 
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 80
+
+#
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Statically compiled modules (those listed by `httpd -l') do not need
+# to be loaded here.
+#
+# Example:
+# LoadModule foo_module modules/mod_foo.so
+#
+LoadModule authn_file_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_file.so
+#LoadModule authn_dbm_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_dbm.so
+#LoadModule authn_anon_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_anon.so
+#LoadModule authn_dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_dbd.so
+#LoadModule authn_socache_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_socache.so
+LoadModule authn_core_module /opt/jboss/httpd/lib/httpd/modules/mod_authn_core.so
+LoadModule authz_host_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_host.so
+LoadModule authz_groupfile_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_groupfile.so
+LoadModule authz_user_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_user.so
+#LoadModule authz_dbm_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_dbm.so
+#LoadModule authz_owner_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_owner.so
+#LoadModule authz_dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_dbd.so
+LoadModule authz_core_module /opt/jboss/httpd/lib/httpd/modules/mod_authz_core.so
+LoadModule access_compat_module /opt/jboss/httpd/lib/httpd/modules/mod_access_compat.so
+LoadModule auth_basic_module /opt/jboss/httpd/lib/httpd/modules/mod_auth_basic.so
+#LoadModule auth_form_module /opt/jboss/httpd/lib/httpd/modules/mod_auth_form.so
+#LoadModule auth_digest_module /opt/jboss/httpd/lib/httpd/modules/mod_auth_digest.so
+#LoadModule allowmethods_module /opt/jboss/httpd/lib/httpd/modules/mod_allowmethods.so
+LoadModule advertise_module /opt/jboss/httpd/lib/httpd/modules/mod_advertise.so
+#LoadModule file_cache_module /opt/jboss/httpd/lib/httpd/modules/mod_file_cache.so
+#LoadModule cache_module /opt/jboss/httpd/lib/httpd/modules/mod_cache.so
+#LoadModule cache_disk_module /opt/jboss/httpd/lib/httpd/modules/mod_cache_disk.so
+#LoadModule cache_socache_module /opt/jboss/httpd/lib/httpd/modules/mod_cache_socache.so
+#LoadModule socache_shmcb_module /opt/jboss/httpd/lib/httpd/modules/mod_socache_shmcb.so
+#LoadModule socache_dbm_module /opt/jboss/httpd/lib/httpd/modules/mod_socache_dbm.so
+#LoadModule socache_memcache_module /opt/jboss/httpd/lib/httpd/modules/mod_socache_memcache.so
+#LoadModule watchdog_module /opt/jboss/httpd/lib/httpd/modules/mod_watchdog.so
+#LoadModule macro_module /opt/jboss/httpd/lib/httpd/modules/mod_macro.so
+#LoadModule dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_dbd.so
+#LoadModule dumpio_module /opt/jboss/httpd/lib/httpd/modules/mod_dumpio.so
+#LoadModule echo_module /opt/jboss/httpd/lib/httpd/modules/mod_echo.so
+#LoadModule buffer_module /opt/jboss/httpd/lib/httpd/modules/mod_buffer.so
+#LoadModule data_module /opt/jboss/httpd/lib/httpd/modules/mod_data.so
+#LoadModule ratelimit_module /opt/jboss/httpd/lib/httpd/modules/mod_ratelimit.so
+LoadModule reqtimeout_module /opt/jboss/httpd/lib/httpd/modules/mod_reqtimeout.so
+#LoadModule ext_filter_module /opt/jboss/httpd/lib/httpd/modules/mod_ext_filter.so
+#LoadModule request_module /opt/jboss/httpd/lib/httpd/modules/mod_request.so
+#LoadModule include_module /opt/jboss/httpd/lib/httpd/modules/mod_include.so
+LoadModule filter_module /opt/jboss/httpd/lib/httpd/modules/mod_filter.so
+#LoadModule reflector_module /opt/jboss/httpd/lib/httpd/modules/mod_reflector.so
+#LoadModule substitute_module /opt/jboss/httpd/lib/httpd/modules/mod_substitute.so
+#LoadModule sed_module /opt/jboss/httpd/lib/httpd/modules/mod_sed.so
+#LoadModule charset_lite_module /opt/jboss/httpd/lib/httpd/modules/mod_charset_lite.so
+#LoadModule deflate_module /opt/jboss/httpd/lib/httpd/modules/mod_deflate.so
+#LoadModule xml2enc_module /opt/jboss/httpd/lib/httpd/modules/mod_xml2enc.so
+#LoadModule proxy_html_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_html.so
+LoadModule mime_module /opt/jboss/httpd/lib/httpd/modules/mod_mime.so
+LoadModule log_config_module /opt/jboss/httpd/lib/httpd/modules/mod_log_config.so
+#LoadModule log_debug_module /opt/jboss/httpd/lib/httpd/modules/mod_log_debug.so
+#LoadModule log_forensic_module /opt/jboss/httpd/lib/httpd/modules/mod_log_forensic.so
+#LoadModule logio_module /opt/jboss/httpd/lib/httpd/modules/mod_logio.so
+LoadModule env_module /opt/jboss/httpd/lib/httpd/modules/mod_env.so
+#LoadModule mime_magic_module /opt/jboss/httpd/lib/httpd/modules/mod_mime_magic.so
+#LoadModule expires_module /opt/jboss/httpd/lib/httpd/modules/mod_expires.so
+LoadModule headers_module /opt/jboss/httpd/lib/httpd/modules/mod_headers.so
+#LoadModule usertrack_module /opt/jboss/httpd/lib/httpd/modules/mod_usertrack.so
+#LoadModule unique_id_module /opt/jboss/httpd/lib/httpd/modules/mod_unique_id.so
+LoadModule setenvif_module /opt/jboss/httpd/lib/httpd/modules/mod_setenvif.so
+LoadModule version_module /opt/jboss/httpd/lib/httpd/modules/mod_version.so
+#LoadModule remoteip_module /opt/jboss/httpd/lib/httpd/modules/mod_remoteip.so
+LoadModule proxy_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy.so
+LoadModule proxy_connect_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_connect.so
+LoadModule proxy_ftp_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_ftp.so
+LoadModule proxy_http_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_http.so
+LoadModule proxy_fcgi_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_fcgi.so
+LoadModule proxy_scgi_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_scgi.so
+#LoadModule proxy_fdpass_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_fdpass.so
+LoadModule proxy_wstunnel_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_wstunnel.so
+LoadModule proxy_ajp_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_ajp.so
+LoadModule proxy_cluster_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_cluster.so
+LoadModule proxy_express_module /opt/jboss/httpd/lib/httpd/modules/mod_proxy_express.so
+#LoadModule session_module /opt/jboss/httpd/lib/httpd/modules/mod_session.so
+#LoadModule session_cookie_module /opt/jboss/httpd/lib/httpd/modules/mod_session_cookie.so
+#LoadModule session_dbd_module /opt/jboss/httpd/lib/httpd/modules/mod_session_dbd.so
+#LoadModule slotmem_shm_module /opt/jboss/httpd/lib/httpd/modules/mod_slotmem_shm.so
+#LoadModule slotmem_plain_module /opt/jboss/httpd/lib/httpd/modules/mod_slotmem_plain.so
+#LoadModule ssl_module /opt/jboss/httpd/lib/httpd/modules/mod_ssl.so
+#LoadModule dialup_module /opt/jboss/httpd/lib/httpd/modules/mod_dialup.so
+LoadModule lbmethod_byrequests_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_byrequests.so
+LoadModule lbmethod_bytraffic_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_bytraffic.so
+LoadModule lbmethod_bybusyness_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_bybusyness.so
+LoadModule lbmethod_heartbeat_module /opt/jboss/httpd/lib/httpd/modules/mod_lbmethod_heartbeat.so
+LoadModule unixd_module /opt/jboss/httpd/lib/httpd/modules/mod_unixd.so
+#LoadModule heartbeat_module /opt/jboss/httpd/lib/httpd/modules/mod_heartbeat.so
+#LoadModule heartmonitor_module /opt/jboss/httpd/lib/httpd/modules/mod_heartmonitor.so
+#LoadModule dav_module /opt/jboss/httpd/lib/httpd/modules/mod_dav.so
+LoadModule status_module /opt/jboss/httpd/lib/httpd/modules/mod_status.so
+LoadModule autoindex_module /opt/jboss/httpd/lib/httpd/modules/mod_autoindex.so
+#LoadModule asis_module /opt/jboss/httpd/lib/httpd/modules/mod_asis.so
+#LoadModule info_module /opt/jboss/httpd/lib/httpd/modules/mod_info.so
+#LoadModule suexec_module /opt/jboss/httpd/lib/httpd/modules/mod_suexec.so
+#LoadModule cgi_module /opt/jboss/httpd/lib/httpd/modules/mod_cgi.so
+#LoadModule cgid_module /opt/jboss/httpd/lib/httpd/modules/mod_cgid.so
+LoadModule cluster_slotmem_module /opt/jboss/httpd/lib/httpd/modules/mod_cluster_slotmem.so
+LoadModule manager_module /opt/jboss/httpd/lib/httpd/modules/mod_manager.so
+#LoadModule dav_fs_module /opt/jboss/httpd/lib/httpd/modules/mod_dav_fs.so
+#LoadModule dav_lock_module /opt/jboss/httpd/lib/httpd/modules/mod_dav_lock.so
+#LoadModule vhost_alias_module /opt/jboss/httpd/lib/httpd/modules/mod_vhost_alias.so
+#LoadModule negotiation_module /opt/jboss/httpd/lib/httpd/modules/mod_negotiation.so
+LoadModule dir_module /opt/jboss/httpd/lib/httpd/modules/mod_dir.so
+#LoadModule actions_module /opt/jboss/httpd/lib/httpd/modules/mod_actions.so
+#LoadModule speling_module /opt/jboss/httpd/lib/httpd/modules/mod_speling.so
+#LoadModule userdir_module /opt/jboss/httpd/lib/httpd/modules/mod_userdir.so
+LoadModule alias_module /opt/jboss/httpd/lib/httpd/modules/mod_alias.so
+#LoadModule rewrite_module /opt/jboss/httpd/lib/httpd/modules/mod_rewrite.so
+
+<IfModule unixd_module>
+#
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.  
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# It is usually good practice to create a dedicated user and group for
+# running httpd, as with most system services.
+#
+User daemon
+Group daemon
+
+</IfModule>
+
+# 'Main' server configuration
+#
+# The directives in this section set up the values used by the 'main'
+# server, which responds to any requests that aren't handled by a
+# <VirtualHost> definition.  These values also provide defaults for
+# any <VirtualHost> containers you may define later in the file.
+#
+# All of these directives may appear inside <VirtualHost> containers,
+# in which case these default settings will be overridden for the
+# virtual host being defined.
+#
+
+#
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed.  This address appears on some server-generated pages, such
+# as error documents.  e.g. admin@your-domain.com
+#
+ServerAdmin you@example.com
+
+#
+# ServerName gives the name and port that the server uses to identify itself.
+# This can often be determined automatically, but we recommend you specify
+# it explicitly to prevent problems during startup.
+#
+# If your host doesn't have a registered DNS name, enter its IP address here.
+#
+#ServerName www.example.com:80
+
+#
+# Deny access to the entirety of your server's filesystem. You must
+# explicitly permit access to web content directories in other 
+# <Directory> blocks below.
+#
+<Directory />
+    AllowOverride none
+    Require all denied
+</Directory>
+
+#
+# Note that from this point forward you must specifically allow
+# particular features to be enabled - so if something's not working as
+# you might expect, make sure that you have specifically enabled it
+# below.
+#
+
+#
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+DocumentRoot "/opt/jboss/httpd/htdocs/htdocs"
+<Directory "/opt/jboss/httpd/htdocs/htdocs">
+    #
+    # Possible values for the Options directive are "None", "All",
+    # or any combination of:
+    #   Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
+    #
+    # Note that "MultiViews" must be named *explicitly* --- "Options All"
+    # doesn't give it to you.
+    #
+    # The Options directive is both complicated and important.  Please see
+    # http://httpd.apache.org/docs/2.4/mod/core.html#options
+    # for more information.
+    #
+    Options Indexes FollowSymLinks
+
+    #
+    # AllowOverride controls what directives may be placed in .htaccess files.
+    # It can be "All", "None", or any combination of the keywords:
+    #   AllowOverride FileInfo AuthConfig Limit
+    #
+    AllowOverride None
+
+    #
+    # Controls who can get stuff from this server.
+    #
+    Require all granted
+</Directory>
+
+#
+# DirectoryIndex: sets the file that Apache will serve if a directory
+# is requested.
+#
+<IfModule dir_module>
+    DirectoryIndex index.html
+</IfModule>
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being 
+# viewed by Web clients. 
+#
+<Files ".ht*">
+    Require all denied
+</Files>
+
+#
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a <VirtualHost>
+# container, error messages relating to that virtual host will be
+# logged here.  If you *do* define an error logfile for a <VirtualHost>
+# container, that host's errors will be logged there and not here.
+#
+# Note: Actually it's custom location mounted to docker volume
+ErrorLog "/apachelogs/error_log"
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel info
+
+<IfModule log_config_module>
+    #
+    # The following directives define some format nicknames for use with
+    # a CustomLog directive (see below).
+    #
+    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+    LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+    <IfModule logio_module>
+      # You need to enable mod_logio.c to use %I and %O
+      LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+    </IfModule>
+
+    #
+    # The location and format of the access logfile (Common Logfile Format).
+    # If you do not define any access logfiles within a <VirtualHost>
+    # container, they will be logged here.  Contrariwise, if you *do*
+    # define per-<VirtualHost> access logfiles, transactions will be
+    # logged therein and *not* in this file.
+    #
+    CustomLog "/apachelogs/access_log" common
+
+    #
+    # If you prefer a logfile with access, agent, and referer information
+    # (Combined Logfile Format) you can use the following directive.
+    #
+    #CustomLog "logs/access_log" combined
+</IfModule>
+
+<IfModule alias_module>
+    #
+    # Redirect: Allows you to tell clients about documents that used to 
+    # exist in your server's namespace, but do not anymore. The client 
+    # will make a new request for the document at its new location.
+    # Example:
+    # Redirect permanent /foo http://www.example.com/bar
+
+    #
+    # Alias: Maps web paths into filesystem paths and is used to
+    # access content that does not live under the DocumentRoot.
+    # Example:
+    # Alias /webpath /full/filesystem/path
+    #
+    # If you include a trailing / on /webpath then the server will
+    # require it to be present in the URL.  You will also likely
+    # need to provide a <Directory> section to allow access to
+    # the filesystem path.
+
+    #
+    # ScriptAlias: This controls which directories contain server scripts. 
+    # ScriptAliases are essentially the same as Aliases, except that
+    # documents in the target directory are treated as applications and
+    # run by the server when requested rather than as documents sent to the
+    # client.  The same rules about trailing "/" apply to ScriptAlias
+    # directives as to Alias.
+    #
+    ScriptAlias /cgi-bin/ "/opt/jboss/httpd/htdocs/cgi-bin/"
+
+</IfModule>
+
+<IfModule cgid_module>
+    #
+    # ScriptSock: On threaded servers, designate the path to the UNIX
+    # socket used to communicate with the CGI daemon of mod_cgid.
+    #
+    #Scriptsock cgisock
+</IfModule>
+
+#
+# "/opt/jboss/httpd/htdocs/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+#
+<Directory "/opt/jboss/httpd/htdocs/cgi-bin">
+    AllowOverride None
+    Options None
+    Require all granted
+</Directory>
+
+<IfModule mime_module>
+    #
+    # TypesConfig points to the file containing the list of mappings from
+    # filename extension to MIME-type.
+    #
+    TypesConfig conf/mime.types
+
+    #
+    # AddType allows you to add to or override the MIME configuration
+    # file specified in TypesConfig for specific file types.
+    #
+    #AddType application/x-gzip .tgz
+    #
+    # AddEncoding allows you to have certain browsers uncompress
+    # information on the fly. Note: Not all browsers support this.
+    #
+    #AddEncoding x-compress .Z
+    #AddEncoding x-gzip .gz .tgz
+    #
+    # If the AddEncoding directives above are commented-out, then you
+    # probably should define those extensions to indicate media types:
+    #
+    AddType application/x-compress .Z
+    AddType application/x-gzip .gz .tgz
+
+    #
+    # AddHandler allows you to map certain file extensions to "handlers":
+    # actions unrelated to filetype. These can be either built into the server
+    # or added with the Action directive (see below)
+    #
+    # To use CGI scripts outside of ScriptAliased directories:
+    # (You will also need to add "ExecCGI" to the "Options" directive.)
+    #
+    #AddHandler cgi-script .cgi
+
+    # For type maps (negotiated resources):
+    #AddHandler type-map var
+
+    #
+    # Filters allow you to process content before it is sent to the client.
+    #
+    # To parse .shtml files for server-side includes (SSI):
+    # (You will also need to add "Includes" to the "Options" directive.)
+    #
+    #AddType text/html .shtml
+    #AddOutputFilter INCLUDES .shtml
+</IfModule>
+
+#
+# The mod_mime_magic module allows the server to use various hints from the
+# contents of the file itself to determine its type.  The MIMEMagicFile
+# directive tells the module where the hint definitions are located.
+#
+#MIMEMagicFile conf/magic
+
+#
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+#
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+#
+
+#
+# MaxRanges: Maximum number of Ranges in a request before
+# returning the entire resource, or one of the special
+# values 'default', 'none' or 'unlimited'.
+# Default setting is to accept 200 Ranges.
+#MaxRanges unlimited
+
+#
+# EnableMMAP and EnableSendfile: On systems that support it, 
+# memory-mapping or the sendfile syscall may be used to deliver
+# files.  This usually improves server performance, but must
+# be turned off when serving from networked-mounted 
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+# Defaults: EnableMMAP On, EnableSendfile Off
+#
+#EnableMMAP off
+#EnableSendfile on
+
+# Supplemental configuration
+#
+# The configuration files in the conf/extra/ directory can be 
+# included to add extra features or to modify the default configuration of 
+# the server, or you may simply copy their contents here and change as 
+# necessary.
+
+# Server-pool management (MPM specific)
+#Include conf/extra/httpd-mpm.conf
+
+# Multi-language error messages
+#Include conf/extra/httpd-multilang-errordoc.conf
+
+# Fancy directory listings
+#Include conf/extra/httpd-autoindex.conf
+
+# Language settings
+#Include conf/extra/httpd-languages.conf
+
+# User home directories
+#Include conf/extra/httpd-userdir.conf
+
+# Real-time info on requests and configuration
+#Include conf/extra/httpd-info.conf
+
+# Virtual hosts
+#Include conf/extra/httpd-vhosts.conf
+
+# Local access to the Apache HTTP Server Manual
+#Include conf/extra/httpd-manual.conf
+
+# Distributed authoring and versioning (WebDAV)
+#Include conf/extra/httpd-dav.conf
+
+# Various default settings
+#Include conf/extra/httpd-default.conf
+
+# Configure mod_proxy_html to understand HTML4/XHTML1
+<IfModule proxy_html_module>
+Include conf/extra/proxy-html.conf
+</IfModule>
+
+# Secure (SSL/TLS) connections
+#Include conf/extra/httpd-ssl.conf
+#
+# Note: The following must must be present to support
+#       starting without SSL on platforms with no /dev/random equivalent
+#       but a statically compiled-in mod_ssl.
+#
+<IfModule ssl_module>
+SSLRandomSeed startup builtin
+SSLRandomSeed connect builtin
+</IfModule>
+#
+# uncomment out the below to deal with user agents that deliberately
+# violate open standards by misusing DNT (DNT *must* be a specific
+# end-user choice)
+#
+#<IfModule setenvif_module>
+#BrowserMatch "MSIE 10.0;" bad_DNT
+#</IfModule>
+#<IfModule headers_module>
+#RequestHeader unset DNT env=bad_DNT
+#</IfModule>
+
+# MOD_CLUSTER_ADDS
+# Adjust to you hostname and subnet.
+<IfModule manager_module>
+  Listen *:10001
+  ManagerBalancerName mycluster
+  <VirtualHost *:10001>
+    <Location />
+     Require all granted
+    </Location>
+
+    KeepAliveTimeout 300
+    MaxKeepAliveRequests 0
+    #ServerAdvertise on http://@IP@:6666
+    AdvertiseFrequency 5
+    #AdvertiseSecurityKey secret
+    #AdvertiseGroup @ADVIP@:23364
+    EnableMCPMReceive
+
+    <Location /mod_cluster_manager>
+       SetHandler mod_cluster-manager
+       Require all granted
+    </Location>
+
+  </VirtualHost>
+</IfModule>
diff --git a/testsuite/docker-cluster/httpd/httpd-run b/testsuite/docker-cluster/httpd/httpd-run
new file mode 100644
index 0000000..26e8f70
--- /dev/null
+++ b/testsuite/docker-cluster/httpd/httpd-run
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+# Make sure we're not confused by old, incompletely-shutdown httpd
+# context after restarting the container.  httpd won't start correctly
+# if it thinks it is already running.
+rm -rf /opt/jboss/httpd/httpd/logs/httpd.pid
+
+exec /opt/jboss/httpd/sbin/apachectl -D FOREGROUND
diff --git a/testsuite/docker-cluster/pom.xml b/testsuite/docker-cluster/pom.xml
new file mode 100644
index 0000000..d552ddf
--- /dev/null
+++ b/testsuite/docker-cluster/pom.xml
@@ -0,0 +1,89 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <artifactId>keycloak-testsuite-pom</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.1.0-Alpha1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>keycloak-testsuite-docker-cluster</artifactId>
+    <packaging>pom</packaging>
+    <name>Keycloak Docker Cluster</name>
+    <description/>
+
+    <dependencies>
+    </dependencies>
+    <build>
+        <finalName>keycloak-docker-cluster</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <configuration>
+                    <skip>true</skip>
+                </configuration>
+            </plugin>
+
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <!--<version>2.7</version> -->
+                <executions>
+                    <execution>
+                        <id>unpack</id>
+                        <phase>prepare-package</phase>
+                        <goals>
+                            <goal>unpack</goal>
+                        </goals>
+                        <configuration>
+                            <artifactItems>
+                                <artifactItem>
+                                    <groupId>org.keycloak</groupId>
+                                    <artifactId>keycloak-war-deployment</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                </artifactItem>
+                                <artifactItem>
+                                    <groupId>org.keycloak</groupId>
+                                    <artifactId>keycloak-wildfly-adapter-dist</artifactId>
+                                    <version>${project.version}</version>
+                                    <type>zip</type>
+                                    <outputDirectory>${project.build.directory}</outputDirectory>
+                                </artifactItem>
+                            </artifactItems>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <artifactId>maven-assembly-plugin</artifactId>
+                <version>2.4</version>
+                <executions>
+                    <execution>
+                        <id>assemble</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>single</goal>
+                        </goals>
+                        <configuration>
+                            <descriptors>
+                                <descriptor>assembly.xml</descriptor>
+                            </descriptors>
+                            <outputDirectory>
+                                target
+                            </outputDirectory>
+                            <workDirectory>
+                                target/assembly/work
+                            </workDirectory>
+                            <appendAssemblyId>false</appendAssemblyId>
+                        </configuration>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+
+</project>
diff --git a/testsuite/docker-cluster/README.md b/testsuite/docker-cluster/README.md
new file mode 100644
index 0000000..2aaa4bf
--- /dev/null
+++ b/testsuite/docker-cluster/README.md
@@ -0,0 +1,135 @@
+How to test Keycloak cluster with Docker
+========================================
+Docker+Fig allows to easily setup and test the whole environment with:
+* Apache HTTPD 2.4 + modcluster 1.3 as Load Balancer
+* MySQL 5.6.1 as database
+* Various number of Keycloak cluster nodes running on WildFly (with "demo" examples deployed)
+
+You don't need to setup Apache with modcluster + MySQL on your laptop as Docker will do it for you and all will run in Docker containers.
+
+Steps to setup
+--------------
+1) Download and install [Docker](https://docs.docker.com/installation) and [Fig](http://www.fig.sh/install.html)
+
+2) Build Keycloak including distribution. This will be used by Docker+Fig. The point is that you can test clustering stuff from latest Keycloak master:
+```shell 
+$ cd $KEYCLOAK_HOME
+$ mvn clean install
+$ cd distribution
+$ mvn clean install
+````
+
+3) Build Docker with maven to ensure that needed data will be accessible to Docker+Fig volumes: 
+```shell 
+$ cd $KEYCLOAK_HOME/testsuite/docker-cluster
+$ mvn clean install
+````
+ 
+4) Build fig and run the whole env. By default it will run Apache + MySQL + 1 Keycloak node:
+```shell
+$ fig build
+$ fig up
+````
+
+First build will take long time as it need to download bunch of stuff and install into Docker container. Next builds will be much faster due to Docker cache.
+After some time, WildFly server is started
+
+Testing
+-------
+
+Apache is running in separate container and have 2 ports exposed locally: 10001 and 8000. Port 10001 is for modCluster - you should 
+be able to access Apache modCluster status page: [http://localhost:10001/mod_cluster_manager](http://localhost:10001/mod_cluster_manager) and see one node
+with deployed "auth-server.war" and few other WARs (keycloak demo). 
+
+Also you can access Keycloak admin console via loadBalancer on [http://localhost:8000/auth/admin](http://localhost:8000/auth/admin) and similarly Account mgmt. 
+TODO: Examples currently doesn't work and I am looking at it..
+
+MySQL can be directly accessed from your machine (if you have MySQL client installed):
+```shell
+$ mysql -h127.0.0.1 -P33306 -uroot -pmysecretpassword
+````
+Used database is "keycloak_db"
+
+Remote debugging
+----------------
+
+With command:
+```shell
+$ docker ps
+````
+ 
+You can see running ports. For the Keycloak node you may see output similar to this:
+```shell
+0.0.0.0:49153->8080/tcp, 0.0.0.0:49154->8787/tcp, 0.0.0.0:49155->9990/tcp
+````
+
+This means that you can directly access Keycloak (bypass loadbalancer) by going to [http://localhost:49153/auth/admin](http://localhost:49153/auth/admin) . 
+Also it means that debugger is mapped From Docker port 8787 to local port 49154 . So in your IDE you can connect with settings similar to:
+```shell
+-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=49154
+````
+
+Scale / more cluster nodes
+--------------------------
+
+Run this in separate terminal to add more (in this case 2) cluster nodes:
+```shell
+$ fig scale node=2
+````
+
+Now it should be visible on mod_cluster_manager page that they are 2 nodes.
+
+Seeing logs
+-----------
+It's easiest to do:
+```shell
+$ fig logs
+````
+to see output of MySql and Keycloak server consoles.
+
+To see Apache and debug logs of keycloak server:
+```shell
+$ fig run node /bin/bash
+````
+  
+Then you're in shell inside docker container, which has some mounted volumes with apache logs and keycloak nodes. Apache logs are at:
+```shell
+$ cd /apachelogs/
+````
+
+Keycloak nodes are at (debug logging enabled by default for "org.keycloak"):
+```shell
+$ cd /keycloak-docker/shared
+````
+ 
+Restart whole environment
+-------------------------
+
+Just run:
+```shell
+$ fig stop
+$ fig start
+````
+
+This will restart apache + MySQL + all nodes, but won't clear data.
+
+Changing configuration and clear data
+-------------------------------------
+Changing configuration (for example UserSession provider from 'mem' to 'jpa') is possible in
+```shell
+$KEYCLOAK_HOME/testsuite/docker-cluster/target/keycloak-docker-cluster/deployments/auth-server.war/WEB-INF/classes/META-INF/keycloak-server.json
+````
+
+then whole environment needs to be stopped, containers removed (in order to update configuration in nodes) and started again:
+```shell 
+$ fig stop
+$ fig rm
+$ fig up
+````
+ 
+Rebuilding after changed sources
+-------------------------------
+In this case you might need to stop and remove existing containers. Then start from step 2 (Rebuild Keycloak or at least 
+changed jars, then rebuild distribution and testsuite/docker-cluster 
+(or just copy changed JAR into $KEYCLOAK_HOME/testsuite/docker-cluster/target/keycloak-docker-cluster/deployments/auth-server.war/WEB-INF/lib if it's not adapter stuff. 
+But 'fig rm' is safer to call anyway)
diff --git a/testsuite/docker-cluster/wildfly/Dockerfile b/testsuite/docker-cluster/wildfly/Dockerfile
new file mode 100644
index 0000000..3727d5b
--- /dev/null
+++ b/testsuite/docker-cluster/wildfly/Dockerfile
@@ -0,0 +1,35 @@
+FROM jboss/wildfly
+
+USER root
+RUN yum install -y unzip && yum install -y wget && yum install -y mc && yum -y install nc
+RUN yum clean all
+
+RUN cd /tmp
+RUN wget http://search.maven.org/remotecontent?filepath=mysql/mysql-connector-java/5.1.32/mysql-connector-java-5.1.32.jar
+RUN mv *.jar mysql-connector-java-5.1.32.jar
+
+RUN mkdir -p mysql/main && mv mysql-connector-java-5.1.32.jar mysql/main/ 
+ADD mysql-module.xml mysql/main/module.xml
+RUN mv mysql /opt/wildfly/modules/system/layers/base/com/
+
+RUN sed -i -e 's/<extensions>/&\n <extension module="org.keycloak.keycloak-wildfly-subsystem"\/>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<profile>/&\n <subsystem xmlns="urn:jboss:domain:keycloak:1.0"\/>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<security-domains>/&\n <security-domain name="keycloak">\n  <authentication>\n   <login-module code="org.keycloak.adapters.jboss.KeycloakLoginModule" flag="required"\/>\n  <\/authentication>\n <\/security-domain>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<drivers>/&\n <driver name="mysql" module="com.mysql">\n  <xa-datasource-class>com.mysql.jdbc.Driver<\/xa-datasource-class>\n <\/driver>/' /opt/wildfly/standalone/configuration/standalone-ha.xml && \
+sed -i -e 's/<\/periodic-rotating-file-handler>/&\n <logger category=\"org.keycloak\">\n  <level name=\"DEBUG\" \/> \n <\/logger>/' /opt/wildfly/standalone/configuration/standalone-ha.xml
+
+RUN sed -i -e 's/<subsystem xmlns=\"urn:jboss:domain:infinispan:2\.0\">/&\n <cache-container name=\"keycloak\" jndi-name=\"infinispan\/Keycloak\" start=\"EAGER\"> \
+\n  <transport lock-timeout=\"60000\"\/>\n  <distributed-cache name=\"sessions\" mode=\"SYNC\" owners=\"2\" segments=\"60\"\/> \
+\n  <invalidation-cache name=\"realms\" mode=\"SYNC\"\/>\n <\/cache-container>/' /opt/wildfly/standalone/configuration/standalone-ha.xml
+
+RUN sed -i "s|<mod-cluster-config .*>|<mod-cluster-config advertise-socket=\"modcluster\" proxy-list=\"\$\{httpd.proxyList\}\" proxy-url=\"\/\" balancer=\"mycluster\" advertise=\"false\" connector=\"ajp\" sticky-session=\"true\">|" /opt/wildfly/standalone/configuration/standalone-ha.xml
+
+RUN sed -i "s|#JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|JAVA_OPTS=\"\$JAVA_OPTS -agentlib:jdwp=transport=dt_socket|" /opt/wildfly/bin/standalone.conf
+
+ADD mysql-keycloak-ds.xml /opt/wildfly/standalone/deployments/
+ADD keycloak-run-node.sh /keycloak-run-node.sh
+RUN chmod u+x /keycloak-run-node.sh
+
+EXPOSE 8787
+
+CMD [ "/keycloak-run-node.sh" ]
diff --git a/testsuite/docker-cluster/wildfly/keycloak-run-node.sh b/testsuite/docker-cluster/wildfly/keycloak-run-node.sh
new file mode 100644
index 0000000..b9af405
--- /dev/null
+++ b/testsuite/docker-cluster/wildfly/keycloak-run-node.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+
+export MYHOST=node$(echo $MYSQL_NAME | awk -F"/dockercluster[^0-9]*|\/mysql" '{print  $2 }');
+echo "MYHOST is $MYHOST. MYSQL_NAME is $MYSQL_NAME";
+
+function waitForPreviousNodeStart
+{
+  myHostNumber=$(echo $MYHOST | awk -F"node" '{ print $2 }');
+  if [ $myHostNumber -eq 1 ]; then
+    echo "Our host is node1. No need to wait for previous server";
+  else 
+    previous=node$(($myHostNumber-1));
+    echo "Waiting for host $previous to start";
+    
+    for I in $(seq 1 10); do
+      cat /keycloak-docker-shared/keycloak-wildfly-$previous/standalone/log/server.log | grep "\(INFO\|ERROR\).*WildFly.*started";
+      if [ 0 -eq $? ]; then
+        echo "Host $previous started. Going to start $MYHOST";
+        return;
+      fi;
+      
+      echo "Host $previous not started yet. Still waiting...";      
+      sleep 5;
+    done; 
+
+    echo "Host $previous not started yet within timeout.";
+  fi;      
+}
+
+function waitForMySQLStart
+{
+  for I in $(seq 1 10); do
+    nc $MYSQL_PORT_3306_TCP_ADDR 3306 < /dev/null;
+    mysqlRunning=$(echo $?);
+    if [ $mysqlRunning -eq 0 ]; then
+      echo "MySQL is running. Starting our server";
+      return;
+    else
+      echo "MySQL not yet available. Still waiting...";
+      sleep 5;
+    fi;
+  done;
+}
+
+echo "Creating keycloak-wildfly-$MYHOST";
+
+cd /opt/wildfly
+cp -r /keycloak-docker-cluster/modules ./
+
+# Deploy keycloak
+cp -r /keycloak-docker-cluster/deployments/* /opt/wildfly/standalone/deployments/
+
+# Deploy examples
+cd /keycloak-docker-cluster/examples
+for I in $(find . | grep .war$); do cp $I /opt/wildfly/standalone/deployments/; done;
+
+# Deploy to volume
+rm -rf /keycloak-docker-shared/keycloak-wildfly-$MYHOST
+cp -r /opt/wildfly-8.1.0.Final /keycloak-docker-shared/keycloak-wildfly-$MYHOST
+chmod -R 777 /keycloak-docker-shared/keycloak-wildfly-$MYHOST
+echo "keycloak-wildfly-$MYHOST prepared and copyied to volume";
+
+
+waitForPreviousNodeStart;
+waitForMySQLStart;
+
+echo "Running keycloak node $MYHOST. Additional arguments: $@";
+cd /keycloak-docker-shared
+export JBOSS_HOME=/keycloak-docker-shared/keycloak-wildfly-$MYHOST;
+
+cd $JBOSS_HOME/bin/
+
+./standalone.sh -c standalone-ha.xml -Djboss.node.name=$MYHOST -b `hostname -i` -Djboss.mod_cluster.jvmRoute=$MYHOST \
+-Dmysql.host=$MYSQL_PORT_3306_TCP_ADDR -Dhttpd.proxyList=$HTTPD_1_PORT_10001_TCP_ADDR:$HTTPD_PORT_10001_TCP_PORT \
+-Dkeycloak.import=/keycloak-docker-cluster/examples/testrealm.json "$@"
diff --git a/testsuite/docker-cluster/wildfly/mysql-keycloak-ds.xml b/testsuite/docker-cluster/wildfly/mysql-keycloak-ds.xml
new file mode 100644
index 0000000..cd6982e
--- /dev/null
+++ b/testsuite/docker-cluster/wildfly/mysql-keycloak-ds.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<datasources xmlns="http://www.jboss.org/ironjacamar/schema">        
+  <datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true">
+    <connection-url>jdbc:mysql://${mysql.host}/keycloak_db</connection-url>
+    <driver>mysql</driver>
+    <security>
+      <user-name>root</user-name>
+      <password>mysecretpassword</password>
+    </security>
+  </datasource>
+</datasources>
diff --git a/testsuite/docker-cluster/wildfly/mysql-module.xml b/testsuite/docker-cluster/wildfly/mysql-module.xml
new file mode 100644
index 0000000..82490c0
--- /dev/null
+++ b/testsuite/docker-cluster/wildfly/mysql-module.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+  ~ JBoss, Home of Professional Open Source.
+  ~ Copyright 2011, Red Hat, Inc., and individual contributors
+  ~ as indicated by the @author tags. See the copyright.txt file in the
+  ~ distribution for a full listing of individual contributors.
+  ~
+  ~ This is free software; you can redistribute it and/or modify it
+  ~ under the terms of the GNU Lesser General Public License as
+  ~ published by the Free Software Foundation; either version 2.1 of
+  ~ the License, or (at your option) any later version.
+  ~
+  ~ This software is distributed in the hope that it will be useful,
+  ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+  ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  ~ Lesser General Public License for more details.
+  ~
+  ~ You should have received a copy of the GNU Lesser General Public
+  ~ License along with this software; if not, write to the Free
+  ~ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  ~ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  -->
+
+<module xmlns="urn:jboss:module:1.0" name="com.mysql">
+    <resources>
+        <resource-root path="mysql-connector-java-5.1.32.jar"/>        
+    </resources>
+
+    <dependencies>
+       <module name="javax.api" />
+       <module name="javax.transaction.api" />       
+    </dependencies>
+</module>
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index b6053bf..6941eb3 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -184,6 +184,10 @@
             </exclusions>
         </dependency>
         <dependency>
+            <groupId>org.infinispan</groupId>
+            <artifactId>infinispan-core</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.seleniumhq.selenium</groupId>
             <artifactId>selenium-java</artifactId>
         </dependency>
@@ -377,6 +381,26 @@
 
         </profile>
 
+        <profile>
+            <id>infinispan</id>
+
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-surefire-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <keycloak.realm.cache.provider>infinispan</keycloak.realm.cache.provider>
+                                <keycloak.user.cache.provider>infinispan</keycloak.user.cache.provider>
+                                <keycloak.userSessions.provider>infinispan</keycloak.userSessions.provider>
+                            </systemPropertyVariables>
+                        </configuration>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
         <!-- MySQL -->
         <profile>
             <activation>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 3eb3dae..3ce90dd 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -160,12 +160,12 @@ public class AccountTest {
         });
     }
 
-    @Test
-    @Ignore
-    public void runit() throws Exception {
-        Thread.sleep(10000000);
-
-    }
+//    @Test
+//    @Ignore
+//    public void runit() throws Exception {
+//        Thread.sleep(10000000);
+//
+//    }
 
 
 
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
index fc9b9fe..3f0279d 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/AssertEvents.java
@@ -156,7 +156,7 @@ public class AssertEvents implements TestRule, EventListenerProviderFactory {
     }
 
     public ExpectedEvent expectRegister(String username, String email) {
-        UserRepresentation user = keycloak.getUser("test", username);
+        UserRepresentation user = username != null ? keycloak.getUser("test", username) : null;
         return expect(EventType.REGISTER)
                 .user(user != null ? user.getId() : null)
                 .detail(Details.USERNAME, username)
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
index 9cb61f5..298ebd5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -104,12 +104,12 @@ public class FederationProvidersIntegrationTest {
     @WebResource
     protected AccountPasswordPage changePasswordPage;
 
-    @Test
-    @Ignore
-    public void runit() throws Exception {
-        Thread.sleep(10000000);
-
-    }
+//    @Test
+//    @Ignore
+//    public void runit() throws Exception {
+//        Thread.sleep(10000000);
+//
+//    }
 
     static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
         UserModel user = session.users().addUser(realm, username);
diff --git a/testsuite/pom.xml b/testsuite/pom.xml
index 07fe7f7..8780d19 100755
--- a/testsuite/pom.xml
+++ b/testsuite/pom.xml
@@ -29,6 +29,7 @@
         <module>performance</module>
         <module>tools</module>
         <module>performance-web</module>
+        <!--<module>docker-cluster</module>-->
     </modules>
 
 </project>