keycloak-uncached

Support for periodic users sync

8/8/2014 6:30:24 PM

Changes

Details

diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java
index 19f1d0c..7229435 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationProviderRepresentation.java
@@ -12,6 +12,9 @@ public class UserFederationProviderRepresentation {
     private String providerName;
     private Map<String, String> config;
     private int priority;
+    private int fullSyncPeriod;
+    private int changedSyncPeriod;
+    private int lastSync;
 
     public String getId() {
         return id;
@@ -54,6 +57,30 @@ public class UserFederationProviderRepresentation {
         this.priority = priority;
     }
 
+    public int getFullSyncPeriod() {
+        return fullSyncPeriod;
+    }
+
+    public void setFullSyncPeriod(int fullSyncPeriod) {
+        this.fullSyncPeriod = fullSyncPeriod;
+    }
+
+    public int getChangedSyncPeriod() {
+        return changedSyncPeriod;
+    }
+
+    public void setChangedSyncPeriod(int changedSyncPeriod) {
+        this.changedSyncPeriod = changedSyncPeriod;
+    }
+
+    public int getLastSync() {
+        return lastSync;
+    }
+
+    public void setLastSync(int lastSync) {
+        this.lastSync = lastSync;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
diff --git a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
index f079e8c..f06250a 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/ClasspathPropertiesFederationFactory.java
@@ -12,6 +12,8 @@ import java.util.Properties;
  */
 public class ClasspathPropertiesFederationFactory extends BasePropertiesFederationFactory {
 
+    public static final String PROVIDER_NAME = "classpath-properties";
+
     @Override
     protected BasePropertiesFederationProvider createProvider(KeycloakSession session, UserFederationProviderModel model, Properties props) {
         return new ClasspathPropertiesFederationProvider(session, model, props);
@@ -30,6 +32,6 @@ public class ClasspathPropertiesFederationFactory extends BasePropertiesFederati
 
     @Override
     public String getId() {
-        return "classpath-properties";
+        return PROVIDER_NAME;
     }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java
index 6d3916e..fe00465 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserFederationProviderEntity.java
@@ -12,6 +12,9 @@ public class UserFederationProviderEntity {
     protected Map<String, String> config;
     protected int priority;
     protected String displayName;
+    private int fullSyncPeriod;
+    private int changedSyncPeriod;
+    private int lastSync;
 
 
     public String getId() {
@@ -53,4 +56,28 @@ public class UserFederationProviderEntity {
     public void setDisplayName(String displayName) {
         this.displayName = displayName;
     }
+
+    public int getFullSyncPeriod() {
+        return fullSyncPeriod;
+    }
+
+    public void setFullSyncPeriod(int fullSyncPeriod) {
+        this.fullSyncPeriod = fullSyncPeriod;
+    }
+
+    public int getChangedSyncPeriod() {
+        return changedSyncPeriod;
+    }
+
+    public void setChangedSyncPeriod(int changedSyncPeriod) {
+        this.changedSyncPeriod = changedSyncPeriod;
+    }
+
+    public int getLastSync() {
+        return lastSync;
+    }
+
+    public void setLastSync(int lastSync) {
+        this.lastSync = lastSync;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index 633756f..4ae280f 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -161,7 +161,7 @@ public interface RealmModel extends RoleContainerModel {
 
     List<UserFederationProviderModel> getUserFederationProviders();
 
-    UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName);
+    UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync);
     void updateUserFederationProvider(UserFederationProviderModel provider);
     void removeUserFederationProvider(UserFederationProviderModel provider);
     void setUserFederationProviders(List<UserFederationProviderModel> providers);
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
index 3f6c451..9c3bf1c 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderModel.java
@@ -16,10 +16,13 @@ public class UserFederationProviderModel {
     private Map<String, String> config = new HashMap<String, String>();
     private int priority;
     private String displayName;
+    private int fullSyncPeriod = -1;    // In seconds. -1 means that periodic full sync is disabled
+    private int changedSyncPeriod = -1; // In seconds. -1 means that periodic changed sync is disabled
+    private int lastSync;               // Date when last sync was done for this provider
 
     public UserFederationProviderModel() {};
 
-    public UserFederationProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName) {
+    public UserFederationProviderModel(String id, String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
         this.id = id;
         this.providerName = providerName;
         if (config != null) {
@@ -27,6 +30,9 @@ public class UserFederationProviderModel {
         }
         this.priority = priority;
         this.displayName = displayName;
+        this.fullSyncPeriod = fullSyncPeriod;
+        this.changedSyncPeriod = changedSyncPeriod;
+        this.lastSync = lastSync;
     }
 
     public String getId() {
@@ -64,4 +70,28 @@ public class UserFederationProviderModel {
     public void setDisplayName(String displayName) {
         this.displayName = displayName;
     }
+
+    public int getFullSyncPeriod() {
+        return fullSyncPeriod;
+    }
+
+    public void setFullSyncPeriod(int fullSyncPeriod) {
+        this.fullSyncPeriod = fullSyncPeriod;
+    }
+
+    public int getChangedSyncPeriod() {
+        return changedSyncPeriod;
+    }
+
+    public void setChangedSyncPeriod(int changedSyncPeriod) {
+        this.changedSyncPeriod = changedSyncPeriod;
+    }
+
+    public int getLastSync() {
+        return lastSync;
+    }
+
+    public void setLastSync(int lastSync) {
+        this.lastSync = lastSync;
+    }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index ab3c865..4499465 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -262,6 +262,9 @@ public class ModelToRepresentation {
         rep.setProviderName(model.getProviderName());
         rep.setPriority(model.getPriority());
         rep.setDisplayName(model.getDisplayName());
+        rep.setFullSyncPeriod(model.getFullSyncPeriod());
+        rep.setChangedSyncPeriod(model.getChangedSyncPeriod());
+        rep.setLastSync(model.getLastSync());
         return rep;
     }
 }
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index 66c8be9..c7d7c26 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -288,7 +288,8 @@ public class RepresentationToModel {
 
         for (UserFederationProviderRepresentation representation : providers) {
             UserFederationProviderModel model = new UserFederationProviderModel(representation.getId(), representation.getProviderName(),
-                    representation.getConfig(), representation.getPriority(), representation.getDisplayName());
+                    representation.getConfig(), representation.getPriority(), representation.getDisplayName(),
+                    representation.getFullSyncPeriod(), representation.getChangedSyncPeriod(), representation.getLastSync());
             result.add(model);
         }
         return result;
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index 5268441..9fb8a1e 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -594,9 +594,9 @@ public class RealmAdapter implements RealmModel {
     }
 
     @Override
-    public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName) {
+    public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
         getDelegateForUpdate();
-        return updated.addUserFederationProvider(providerName, config, priority, displayName);
+        return updated.addUserFederationProvider(providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
     }
 
     @Override
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
index aec144e..22370f3 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationProviderEntity.java
@@ -42,6 +42,13 @@ public class UserFederationProviderEntity {
     @Column(name="DISPLAY_NAME")
     private String displayName;
 
+    @Column(name="FULL_SYNC_PERIOD")
+    private int fullSyncPeriod;
+    @Column(name="CHANGED_SYNC_PERIOD")
+    private int changedSyncPeriod;
+    @Column(name="LAST_SYNC")
+    private int lastSync;
+
     public String getId() {
         return id;
     }
@@ -89,4 +96,28 @@ public class UserFederationProviderEntity {
     public void setDisplayName(String displayName) {
         this.displayName = displayName;
     }
+
+    public int getFullSyncPeriod() {
+        return fullSyncPeriod;
+    }
+
+    public void setFullSyncPeriod(int fullSyncPeriod) {
+        this.fullSyncPeriod = fullSyncPeriod;
+    }
+
+    public int getChangedSyncPeriod() {
+        return changedSyncPeriod;
+    }
+
+    public void setChangedSyncPeriod(int changedSyncPeriod) {
+        this.changedSyncPeriod = changedSyncPeriod;
+    }
+
+    public int getLastSync() {
+        return lastSync;
+    }
+
+    public void setLastSync(int lastSync) {
+        this.lastSync = lastSync;
+    }
 }
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index c53354e..4f0da3c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -676,14 +676,15 @@ public class RealmAdapter implements RealmModel {
         });
         List<UserFederationProviderModel> result = new ArrayList<UserFederationProviderModel>();
         for (UserFederationProviderEntity entity : copy) {
-            result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+            result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+                    entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
         }
 
         return result;
     }
 
     @Override
-    public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName) {
+    public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
         String id = KeycloakModelUtils.generateId();
         UserFederationProviderEntity entity = new UserFederationProviderEntity();
         entity.setId(id);
@@ -695,10 +696,13 @@ public class RealmAdapter implements RealmModel {
             displayName = id;
         }
         entity.setDisplayName(displayName);
+        entity.setFullSyncPeriod(fullSyncPeriod);
+        entity.setChangedSyncPeriod(changedSyncPeriod);
+        entity.setLastSync(lastSync);
         em.persist(entity);
         realm.getUserFederationProviders().add(entity);
         em.flush();
-        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName);
+        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
     }
 
     @Override
@@ -728,6 +732,9 @@ public class RealmAdapter implements RealmModel {
                 entity.setPriority(model.getPriority());
                 entity.setProviderName(model.getProviderName());
                 entity.setPriority(model.getPriority());
+                entity.setFullSyncPeriod(model.getFullSyncPeriod());
+                entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+                entity.setLastSync(model.getLastSync());
                 break;
             }
         }
@@ -750,13 +757,17 @@ public class RealmAdapter implements RealmModel {
                     if (displayName != null) {
                         entity.setDisplayName(model.getDisplayName());
                     }
+                    entity.setFullSyncPeriod(model.getFullSyncPeriod());
+                    entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+                    entity.setLastSync(model.getLastSync());
                     found = true;
                     break;
                 }
 
             }
             if (found) continue;
-            session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+            session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+                    entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
             it.remove();
             em.remove(entity);
         }
@@ -786,6 +797,9 @@ public class RealmAdapter implements RealmModel {
                 displayName = entity.getId();
             }
             entity.setDisplayName(displayName);
+            entity.setFullSyncPeriod(model.getFullSyncPeriod());
+            entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+            entity.setLastSync(model.getLastSync());
             em.persist(entity);
             realm.getUserFederationProviders().add(entity);
 
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index f275dee..893f52e 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -757,7 +757,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
     }
 
     @Override
-    public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName) {
+    public UserFederationProviderModel addUserFederationProvider(String providerName, Map<String, String> config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) {
         UserFederationProviderEntity entity = new UserFederationProviderEntity();
         entity.setId(KeycloakModelUtils.generateId());
         entity.setPriority(priority);
@@ -767,10 +767,13 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
             displayName = entity.getId();
         }
         entity.setDisplayName(displayName);
+        entity.setFullSyncPeriod(fullSyncPeriod);
+        entity.setChangedSyncPeriod(changedSyncPeriod);
+        entity.setLastSync(lastSync);
         realm.getUserFederationProviders().add(entity);
         updateRealm();
 
-        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName);
+        return new UserFederationProviderModel(entity.getId(), providerName, config, priority, displayName, fullSyncPeriod, changedSyncPeriod, lastSync);
     }
 
     @Override
@@ -779,7 +782,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         while (it.hasNext()) {
             UserFederationProviderEntity entity = it.next();
             if (entity.getId().equals(provider.getId())) {
-                session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+                session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+                        entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
                 it.remove();
             }
         }
@@ -799,6 +803,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
                 if (displayName != null) {
                     entity.setDisplayName(model.getDisplayName());
                 }
+                entity.setFullSyncPeriod(model.getFullSyncPeriod());
+                entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+                entity.setLastSync(model.getLastSync());
             }
         }
         updateRealm();
