keycloak-aplcache

Merge pull request #2226 from mposolda/master KEYCLOAK-1989

2/12/2016 10:32:21 AM

Details

diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
index 9cf19c2..1fc04de 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProvider.java
@@ -24,6 +24,7 @@ import org.keycloak.common.util.Time;
 import org.keycloak.models.*;
 import org.keycloak.models.session.UserSessionPersisterProvider;
 import org.keycloak.models.sessions.infinispan.entities.*;
+import org.keycloak.models.sessions.infinispan.initializer.TimeAwareInitializerState;
 import org.keycloak.models.sessions.infinispan.stream.*;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RealmInfoUtil;
@@ -411,6 +412,19 @@ public class InfinispanUserSessionProvider implements UserSessionProvider {
     }
 
     @Override
+    public int getClusterStartupTime() {
+        TimeAwareInitializerState state = (TimeAwareInitializerState) offlineSessionCache.get(InfinispanUserSessionProviderFactory.SESSION_INITIALIZER_STATE_KEY);
+        int startTime;
+        if (state == null) {
+            log.warn("Cluster startup time not yet available. Fallback to local startup time");
+            startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+        } else {
+            startTime = state.getClusterStartupTime();
+        }
+        return startTime;
+    }
+
+    @Override
     public void close() {
     }
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
index 7f20f0e..1bb82a1 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/InfinispanUserSessionProviderFactory.java
@@ -34,6 +34,9 @@ import org.keycloak.provider.ProviderEventListener;
 
 public class InfinispanUserSessionProviderFactory implements UserSessionProviderFactory {
 
+    private static final String STATE_KEY_PREFIX = "initializerState";
+    public static final String SESSION_INITIALIZER_STATE_KEY = STATE_KEY_PREFIX + "::offlineUserSessions";
+
     private static final Logger log = Logger.getLogger(InfinispanUserSessionProviderFactory.class);
 
     private Config.Scope config;
@@ -84,7 +87,7 @@ public class InfinispanUserSessionProviderFactory implements UserSessionProvider
                 InfinispanConnectionProvider connections = session.getProvider(InfinispanConnectionProvider.class);
                 Cache<String, SessionEntity> cache = connections.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
 
-                InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, "offlineUserSessions");
+                InfinispanUserSessionInitializer initializer = new InfinispanUserSessionInitializer(sessionFactory, cache, new OfflineUserSessionLoader(), maxErrors, sessionsPerSegment, SESSION_INITIALIZER_STATE_KEY);
                 initializer.initCache();
                 initializer.loadPersistentSessions();
             }
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
index 2ce4ac2..deae897 100755
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/InfinispanUserSessionInitializer.java
@@ -35,6 +35,7 @@ import org.infinispan.notifications.cachemanagerlistener.annotation.ViewChanged;
 import org.infinispan.notifications.cachemanagerlistener.event.ViewChangedEvent;
 import org.infinispan.remoting.transport.Transport;
 import org.jboss.logging.Logger;
