keycloak-memoizeit

Details

diff --git a/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java b/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
index 5ee0b56..b8911f2 100644
--- a/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
+++ b/server-spi/src/main/java/org/keycloak/models/UserFederationSyncResult.java
@@ -22,11 +22,21 @@ package org.keycloak.models;
  */
 public class UserFederationSyncResult {
 
+    private boolean ignored;
+
     private int added;
     private int updated;
     private int removed;
     private int failed;
 
+    public boolean isIgnored() {
+        return ignored;
+    }
+
+    public void setIgnored(boolean ignored) {
+        this.ignored = ignored;
+    }
+
     public int getAdded() {
         return added;
     }
@@ -83,11 +93,15 @@ public class UserFederationSyncResult {
     }
 
     public String getStatus() {
-        String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
-        if (failed != 0) {
-            status += String.format(", %d users failed sync! See server log for more details", failed);
+        if (ignored) {
+            return "Synchronization ignored as it's already in progress";
+        } else {
+            String status = String.format("%d imported users, %d updated users, %d removed users", added, updated, removed);
+            if (failed != 0) {
+                status += String.format(", %d users failed sync! See server log for more details", failed);
+            }
+            return status;
         }
-        return status;
     }
 
     @Override
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 8dafd44..3672b3d 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -237,6 +237,16 @@
             <scope>test</scope>
         </dependency>
 
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.postgresql</groupId>
+            <artifactId>postgresql</artifactId>
+            <version>${postgresql.version}</version>
+        </dependency>
+
     </dependencies>
     <build>
         <plugins>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
new file mode 100644
index 0000000..517d45e
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncDummyUserFederationProviderFactory.java
@@ -0,0 +1,100 @@
+/*
+ * 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.testsuite.federation.sync;
+
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.testsuite.DummyUserFederationProviderFactory;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SyncDummyUserFederationProviderFactory extends DummyUserFederationProviderFactory {
+
+    private static final Logger logger = Logger.getLogger(SyncDummyUserFederationProviderFactory.class);
+
+    public static final String SYNC_PROVIDER_ID = "sync-dummy";
+    public static final String WAIT_TIME = "wait-time"; // waitTime before transaction is commited
+
+    @Override
+    public String getId() {
+        return SYNC_PROVIDER_ID;
+    }
+
+    @Override
+    public Set<String> getConfigurationOptions() {
+        Set<String> list = super.getConfigurationOptions();
+        list.add(WAIT_TIME);
+        return list;
+    }
+
+    @Override
+    public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
+
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                int waitTime = Integer.parseInt(model.getConfig().get(WAIT_TIME));
+
+                logger.infof("Starting sync of changed users. Wait time is: %s", waitTime);
+
+                RealmModel realm = session.realms().getRealm(realmId);
+
+                // KEYCLOAK-2412 : Just remove and add some users for testing purposes
+                for (int i = 0; i<10; i++) {
+                    String username = "dummyuser-" + i;
+                    UserModel user = session.userStorage().getUserByUsername(username, realm);
+
+                    if (user != null) {
+                        session.userStorage().removeUser(realm, user);
+                    }
+
+                    user = session.userStorage().addUser(realm, username);
+                }
+
+                logger.infof("Finished sync of changed users. Waiting now for %d seconds", waitTime);
+
+
+                try {
+                    Thread.sleep(waitTime * 1000);
+                } catch (InterruptedException ie) {
+                    Thread.currentThread().interrupt();
+                    throw new RuntimeException("Interrupted!", ie);
+                }
+
+                logger.infof("Finished waiting");
+            }
+
+        });
+
+        return new UserFederationSyncResult();
+    }
+
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
new file mode 100644
index 0000000..8259fe8
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/sync/SyncFederationTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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.testsuite.federation.sync;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.keycloak.common.util.Time;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.UserFederationSyncResult;
+import org.keycloak.services.managers.RealmManager;
+import org.keycloak.services.managers.UsersSyncManager;
+import org.keycloak.testsuite.DummyUserFederationProviderFactory;
+import org.keycloak.testsuite.rule.KeycloakRule;
+import org.keycloak.timer.TimerProvider;
+
+/**
+ * Test with Dummy providers (For LDAP see {@link org.keycloak.testsuite.federation.ldap.base.LDAPSyncTest}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class SyncFederationTest {
+
+    private static UserFederationProviderModel dummyModel = null;
+
+    @ClassRule
+    public static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            // Other tests may left Time offset uncleared, which could cause issues
+            Time.setOffset(0);
+        }
+    });
+
+    @Test
+    public void test01PeriodicSync() {
+
+        // Enable timer for SyncDummyUserFederationProvider
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-sync-dummy", -1, 1, 0);
+            }
+
+        });
+
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            DummyUserFederationProviderFactory dummyFedFactory = (DummyUserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, DummyUserFederationProviderFactory.PROVIDER_NAME);
+            int full = dummyFedFactory.getFullSyncCounter();
+            int changed = dummyFedFactory.getChangedSyncCounter();
+
+            // Assert that after some period was DummyUserFederationProvider triggered
+            UsersSyncManager usersSyncManager = new UsersSyncManager();
+            usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
+            sleep(1800);
+
+            // Cancel timer
+            usersSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), dummyModel);
+
+            // Assert that DummyUserFederationProviderFactory.syncChangedUsers was invoked
+            int newChanged = dummyFedFactory.getChangedSyncCounter();
+            Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
+            Assert.assertTrue(newChanged > changed);
+
+            // Assert that dummy provider won't be invoked anymore
+            sleep(1800);
+            Assert.assertEquals(full, dummyFedFactory.getFullSyncCounter());
+            Assert.assertEquals(newChanged, dummyFedFactory.getChangedSyncCounter());
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        // remove dummyProvider
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.removeUserFederationProvider(dummyModel);
+            }
+
+        });
+    }
+
+    @Test
+    public void test02ConcurrentSync() {
+        // Enable timer for SyncDummyUserFederationProvider
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                Map<String, String> config = new HashMap<>();
+                config.put(SyncDummyUserFederationProviderFactory.WAIT_TIME, "2000");
+                dummyModel = appRealm.addUserFederationProvider(SyncDummyUserFederationProviderFactory.SYNC_PROVIDER_ID, config, 1, "test-sync-dummy", -1, 1, 0);
+            }
+
+        });
+
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+
+            // bootstrap periodic sync
+            UsersSyncManager usersSyncManager = new UsersSyncManager();
+            usersSyncManager.bootstrapPeriodic(sessionFactory, session.getProvider(TimerProvider.class));
+
+            // Wait and then trigger sync manually. Assert it will be ignored
+            sleep(1800);
+            RealmModel realm = session.realms().getRealm("test");
+            UserFederationSyncResult syncResult = usersSyncManager.syncChangedUsers(sessionFactory, realm.getId(), dummyModel);
+            Assert.assertTrue(syncResult.isIgnored());
+
+            // Cancel timer
+            usersSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), dummyModel);
+        } finally {
+            keycloakRule.stopSession(session, true);
+        }
+
+        // remove provider
+        keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+            @Override
+            public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+                appRealm.removeUserFederationProvider(dummyModel);
+            }
+
+        });
+    }
+
+    private void sleep(int time) {
+        try {
+            Thread.sleep(time);
+        } catch (InterruptedException ie) {
+            throw new RuntimeException(ie);
+        }
+    }
+}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java
index b29b610..0efac4a 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/InfinispanCLI.java
@@ -69,7 +69,8 @@ public class InfinispanCLI {
             UserCommands.Create.class,
             UserCommands.Remove.class,
             UserCommands.Count.class,
-            UserCommands.GetUser.class
+            UserCommands.GetUser.class,
+            SyncDummyFederationProviderCommand.class
     };
 
     private final KeycloakSessionFactory sessionFactory;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/SyncDummyFederationProviderCommand.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/SyncDummyFederationProviderCommand.java
new file mode 100644
index 0000000..7a31d6d
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/util/cli/SyncDummyFederationProviderCommand.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.testsuite.util.cli;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.services.managers.UsersSyncManager;
+import org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory;
+import org.keycloak.timer.TimerProvider;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SyncDummyFederationProviderCommand extends AbstractCommand {
+
+    @Override
+    protected void doRunCommand(KeycloakSession session) {
+        int waitTime = getIntArg(0);
+        int changedSyncPeriod = getIntArg(1);
+
+        RealmModel realm = session.realms().getRealmByName("master");
+        UserFederationProviderModel fedProviderModel = KeycloakModelUtils.findUserFederationProviderByDisplayName("cluster-dummy", realm);
+        if (fedProviderModel == null) {
+            Map<String, String> cfg = new HashMap<>();
+            updateConfig(cfg, waitTime);
+            fedProviderModel = realm.addUserFederationProvider(SyncDummyUserFederationProviderFactory.SYNC_PROVIDER_ID, cfg, 1, "cluster-dummy", -1, changedSyncPeriod, -1);
+        } else {
+            Map<String, String> cfg = fedProviderModel.getConfig();
+            updateConfig(cfg, waitTime);
+            realm.updateUserFederationProvider(fedProviderModel);
+        }
+
+        new UsersSyncManager().refreshPeriodicSyncForProvider(sessionFactory, session.getProvider(TimerProvider.class), fedProviderModel, "master");
+
+        log.infof("User federation provider created and sync was started", waitTime);
+    }
+
+    private void updateConfig(Map<String, String> cfg, int waitTime) {
+        cfg.put(SyncDummyUserFederationProviderFactory.WAIT_TIME, String.valueOf(waitTime));
+    }
+
+
+    @Override
+    public String getName() {
+        return "startSyncDummy";
+    }
+
+    @Override
+    public String printUsage() {
+        return super.printUsage() + " <wait-time-before-sync-commit-in-seconds> <changed-sync-period-in-seconds>";
+    }
+}
diff --git a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
index c730b1e..1b4de73 100755
--- a/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
+++ b/testsuite/integration/src/test/resources/META-INF/services/org.keycloak.models.UserFederationProviderFactory
@@ -15,4 +15,5 @@
 # limitations under the License.
 #
 
-org.keycloak.testsuite.DummyUserFederationProviderFactory
\ No newline at end of file
+org.keycloak.testsuite.DummyUserFederationProviderFactory
+org.keycloak.testsuite.federation.sync.SyncDummyUserFederationProviderFactory
\ No newline at end of file