@@ -822,7 +829,8 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
         });
         List<UserFederationProviderModel> result = new LinkedList<UserFederationProviderModel>();
         for (UserFederationProviderEntity entity : copy) {
-            result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName()));
+            result.add(new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(),
+                    entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync()));
         }
 
         return result;
@@ -843,6 +851,9 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
                 entity.setDisplayName(entity.getId());
             }
             entity.setDisplayName(displayName);
+            entity.setFullSyncPeriod(model.getFullSyncPeriod());
+            entity.setChangedSyncPeriod(model.getChangedSyncPeriod());
+            entity.setLastSync(model.getLastSync());
             entities.add(entity);
         }
 
diff --git a/services/src/main/java/org/keycloak/services/managers/PeriodicSyncManager.java b/services/src/main/java/org/keycloak/services/managers/PeriodicSyncManager.java
new file mode 100644
index 0000000..7a86529
--- /dev/null
+++ b/services/src/main/java/org/keycloak/services/managers/PeriodicSyncManager.java
@@ -0,0 +1,105 @@
+package org.keycloak.services.managers;
+
+import java.util.List;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProvider;
+import org.keycloak.models.UserFederationProviderFactory;
+import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.timer.TimerProvider;
+import org.keycloak.util.Time;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class PeriodicSyncManager {
+
+    /**
+     * Check federationProviderModel of all realms and possibly start periodic sync for them
+     *
+     * @param sessionFactory
+     * @param timer
+     */
+    public void bootstrap(final KeycloakSessionFactory sessionFactory, final TimerProvider timer) {
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                List<RealmModel> realms = session.realms().getRealms();
+                for (final RealmModel realm : realms) {
+                    List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+                    for (final UserFederationProviderModel fedProvider : federationProviders) {
+                        startPeriodicSyncForProvider(sessionFactory, timer, fedProvider, realm.getId());
+                    }
+                }
+            }
+        });
+    }
+
+    public void startPeriodicSyncForProvider(final KeycloakSessionFactory sessionFactory, TimerProvider timer, final UserFederationProviderModel fedProvider, final String realmId) {
+        final UserFederationProviderFactory fedProviderFactory = (UserFederationProviderFactory) sessionFactory.getProviderFactory(UserFederationProvider.class, fedProvider.getProviderName());
+
+        if (fedProvider.getFullSyncPeriod() > 0) {
+            // We want periodic full sync for this provider
+            timer.schedule(new Runnable() {
+
+                @Override
+                public void run() {
+                    updateLastSyncInterval(sessionFactory, fedProvider, realmId);
+                    fedProviderFactory.syncAllUsers(sessionFactory, realmId, fedProvider);
+                }
+
+            }, fedProvider.getFullSyncPeriod() * 1000, fedProvider.getId() + "-FULL");
+        }
+
+        if (fedProvider.getChangedSyncPeriod() > 0) {
+            // We want periodic sync of just changed users for this provider
+            timer.schedule(new Runnable() {
+
+                @Override
+                public void run() {
+                    // See when we did last sync.
+                    int oldLastSync = fedProvider.getLastSync();
+                    updateLastSyncInterval(sessionFactory, fedProvider, realmId);
+                    fedProviderFactory.syncChangedUsers(sessionFactory, realmId, fedProvider, Time.toDate(oldLastSync));
+                }
+
+            }, fedProvider.getChangedSyncPeriod() * 1000, fedProvider.getId() + "-CHANGED");
+
+        }
+    }
+
+    public void removePeriodicSyncForProvider(TimerProvider timer, final UserFederationProviderModel fedProvider) {
+        timer.cancelTask(fedProvider.getId() + "-FULL");
+        timer.cancelTask(fedProvider.getId() + "-CHANGED");
+    }
+
+    // Update interval of last sync for given UserFederationProviderModel. Do it in separate transaction
+    private void updateLastSyncInterval(final KeycloakSessionFactory sessionFactory, final UserFederationProviderModel fedProvider, final String realmId) {
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                RealmModel persistentRealm = session.realms().getRealm(realmId);
+                List<UserFederationProviderModel> persistentFedProviders = persistentRealm.getUserFederationProviders();
+                for (UserFederationProviderModel persistentFedProvider : persistentFedProviders) {
+                    if (fedProvider.getId().equals(persistentFedProvider.getId())) {
+                        // Update persistent provider in DB
+                        int lastSync = Time.currentTime();
+                        persistentFedProvider.setLastSync(lastSync);
+                        persistentRealm.updateUserFederationProvider(persistentFedProvider);
+
+                        // Update "cached" reference
+                        fedProvider.setLastSync(lastSync);
+                    }
+                }
+            }
+
+        });
+    }
+
+}
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index ae1e116..c8b613c 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -12,12 +12,14 @@ import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RealmProvider;
 import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserSessionProvider;
 import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.models.utils.RepresentationToModel;
 import org.keycloak.representations.idm.RealmAuditRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.timer.TimerProvider;
 
 import java.util.Collections;
 import java.util.HashSet;