+import org.keycloak.common.util.Time;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.KeycloakSessionTask;
@@ -51,8 +52,6 @@ public class InfinispanUserSessionInitializer {
 
     private static final Logger log = Logger.getLogger(InfinispanUserSessionInitializer.class);
 
-    private static final String STATE_KEY_PREFIX = "initializerState";
-
     private final KeycloakSessionFactory sessionFactory;
     private final Cache<String, SessionEntity> cache;
     private final SessionLoader sessionLoader;
@@ -60,21 +59,18 @@ public class InfinispanUserSessionInitializer {
     private final int sessionsPerSegment;
     private final String stateKey;
 
-    private volatile CountDownLatch latch = new CountDownLatch(1);
-
 
-    public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKeySuffix) {
+    public InfinispanUserSessionInitializer(KeycloakSessionFactory sessionFactory, Cache<String, SessionEntity> cache, SessionLoader sessionLoader, int maxErrors, int sessionsPerSegment, String stateKey) {
         this.sessionFactory = sessionFactory;
         this.cache = cache;
         this.sessionLoader = sessionLoader;
         this.maxErrors = maxErrors;
         this.sessionsPerSegment = sessionsPerSegment;
-        this.stateKey = STATE_KEY_PREFIX + "::" + stateKeySuffix;
+        this.stateKey = stateKey;
     }
 
     public void initCache() {
         this.cache.getAdvancedCache().getComponentRegistry().registerComponent(sessionFactory, KeycloakSessionFactory.class);
-        cache.getCacheManager().addListener(new ViewChangeListener());
     }
 
 
@@ -86,7 +82,7 @@ public class InfinispanUserSessionInitializer {
         while (!isFinished()) {
             if (!isCoordinator()) {
                 try {
-                    latch.await(500, TimeUnit.MILLISECONDS);
+                    Thread.sleep(1000);
                 } catch (InterruptedException ie) {
                     log.error("Interrupted", ie);
                 }
@@ -104,8 +100,10 @@ public class InfinispanUserSessionInitializer {
 
 
     private InitializerState getOrCreateInitializerState() {
-        InitializerState state = (InitializerState) cache.get(stateKey);
+        TimeAwareInitializerState state = (TimeAwareInitializerState) cache.get(stateKey);
         if (state == null) {
+            int startTime = (int)(sessionFactory.getServerStartupTimestamp() / 1000);
+
             final int[] count = new int[1];
 
             // Rather use separate transactions for update and counting
@@ -113,7 +111,7 @@ public class InfinispanUserSessionInitializer {
             KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
                 @Override
                 public void run(KeycloakSession session) {
-                    sessionLoader.init(session);
+                    sessionLoader.init(session, startTime);
                 }
 
             });
@@ -126,8 +124,9 @@ public class InfinispanUserSessionInitializer {
 
             });
 
-            state = new InitializerState();
+            state = new TimeAwareInitializerState();
             state.init(count[0], sessionsPerSegment);
+            state.setClusterStartupTime(startTime);
             saveStateToCache(state);
         }
         return state;
@@ -251,24 +250,6 @@ public class InfinispanUserSessionInitializer {
         }
     }
 
-
-    @Listener
-    public class ViewChangeListener {
-
-        @ViewChanged
-        public void viewChanged(ViewChangedEvent event) {
-            boolean isCoordinator = isCoordinator();
-            log.debug("View Changed: is coordinator: " + isCoordinator);
-
-            if (isCoordinator) {
-                latch.countDown();
-                latch = new CountDownLatch(1);
-            }
-        }
-
-    }
-
-
     public static class WorkerResult implements Serializable {
 
         private Integer segment;
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
index c847d51..8b651c0 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/OfflineUserSessionLoader.java
@@ -33,14 +33,13 @@ public class OfflineUserSessionLoader implements SessionLoader {
     private static final Logger log = Logger.getLogger(OfflineUserSessionLoader.class);
 
     @Override
-    public void init(KeycloakSession session) {
+    public void init(KeycloakSession session, int clusterStartupTime) {
         UserSessionPersisterProvider persister = session.getProvider(UserSessionPersisterProvider.class);
-        int startTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
 
-        log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", startTime);
+        log.debugf("Clearing detached sessions from persistent storage and updating timestamps to %d", clusterStartupTime);
 
         persister.clearDetachedUserSessions();
-        persister.updateAllTimestamps(startTime);
+        persister.updateAllTimestamps(clusterStartupTime);
     }
 
     @Override
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
index 3185a39..b8aa0f8 100644
--- a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/SessionLoader.java
@@ -26,7 +26,7 @@ import org.keycloak.models.KeycloakSession;
  */
 public interface SessionLoader extends Serializable {
 
-    void init(KeycloakSession session);
+    void init(KeycloakSession session, int clusterStartupTime);
 
     int getSessionsCount(KeycloakSession session);
 
diff --git a/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java
new file mode 100644
index 0000000..f5b7f04
--- /dev/null
+++ b/model/infinispan/src/main/java/org/keycloak/models/sessions/infinispan/initializer/TimeAwareInitializerState.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.models.sessions.infinispan.initializer;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class TimeAwareInitializerState extends InitializerState {
+
+    private int clusterStartupTime;
+
+    public int getClusterStartupTime() {
+        return clusterStartupTime;
+    }
+
+    public void setClusterStartupTime(int clusterStartupTime) {
+        this.clusterStartupTime = clusterStartupTime;
+    }
+}
diff --git a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
index 57cd49c..9d7c413 100755
--- a/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserSessionProvider.java
@@ -82,6 +82,9 @@ public interface UserSessionProvider extends Provider {
     void removeClientInitialAccessModel(RealmModel realm, String id);
     List<ClientInitialAccessModel> listClientInitialAccess(RealmModel realm);
 
+    // Will use startup time of this server in non-cluster environment
+    int getClusterStartupTime();
+
     void close();
 
 }
diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
index 45dc043..e4174d9 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -193,9 +193,9 @@ public class TokenManager {
         int currentTime = Time.currentTime();
 
         if (realm.isRevokeRefreshToken()) {
-            int serverStartupTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+            int clusterStartupTime = session.sessions().getClusterStartupTime();
 
-            if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (serverStartupTime != validation.clientSession.getTimestamp())) {
+            if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp() && (clusterStartupTime != validation.clientSession.getTimestamp())) {
                 throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token");
             }
 
diff --git a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
index 6fb06f6..4b49d79 100644
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -91,7 +91,6 @@ public class KeycloakApplication extends Application {
         singletons.add(new ObjectMapperResolver(Boolean.parseBoolean(System.getProperty("keycloak.jsonPrettyPrint", "false"))));
 
         migrateModel();
-        sessionFactory.publish(new PostMigrationEvent());
 
         boolean bootstrapAdminUser = false;
 
@@ -138,6 +137,8 @@ public class KeycloakApplication extends Application {
             session.close();
         }
 
+        sessionFactory.publish(new PostMigrationEvent());
+
         singletons.add(new WelcomeResource(bootstrapAdminUser));
 
         setupScheduledTasks(sessionFactory);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
index 929de9b..2d763b6 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserSessionInitializerTest.java
@@ -83,7 +83,7 @@ public class UserSessionInitializerTest {
 
         // Create and persist offline sessions
         int started = Time.currentTime();
-        int serverStartTime = (int)(session.getKeycloakSessionFactory().getServerStartupTimestamp() / 1000);
+        int serverStartTime = session.sessions().getClusterStartupTime();
 
         for (UserSessionModel origSession : origSessions) {
             UserSessionModel userSession = session.sessions().getUserSession(realm, origSession.getId());