@@ -127,6 +129,8 @@ public class RealmManager {
     }
 
     public boolean removeRealm(RealmModel realm) {
+        List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+
         boolean removed = model.removeRealm(realm.getId());
         if (removed) {
             new ApplicationManager(this).removeApplication(getKeycloakAdminstrationRealm(), realm.getMasterAdminApp());
@@ -135,6 +139,12 @@ public class RealmManager {
             if (sessions != null) {
                 sessions.onRealmRemoved(realm);
             }
+
+            // Remove all periodic syncs for configured federation providers
+            PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+            for (final UserFederationProviderModel fedProvider : federationProviders) {
+                periodicSyncManager.removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), fedProvider);
+            }
         }
         return removed;
     }
@@ -202,6 +212,13 @@ public class RealmManager {
 
     public void importRealm(RealmRepresentation rep, RealmModel newRealm) {
         RepresentationToModel.importRealm(session, rep, newRealm);
+
+        // Refresh periodic sync tasks for configured federationProviders
+        List<UserFederationProviderModel> federationProviders = newRealm.getUserFederationProviders();
+        PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+        for (final UserFederationProviderModel fedProvider : federationProviders) {
+            periodicSyncManager.startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, newRealm.getId());
+        }
     }
 
     /**
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
index 9344d28..b6af7a4 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RealmAdminResource.java
@@ -12,6 +12,7 @@ import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.ModelDuplicateException;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserSessionModel;
 import org.keycloak.models.cache.CacheRealmProvider;
 import org.keycloak.models.cache.CacheUserProvider;
@@ -21,10 +22,12 @@ import org.keycloak.representations.adapters.action.SessionStats;
 import org.keycloak.representations.idm.RealmAuditRepresentation;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.services.managers.LDAPConnectionTestManager;
+import org.keycloak.services.managers.PeriodicSyncManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.services.managers.ResourceAdminManager;
 import org.keycloak.services.managers.TokenManager;
 import org.keycloak.services.resources.flows.Flows;
+import org.keycloak.timer.TimerProvider;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -160,6 +163,13 @@ public class RealmAdminResource {
                 cache.setEnabled(rep.isUserCacheEnabled());
             }
 
+            // Refresh periodic sync tasks for configured federationProviders
+            List<UserFederationProviderModel> federationProviders = realm.getUserFederationProviders();
+            PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+            for (final UserFederationProviderModel fedProvider : federationProviders) {
+                periodicSyncManager.startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), fedProvider, realm.getId());
+            }
+
             return Response.noContent().build();
         } catch (ModelDuplicateException e) {
             return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
index c156c4b..6039ccb 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UserFederationResource.java
@@ -12,6 +12,8 @@ import org.keycloak.models.utils.ModelToRepresentation;
 import org.keycloak.provider.ProviderFactory;
 import org.keycloak.representations.idm.UserFederationProviderFactoryRepresentation;
 import org.keycloak.representations.idm.UserFederationProviderRepresentation;
+import org.keycloak.services.managers.PeriodicSyncManager;
+import org.keycloak.timer.TimerProvider;
 
 import javax.ws.rs.Consumes;
 import javax.ws.rs.DELETE;
@@ -116,7 +118,10 @@ public class UserFederationResource {
         if (displayName != null && displayName.trim().equals("")) {
             displayName = null;
         }
-        UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName);
+        UserFederationProviderModel model = realm.addUserFederationProvider(rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
+                rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
+        new PeriodicSyncManager().startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
+
         return Response.created(uriInfo.getAbsolutePathBuilder().path(model.getId()).build()).build();
     }
 
@@ -136,8 +141,10 @@ public class UserFederationResource {
         if (displayName != null && displayName.trim().equals("")) {
             displayName = null;
         }
-        UserFederationProviderModel model = new UserFederationProviderModel(id, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName);
+        UserFederationProviderModel model = new UserFederationProviderModel(id, rep.getProviderName(), rep.getConfig(), rep.getPriority(), displayName,
+                rep.getFullSyncPeriod(), rep.getChangedSyncPeriod(), rep.getLastSync());
         realm.updateUserFederationProvider(model);
+        new PeriodicSyncManager().startPeriodicSyncForProvider(session.getKeycloakSessionFactory(), session.getProvider(TimerProvider.class), model, realm.getId());
     }
 
     /**
@@ -170,9 +177,9 @@ public class UserFederationResource {
     public void deleteProviderInstance(@PathParam("id") String id) {
         logger.info("deleteProvider");
         auth.requireManage();
-        UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null);
+        UserFederationProviderModel model = new UserFederationProviderModel(id, null, null, -1, null, -1, -1, 0);
         realm.removeUserFederationProvider(model);
-
+        new PeriodicSyncManager().removePeriodicSyncForProvider(session.getProvider(TimerProvider.class), model);
     }
 
 
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 3426336..ca8db44 100755
--- a/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
+++ b/services/src/main/java/org/keycloak/services/resources/KeycloakApplication.java
@@ -84,8 +84,8 @@ public class KeycloakApplication extends Application {
 
         setupDefaultRealm(context.getContextPath());
 
-        setupScheduledTasks(sessionFactory);
         importRealms(context);
+        setupScheduledTasks(sessionFactory);
     }
 
     public String getContextPath() {
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java
index d20ef87..2809544 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProviderFactory.java
@@ -1,5 +1,6 @@
 package org.keycloak.testutils;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
@@ -9,12 +10,20 @@ import org.keycloak.models.UserFederationProviderModel;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class DummyUserFederationProviderFactory implements UserFederationProviderFactory {
+
+    private static final Logger logger = Logger.getLogger(DummyUserFederationProviderFactory.class);
+    public static final String PROVIDER_NAME = "dummy";
+
+    private AtomicInteger fullSyncCounter = new AtomicInteger();
+    private AtomicInteger changedSyncCounter = new AtomicInteger();
+
     @Override
     public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
         return new DummyUserFederationProvider();
@@ -44,14 +53,26 @@ public class DummyUserFederationProviderFactory implements UserFederationProvide
 
     @Override
     public String getId() {
-        return "dummy";
+        return PROVIDER_NAME;
     }
 
     @Override
     public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+        logger.info("syncAllUsers invoked");
+        fullSyncCounter.incrementAndGet();
     }
 
     @Override
     public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+        logger.info("syncChangedUsers invoked");
+        changedSyncCounter.incrementAndGet();
+    }
+
+    public int getFullSyncCounter() {
+        return fullSyncCounter.get();
+    }
+
+    public int getChangedSyncCounter() {
+        return changedSyncCounter.get();
     }
 }
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 1e8e9ce..99f6558 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
@@ -60,7 +60,7 @@ public class FederationProvidersIntegrationTest {
             ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true");
             ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.WRITABLE.toString());
 
-            ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap");
+            ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap", -1, -1, 0);
 
             // Delete all LDAP users and add some new for testing
             PartitionManager partitionManager = getPartitionManager(manager.getSession(), ldapModel);
@@ -166,7 +166,7 @@ public class FederationProvidersIntegrationTest {
                 RealmManager manager = new RealmManager(session);
 
                 RealmModel appRealm = manager.getRealm("test");
-                ldapModel = appRealm.addUserFederationProvider(ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+                ldapModel = appRealm.addUserFederationProvider(ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0);
             } finally {
                 keycloakRule.stopSession(session, true);
             }
@@ -233,7 +233,8 @@ public class FederationProvidersIntegrationTest {
         try {
             RealmModel appRealm = session.realms().getRealmByName("test");
 
-            UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+            UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(),
+                    ldapModel.getPriority(), ldapModel.getDisplayName(), -1, -1, 0);
             model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.READ_ONLY.toString());
             appRealm.updateUserFederationProvider(model);
             UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
@@ -284,7 +285,8 @@ public class FederationProvidersIntegrationTest {
         try {
             RealmModel appRealm = session.realms().getRealmByName("test");
 
-            UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(), ldapModel.getDisplayName());
+            UserFederationProviderModel model = new UserFederationProviderModel(ldapModel.getId(), ldapModel.getProviderName(), ldapModel.getConfig(), ldapModel.getPriority(),
+                    ldapModel.getDisplayName(), -1, -1, 0);
             model.getConfig().put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
             appRealm.updateUserFederationProvider(model);
             UserModel user = session.users().getUserByUsername("johnkeycloak", appRealm);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
index 8105769..21ac290 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
@@ -1,6 +1,7 @@
 package org.keycloak.testsuite.forms;
 
 import java.util.Date;
+import java.util.HashMap;
 import java.util.Map;
 
 import org.junit.Assert;
@@ -10,6 +11,7 @@ import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
 import org.junit.runners.MethodSorters;
+import org.keycloak.examples.federation.properties.ClasspathPropertiesFederationFactory;
 import org.keycloak.federation.ldap.LDAPFederationProvider;
 import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
 import org.keycloak.federation.ldap.LDAPUtils;
@@ -21,10 +23,14 @@ import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
 import org.keycloak.models.UserModel;
 import org.keycloak.models.UserProvider;
+import org.keycloak.services.managers.PeriodicSyncManager;
 import org.keycloak.services.managers.RealmManager;
 import org.keycloak.testsuite.rule.KeycloakRule;
 import org.keycloak.testsuite.rule.LDAPRule;
+import org.keycloak.testutils.DummyUserFederationProvider;
+import org.keycloak.testutils.DummyUserFederationProviderFactory;
 import org.keycloak.testutils.LDAPEmbeddedServer;
+import org.keycloak.timer.TimerProvider;
 import org.picketlink.idm.PartitionManager;
 import org.picketlink.idm.model.basic.User;
 
@@ -37,6 +43,7 @@ public class SyncProvidersTest {
     private static LDAPRule ldapRule = new LDAPRule();
 
     private static UserFederationProviderModel ldapModel = null;
+    private static UserFederationProviderModel dummyModel = null;
 
     private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
 
@@ -47,7 +54,8 @@ public class SyncProvidersTest {
             ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "false");
             ldapConfig.put(LDAPFederationProvider.EDIT_MODE, UserFederationProvider.EditMode.UNSYNCED.toString());
 
-            ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap");
+            ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, "test-ldap",
+                    -1, -1, 0);
 
             // Delete all LDAP users and add 5 new users for testing
             PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(manager.getSession(), ldapModel);
@@ -65,9 +73,7 @@ public class SyncProvidersTest {
             LDAPUtils.updatePassword(partitionManager, user5, "password5");
 
             // Add properties provider
-//            Map<String,String> filePropertiesConfig = new HashMap<String, String>();
-//            filePropertiesConfig.put("path", );
-//            appRealm.addUserFederationProvider(FilePropertiesFederationFactory.PROVIDER_NAME, filePropertiesConfig, 1, "test-fileProps");
+            dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
         }
     });
 
@@ -99,11 +105,7 @@ public class SyncProvidersTest {
             assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
 
             // wait a bit
-            try {
-                Thread.sleep(1000);
-            } catch (InterruptedException ie) {
-                throw new RuntimeException(ie);
-            }
+            sleep(1000);
             Date beforeLDAPUpdate = new Date();
 
             // Add user to LDAP and update 'user5' in LDAP
@@ -135,6 +137,45 @@ public class SyncProvidersTest {
         }
     }
 
+    @Test
+    public void testPeriodicSync() {
+        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
+            PeriodicSyncManager periodicSyncManager = new PeriodicSyncManager();
+            periodicSyncManager.bootstrap(sessionFactory, session.getProvider(TimerProvider.class));
+            sleep(1800);
+
+            // Cancel timer
+            periodicSyncManager.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, false);
+        }
+    }
+
+    private void sleep(int time) {
+        try {
+            Thread.sleep(time);
+        } catch (InterruptedException ie) {
+            throw new RuntimeException(ie);
+        }
+    }
+
     private void assertUserImported(UserProvider userProvider, RealmModel realm, String username, String expectedFirstName, String expectedLastName, String expectedEmail) {
         UserModel user = userProvider.getUserByUsername(username, realm);
         Assert.assertNotNull(user);