keycloak-aplcache

Changes

export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportUtils.java 46(+0 -46)

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/BasePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
index 04845fb..b0d9925 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/BasePropertiesFederationFactory.java
@@ -2,12 +2,19 @@ package org.keycloak.examples.federation.properties;
 
 import org.keycloak.Config;
 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.UserModel;
+import org.keycloak.models.UserProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Properties;
 import java.util.Set;
@@ -84,4 +91,44 @@ public abstract class BasePropertiesFederationFactory implements UserFederationP
     public void close() {
 
     }
+
+    @Override
+    public void syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                RealmModel realm = session.realms().getRealm(realmId);
+                BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model);
+                Set<String> allUsernames = federationProvider.getProperties().stringPropertyNames();
+                for (String username : allUsernames) {
+                    federationProvider.getUserByUsername(realm, username);
+                }
+            }
+
+        });
+    }
+
+    @Override
+    public void syncChangedUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model, Date lastSync) {
+        KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+            @Override
+            public void run(KeycloakSession session) {
+                RealmModel realm = session.realms().getRealm(realmId);
+                UserProvider localProvider = session.userStorage();
+                BasePropertiesFederationProvider federationProvider = (BasePropertiesFederationProvider)getInstance(session, model);
+                Set<String> allUsernames = federationProvider.getProperties().stringPropertyNames();
+                for (String username : allUsernames) {
+                    UserModel localUser = localProvider.getUserByUsername(username, realm);
+
+                    if (localUser == null) {
+                        // New user, let's import him
+                        federationProvider.getUserByUsername(realm, username);
+                    }
+                }
+            }
+
+        });
+    }
 }
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/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
index 5c64b73..2f89575 100755
--- a/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
+++ b/examples/providers/federation-provider/src/main/java/org/keycloak/examples/federation/properties/FilePropertiesFederationFactory.java
@@ -14,6 +14,8 @@ import java.util.Properties;
  */
 public class FilePropertiesFederationFactory extends BasePropertiesFederationFactory {
 
+    public static final String PROVIDER_NAME = "file-properties";
+
     @Override
     protected BasePropertiesFederationProvider createProvider(KeycloakSession session, UserFederationProviderModel model, Properties props) {
         return new FilePropertiesFederationProvider(session, props, model);
@@ -35,6 +37,6 @@ public class FilePropertiesFederationFactory extends BasePropertiesFederationFac
      */
     @Override
     public String getId() {
-        return "file-properties";
+        return PROVIDER_NAME;
     }
 }
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java
new file mode 100644
index 0000000..185a966
--- /dev/null
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportImportSessionTask.java
@@ -0,0 +1,25 @@
+package org.keycloak.exportimport.util;
+
+import java.io.IOException;
+
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionTask;
+
+/**
+ * Just to wrap {@link IOException}
+ *
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public abstract class ExportImportSessionTask implements KeycloakSessionTask {
+
+    @Override
+    public void run(KeycloakSession session) {
+        try {
+            runExportImportTask(session);
+        } catch (IOException ioe) {
+            throw new RuntimeException("Error during export/import: " + ioe.getMessage(), ioe);
+        }
+    }
+
+    protected abstract void runExportImportTask(KeycloakSession session) throws IOException;
+}
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
index aa4d83d..edc5b52 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ImportUtils.java
@@ -77,7 +77,7 @@ public class ImportUtils {
             // We just imported master realm. All 'masterAdminApps' need to be refreshed
             RealmModel adminRealm = realm;
             for (RealmModel currentRealm : model.getRealms()) {
-                ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(currentRealm));
+                ApplicationModel masterApp = adminRealm.getApplicationByName(KeycloakModelUtils.getMasterRealmAdminApplicationName(currentRealm));
                 if (masterApp != null) {
                     currentRealm.setMasterAdminApp(masterApp);
                 }  else {
@@ -87,7 +87,7 @@ public class ImportUtils {
         } else {
             // Need to refresh masterApp for current realm
             RealmModel adminRealm = model.getRealm(adminRealmId);
-            ApplicationModel masterApp = adminRealm.getApplicationByName(ExportImportUtils.getMasterRealmAdminApplicationName(realm));
+            ApplicationModel masterApp = adminRealm.getApplicationByName(KeycloakModelUtils.getMasterRealmAdminApplicationName(realm));
             if (masterApp != null) {
                 realm.setMasterAdminApp(masterApp);
             }  else {
@@ -113,7 +113,7 @@ public class ImportUtils {
             adminRole = adminRealm.getRole(AdminRoles.ADMIN);
         }
 
-        ApplicationModel realmAdminApp = KeycloakModelUtils.createApplication(adminRealm, ExportImportUtils.getMasterRealmAdminApplicationName(realm));
+        ApplicationModel realmAdminApp = KeycloakModelUtils.createApplication(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationName(realm));
         realmAdminApp.setBearerOnly(true);
         realm.setMasterAdminApp(realmAdminApp);
 
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java
index 5899757..2f66d09 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/MultipleStepsExportProvider.java
@@ -6,8 +6,10 @@ import org.keycloak.exportimport.ExportProvider;
 import org.keycloak.exportimport.UsersExportStrategy;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.UserModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.RealmRepresentation;
 
 import java.io.IOException;
@@ -24,7 +26,7 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
     public void exportModel(KeycloakSessionFactory factory) throws IOException {
         final RealmsHolder holder = new RealmsHolder();
 
-        ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+        KeycloakModelUtils.runJobInTransaction(factory, new KeycloakSessionTask() {
 
             @Override
             public void run(KeycloakSession session) {
@@ -46,10 +48,10 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
         final UsersHolder usersHolder = new UsersHolder();
         final boolean exportUsersIntoRealmFile = usersExportStrategy == UsersExportStrategy.REALM_FILE;
 
-        ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+        KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
             @Override
-            public void run(KeycloakSession session) throws IOException {
+            protected void runExportImportTask(KeycloakSession session) throws IOException {
                 RealmModel realm = session.realms().getRealmByName(realmName);
                 RealmRepresentation rep = ExportUtils.exportRealm(session, realm, exportUsersIntoRealmFile);
                 writeRealm(realmName + "-realm.json", rep);
@@ -77,10 +79,10 @@ public abstract class MultipleStepsExportProvider implements ExportProvider {
                     usersHolder.currentPageEnd = usersHolder.totalCount;
                 }
 
-                ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+                KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
                     @Override
-                    public void run(KeycloakSession session) throws IOException {
+                    protected void runExportImportTask(KeycloakSession session) throws IOException {
                         RealmModel realm = session.realms().getRealmByName(realmName);
                         usersHolder.users = session.users().getUsers(realm, usersHolder.currentPageStart, usersHolder.currentPageEnd - usersHolder.currentPageStart);
 
diff --git a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
index 4c532f0..7399574 100755
--- a/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
+++ b/export-import/export-import-dir/src/main/java/org/keycloak/exportimport/dir/DirImportProvider.java
@@ -4,11 +4,12 @@ import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.exportimport.ImportProvider;
 import org.keycloak.exportimport.Strategy;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
 import org.keycloak.exportimport.util.ImportUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.util.JsonSerialization;
 
@@ -91,10 +92,10 @@ public class DirImportProvider implements ImportProvider {
         FileInputStream is = new FileInputStream(realmFile);
         final RealmRepresentation realmRep = JsonSerialization.readValue(is, RealmRepresentation.class);
 
-        ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+        KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
             @Override
-            public void run(KeycloakSession session) throws IOException {
+            public void runExportImportTask(KeycloakSession session) throws IOException {
                 ImportUtils.importRealm(session, realmRep, strategy);
             }
 
@@ -103,10 +104,10 @@ public class DirImportProvider implements ImportProvider {
         // Import users
         for (File userFile : userFiles) {
             final FileInputStream fis = new FileInputStream(userFile);
-            ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+            KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
                 @Override
-                public void run(KeycloakSession session) throws IOException {
+                protected void runExportImportTask(KeycloakSession session) throws IOException {
                     ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
                 }
             });
diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java
index 17cec51..a8d7d98 100755
--- a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java
+++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileExportProvider.java
@@ -3,12 +3,13 @@ package org.keycloak.exportimport.singlefile;
 import org.codehaus.jackson.map.ObjectMapper;
 import org.jboss.logging.Logger;
 import org.keycloak.exportimport.ExportProvider;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
 import org.keycloak.exportimport.util.ExportUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
 import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.util.JsonSerialization;
 
@@ -38,10 +39,10 @@ public class SingleFileExportProvider implements ExportProvider {
     @Override
     public void exportModel(KeycloakSessionFactory factory) throws IOException {
         logger.infof("Exporting model into file %s", this.file.getAbsolutePath());
-        ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+        KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
             @Override
-            public void run(KeycloakSession session) throws IOException {
+            protected void runExportImportTask(KeycloakSession session) throws IOException {
                 List<RealmModel> realms = session.realms().getRealms();
                 List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
                 for (RealmModel realm : realms) {
@@ -58,10 +59,10 @@ public class SingleFileExportProvider implements ExportProvider {
     @Override
     public void exportRealm(KeycloakSessionFactory factory, final String realmName) throws IOException {
         logger.infof("Exporting realm '%s' into file %s", realmName, this.file.getAbsolutePath());
-        ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+        KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
             @Override
-            public void run(KeycloakSession session) throws IOException {
+            protected void runExportImportTask(KeycloakSession session) throws IOException {
                 RealmModel realm = session.realms().getRealmByName(realmName);
                 RealmRepresentation realmRep = ExportUtils.exportRealm(session, realm, true);
                 writeToFile(realmRep);
diff --git a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
index 6fbe1c8..a1e1109 100755
--- a/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
+++ b/export-import/export-import-single-file/src/main/java/org/keycloak/exportimport/singlefile/SingleFileImportProvider.java
@@ -3,16 +3,15 @@ package org.keycloak.exportimport.singlefile;
 import org.jboss.logging.Logger;
 import org.keycloak.exportimport.ImportProvider;
 import org.keycloak.exportimport.Strategy;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
 import org.keycloak.exportimport.util.ImportUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
-import org.keycloak.util.JsonSerialization;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.util.JsonSerialization;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -30,10 +29,10 @@ public class SingleFileImportProvider implements ImportProvider {
     @Override
     public void importModel(KeycloakSessionFactory factory, final Strategy strategy) throws IOException {
         logger.infof("Full importing from file %s", this.file.getAbsolutePath());
-        ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+        KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
             @Override
-            public void run(KeycloakSession session) throws IOException {
+            protected void runExportImportTask(KeycloakSession session) throws IOException {
                 FileInputStream is = new FileInputStream(file);
                 ImportUtils.importFromStream(session, JsonSerialization.mapper, is, strategy);
             }
diff --git a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
index d9a93ee..69cfd46 100755
--- a/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
+++ b/export-import/export-import-zip/src/main/java/org/keycloak/exportimport/zip/ZipImportProvider.java
@@ -8,11 +8,12 @@ import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.exportimport.ImportProvider;
 import org.keycloak.exportimport.Strategy;
-import org.keycloak.exportimport.util.ExportImportJob;
-import org.keycloak.exportimport.util.ExportImportUtils;
+import org.keycloak.exportimport.util.ExportImportSessionTask;
 import org.keycloak.exportimport.util.ImportUtils;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.representations.idm.RealmRepresentation;
 import org.keycloak.util.JsonSerialization;
 
@@ -81,10 +82,10 @@ public class ZipImportProvider implements ImportProvider {
             ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
             final RealmRepresentation realmRep = JsonSerialization.mapper.readValue(bis, RealmRepresentation.class);
 
-            ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+            KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
                 @Override
-                public void run(KeycloakSession session) throws IOException {
+                protected void runExportImportTask(KeycloakSession session) throws IOException {
                     ImportUtils.importRealm(session, realmRep, strategy);
                 }
 
@@ -99,10 +100,10 @@ public class ZipImportProvider implements ImportProvider {
                     this.decrypter.extractEntry(entry, bos, this.password);
                     final ByteArrayInputStream bis2 = new ByteArrayInputStream(bos.toByteArray());
 
-                    ExportImportUtils.runJobInTransaction(factory, new ExportImportJob() {
+                    KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
 
                         @Override
-                        public void run(KeycloakSession session) throws IOException {
+                        protected void runExportImportTask(KeycloakSession session) throws IOException {
                             ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, bis2);
                         }
                     });
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
index fed574b..85a049f 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -292,4 +292,29 @@ public class LDAPFederationProvider implements UserFederationProvider {
     public void close() {
         //To change body of implemented methods use File | Settings | File Templates.
     }
+
+    protected void importPicketlinkUsers(RealmModel realm, List<User> users, UserFederationProviderModel fedModel) {
+        for (User picketlinkUser : users) {
+            String username = picketlinkUser.getLoginName();
+            UserModel currentUser = session.userStorage().getUserByUsername(username, realm);
+
+            if (currentUser == null) {
+                // Add new user to Keycloak
+                importUserFromPicketlink(realm, picketlinkUser);
+                logger.infof("Added new user from LDAP: " + username);
+            } else {
+                if ((fedModel.getId().equals(currentUser.getFederationLink())) && (picketlinkUser.getId().equals(currentUser.getAttribute(LDAPFederationProvider.LDAP_ID)))) {
+                    // Update keycloak user
+                    String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+                    currentUser.setEmail(email);
+                    currentUser.setFirstName(picketlinkUser.getFirstName());
+                    currentUser.setLastName(picketlinkUser.getLastName());
+                    logger.infof("Updated user from LDAP: " + currentUser.getUsername());
+                } else {
+                    // TODO: We have local user of same username like LDAP user, but not linked. What to do? Delete him and import again?
+                    throw new IllegalStateException("User " + username + " has invalid LDAP ID or doesn't have federation link");
+                }
+            }
+        }
+    }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
index 53aeb0c..88bf66f 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -1,14 +1,26 @@
 package org.keycloak.federation.ldap;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 import org.keycloak.models.UserFederationProviderModel;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
 import org.keycloak.picketlink.PartitionManagerProvider;
+import org.picketlink.idm.IdentityManager;
 import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.model.IdentityType;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.query.IdentityQuery;
 
 import java.util.Collections;
+import java.util.Date;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -17,6 +29,7 @@ import java.util.Set;
  * @version $Revision: 1 $
  */
 public class LDAPFederationProviderFactory implements UserFederationProviderFactory {
+    private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
     public static final String PROVIDER_NAME = "ldap";
 
     @Override
@@ -25,7 +38,7 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
     }
 
     @Override
-    public UserFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
+    public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
         PartitionManagerProvider idmProvider = session.getProvider(PartitionManagerProvider.class);
         PartitionManager partition = idmProvider.getPartitionManager(model);
         return new LDAPFederationProvider(session, model, partition);
@@ -49,4 +62,76 @@ public class LDAPFederationProviderFactory implements UserFederationProviderFact
     public Set<String> getConfigurationOptions() {
         return Collections.emptySet();
     }
+
+    @Override
+    public void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model) {
+        logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date(), realmId, model.getDisplayName());
+
+        PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
+        PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
+        IdentityQuery<User> userQuery = partitionMgr.createIdentityManager().createIdentityQuery(User.class);
+        syncImpl(sessionFactory, userQuery, realmId, model);
+
+        // TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
+    }
+
+    @Override
+    public void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
+        logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, current time: " + new Date() + ", last sync time: " + lastSync, realmId, model.getDisplayName());
+
+        PartitionManagerProvider idmProvider = sessionFactory.create().getProvider(PartitionManagerProvider.class);
+        PartitionManager partitionMgr = idmProvider.getPartitionManager(model);
+
+        // Sync newly created users
+        IdentityManager identityManager = partitionMgr.createIdentityManager();
+        IdentityQuery<User> userQuery = identityManager.createIdentityQuery(User.class)
+                .setParameter(IdentityType.CREATED_AFTER, lastSync);
+        syncImpl(sessionFactory, userQuery, realmId, model);
+
+        // Sync updated users
+        userQuery = identityManager.createIdentityQuery(User.class)
+                .setParameter(IdentityType.MODIFIED_AFTER, lastSync);
+        syncImpl(sessionFactory, userQuery, realmId, model);
+    }
+
+    protected void syncImpl(KeycloakSessionFactory sessionFactory, IdentityQuery<User> userQuery, final String realmId, final UserFederationProviderModel fedModel) {
+        boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
+
+        if (pagination) {
+            String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
+            int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
+            boolean nextPage = true;
+            while (nextPage) {
+                userQuery.setLimit(pageSize);
+                final List<User> users = userQuery.getResultList();
+                nextPage = userQuery.getPaginationContext() != null;
+
+                KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+                    @Override
+                    public void run(KeycloakSession session) {
+                        importPicketlinkUsers(session, realmId, fedModel, users);
+                    }
+
+                });
+            }
+        } else {
+            // LDAP pagination not available. Do everything in single transaction
+            final List<User> users = userQuery.getResultList();
+            KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
+
+                @Override
+                public void run(KeycloakSession session) {
+                    importPicketlinkUsers(session, realmId, fedModel, users);
+                }
+
+            });
+        }
+    }
+
+    protected void importPicketlinkUsers(KeycloakSession session, String realmId, UserFederationProviderModel fedModel, List<User> users) {
+        RealmModel realm = session.realms().getRealm(realmId);
+        LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
+        ldapFedProvider.importPicketlinkUsers(realm, users, fedModel);
+    }
 }
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
index d67c499..f862d03 100755
--- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java
@@ -29,6 +29,17 @@ public class LDAPUtils {
         return picketlinkUser;
     }
 
+    public static User updateUser(PartitionManager partitionManager, String username, String firstName, String lastName, String email) {
+        IdentityManager idmManager = getIdentityManager(partitionManager);
+        User picketlinkUser = BasicModel.getUser(idmManager, username);
+        picketlinkUser.setFirstName(firstName);
+        picketlinkUser.setLastName(lastName);
+        picketlinkUser.setEmail(email);
+        picketlinkUser.setAttribute(new Attribute("fullName", getFullName(username, firstName, lastName)));
+        idmManager.update(picketlinkUser);
+        return picketlinkUser;
+    }
+
     public static void updatePassword(PartitionManager partitionManager, User picketlinkUser, String password) {
         IdentityManager idmManager = getIdentityManager(partitionManager);
         idmManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
@@ -48,10 +59,6 @@ public class LDAPUtils {
         }
     }
 
-    public static boolean isUserExists(PartitionManager partitionManager, String username) {
-        return getUser(partitionManager, username) != null;
-    }
-
     public static User getUser(PartitionManager partitionManager, String username) {
         IdentityManager idmManager = getIdentityManager(partitionManager);
         return BasicModel.getUser(idmManager, username);
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/LDAPConstants.java b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
index 1328c01..38b538e 100644
--- a/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/LDAPConstants.java
@@ -22,5 +22,9 @@ public class LDAPConstants {
     public static final String CONNECTION_POOLING = "connectionPooling";
     public static final String PAGINATION = "pagination";
 
+    // Count of users processed per single transaction during sync process
+    public static final String BATCH_SIZE_FOR_SYNC = "batchSizeForSync";
+    public static final int DEFAULT_BATCH_SIZE_FOR_SYNC = 1000;
+
     public static final String USER_ACCOUNT_CONTROLS_AFTER_PASSWORD_UPDATE = "userAccountControlsAfterPasswordUpdate";
 }
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/UserFederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
index 956779f..4247649 100755
--- a/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/UserFederationProviderFactory.java
@@ -2,6 +2,7 @@ package org.keycloak.models;
 
 import org.keycloak.provider.ProviderFactory;
 
+import java.util.Date;
 import java.util.Set;
 
 /**
@@ -32,4 +33,26 @@ public interface UserFederationProviderFactory extends ProviderFactory<UserFeder
      */
     @Override
     String getId();
+
+    /**
+     * Sync all users from the provider storage to Keycloak storage.
+     *
+     * @param sessionFactory
+     * @param realmId
+     * @param model
+     */
+    void syncAllUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model);
+
+    /**
+     * Sync just changed (added / updated / removed) users from the provider storage to Keycloak storage. This is useful in case
+     * that your storage supports "changelogs" (Tracking what users changed since specified date). It's implementation specific to
+     * decide what exactly will be changed (For example LDAP supports tracking of added / updated users, but not removed users. So
+     * removed users are not synced)
+     *
+     * @param sessionFactory
+     * @param realmId
+     * @param model
+     * @param lastSync
+     */
+    void syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync);
 }
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/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index ca07fdb..2f329b7 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -5,6 +5,9 @@ import org.keycloak.models.ApplicationModel;
 import org.keycloak.models.ClaimMask;
 import org.keycloak.models.ClientModel;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.KeycloakSessionTask;
+import org.keycloak.models.KeycloakTransaction;
 import org.keycloak.models.RealmModel;
 import org.keycloak.models.RoleModel;
 import org.keycloak.models.UserCredentialModel;
@@ -132,4 +135,36 @@ public final class KeycloakModelUtils {
         }
         return user;
     }
+
+    /**
+     * Wrap given runnable job into KeycloakTransaction.
+     *
+     * @param factory
+     * @param task
+     */
+    public static void runJobInTransaction(KeycloakSessionFactory factory, KeycloakSessionTask task) {
+        KeycloakSession session = factory.create();
+        KeycloakTransaction tx = session.getTransaction();
+        try {
+            tx.begin();
+            task.run(session);
+
+            if (tx.isActive()) {
+                if (tx.getRollbackOnly()) {
+                    tx.rollback();
+                } else {
+                    tx.commit();
+                }
+            }
+        } finally {
+            if (tx.isActive()) {
+                tx.rollback();
+            }
+            session.close();
+        }
+    }
+
+    public static String getMasterRealmAdminApplicationName(RealmModel realm) {
+        return realm.getName() + "-realm";
+    }
 }
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/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
index ec1ba58..6fc5237 100755
--- a/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
+++ b/picketlink/keycloak-picketlink-ldap/src/main/java/org/keycloak/picketlink/ldap/PartitionManagerRegistry.java
@@ -11,13 +11,17 @@ import org.picketlink.idm.config.LDAPMappingConfigurationBuilder;
 import org.picketlink.idm.config.LDAPStoreConfigurationBuilder;
 import org.picketlink.idm.internal.DefaultPartitionManager;
 import org.picketlink.idm.model.basic.User;
-
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 
-import static org.picketlink.common.constants.LDAPConstants.*;
+import static org.picketlink.common.constants.LDAPConstants.CN;
+import static org.picketlink.common.constants.LDAPConstants.EMAIL;
+import static org.picketlink.common.constants.LDAPConstants.SN;
+import static org.picketlink.common.constants.LDAPConstants.UID;
+import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
+import static org.picketlink.common.constants.LDAPConstants.MODIFY_TIMESTAMP;
 
 /**
  * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@@ -80,6 +84,8 @@ public class PartitionManagerRegistry {
         }
 
         String ldapFirstNameMapping = activeDirectory ?  "givenName" : CN;
+        String createTimestampMapping = activeDirectory ? "whenCreated" : CREATE_TIMESTAMP;
+        String modifyTimestampMapping = activeDirectory ? "whenChanged" : MODIFY_TIMESTAMP;
         String[] userObjectClasses = getUserObjectClasses(ldapConfig);
 
         boolean pagination = ldapConfig.containsKey(LDAPConstants.PAGINATION) ? Boolean.parseBoolean(ldapConfig.get(LDAPConstants.PAGINATION)) : false;
@@ -112,7 +118,9 @@ public class PartitionManagerRegistry {
                 .attribute("loginName", ldapLoginNameMapping, true)
                 .attribute("firstName", ldapFirstNameMapping)
                 .attribute("lastName", SN)
-                .attribute("email", EMAIL);
+                .attribute("email", EMAIL)
+                .readOnlyAttribute("createdDate", createTimestampMapping)
+                .readOnlyAttribute("modifyDate", modifyTimestampMapping);
 
         if (activeDirectory && ldapLoginNameMapping.equals("sAMAccountName")) {
             ldapUserMappingBuilder.bindingAttribute("fullName", CN);
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 77da618..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() {
@@ -146,8 +146,8 @@ public class KeycloakApplication extends Application {
         long interval = Config.scope("scheduled").getLong("interval", 60L) * 1000;
 
         TimerProvider timer = sessionFactory.create().getProvider(TimerProvider.class);
-        timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredAuditEvents()), interval);
-        timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval);
+        timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredAuditEvents()), interval, "ClearExpiredAuditEvents");
+        timer.schedule(new ScheduledTaskRunner(sessionFactory, new ClearExpiredUserSessions()), interval, "ClearExpiredUserSessions");
     }
 
     public KeycloakSessionFactory getSessionFactory() {
diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
index 04ac0a1..753409a 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java
@@ -38,7 +38,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
 
     @Override
     public boolean removeUser(RealmModel realm, UserModel user) {
-        return true;
+        return users.remove(user.getUsername()) != null;
     }
 
     @Override
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 a5a0b6e..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,19 +1,29 @@
 package org.keycloak.testutils;
 
+import org.jboss.logging.Logger;
 import org.keycloak.Config;
 import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
 import org.keycloak.models.UserFederationProvider;
 import org.keycloak.models.UserFederationProviderFactory;
 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();
@@ -43,6 +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/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java b/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java
index 466952a..1c10b24 100755
--- a/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java
+++ b/testsuite/integration/src/main/java/org/keycloak/testutils/LDAPEmbeddedServer.java
@@ -42,6 +42,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
     protected String vendor = LDAPConstants.VENDOR_OTHER;
     protected boolean connectionPooling = true;
     protected boolean pagination = true;
+    protected int batchSizeForSync = LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
 
     public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url";
     public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn";
@@ -55,6 +56,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
     public static String IDM_TEST_LDAP_VENDOR = "idm.test.ldap.vendor";
     public static String IDM_TEST_LDAP_CONNECTION_POOLING = "idm.test.ldap.connection.pooling";
     public static String IDM_TEST_LDAP_PAGINATION = "idm.test.ldap.pagination";
+    public static String IDM_TEST_LDAP_BATCH_SIZE_FOR_SYNC = "idm.test.ldap.batch.size.for.sync";
 
 
     public LDAPEmbeddedServer() {
@@ -84,6 +86,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
         vendor = p.getProperty(IDM_TEST_LDAP_VENDOR);
         connectionPooling = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_CONNECTION_POOLING, "true"));
         pagination = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_PAGINATION, "true"));
+        batchSizeForSync = Integer.parseInt(p.getProperty(IDM_TEST_LDAP_BATCH_SIZE_FOR_SYNC, String.valueOf(batchSizeForSync)));
     }
 
     @Override
@@ -134,6 +137,7 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
         ldapConfig.put(LDAPConstants.VENDOR, getVendor());
         ldapConfig.put(LDAPConstants.CONNECTION_POOLING, String.valueOf(isConnectionPooling()));
         ldapConfig.put(LDAPConstants.PAGINATION, String.valueOf(isPagination()));
+        ldapConfig.put(LDAPConstants.BATCH_SIZE_FOR_SYNC, String.valueOf(getBatchSizeForSync()));
         return ldapConfig;
     }
 
@@ -216,6 +220,10 @@ public class LDAPEmbeddedServer extends AbstractLDAPTest {
         return pagination;
     }
 
+    public int getBatchSizeForSync() {
+        return batchSizeForSync;
+    }
+
     @Override
     public void importLDIF(String fileName) throws Exception {
         // import LDIF only in case we are running against embedded LDAP server
diff --git a/testsuite/integration/src/main/resources/ldap/ldap-connection.properties b/testsuite/integration/src/main/resources/ldap/ldap-connection.properties
index 6119852..c275c2f 100644
--- a/testsuite/integration/src/main/resources/ldap/ldap-connection.properties
+++ b/testsuite/integration/src/main/resources/ldap/ldap-connection.properties
@@ -8,4 +8,5 @@ idm.test.ldap.start.embedded.ldap.server=true
 idm.test.ldap.bind.dn=uid\=admin,ou\=system
 idm.test.ldap.bind.credential=secret
 idm.test.ldap.connection.pooling=true
-idm.test.ldap.pagination=true
\ No newline at end of file
+idm.test.ldap.pagination=true
+idm.test.ldap.batch.size.for.sync=3
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 791e686..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);
@@ -102,7 +102,7 @@ public class FederationProvidersIntegrationTest {
     @WebResource
     protected AccountPasswordPage changePasswordPage;
 
-    private static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
+    static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
         UserModel user = session.users().addUser(realm, username);
         user.setEmail(email);
         user.setEnabled(true);
@@ -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);
@@ -313,7 +315,7 @@ public class FederationProvidersIntegrationTest {
         }
     }
 
-    private static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
+    static PartitionManager getPartitionManager(KeycloakSession keycloakSession, UserFederationProviderModel ldapFedModel) {
         PartitionManagerProvider partitionManagerProvider = keycloakSession.getProvider(PartitionManagerProvider.class);
         return partitionManagerProvider.getPartitionManager(ldapFedModel);
     }
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
new file mode 100644
index 0000000..21ac290
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/SyncProvidersTest.java
@@ -0,0 +1,186 @@
+package org.keycloak.testsuite.forms;
+
+import java.util.Date;
+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.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;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserFederationProvider;
+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;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+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() {
+
+        @Override
+        public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+            LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
+            Map<String,String> ldapConfig = ldapServer.getLDAPConfig();
+            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",
+                    -1, -1, 0);
+
+            // Delete all LDAP users and add 5 new users for testing
+            PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(manager.getSession(), ldapModel);
+            LDAPUtils.removeAllUsers(partitionManager);
+
+            User user1 = LDAPUtils.addUser(partitionManager, "user1", "User1FN", "User1LN", "user1@email.org");
+            LDAPUtils.updatePassword(partitionManager, user1, "password1");
+            User user2 = LDAPUtils.addUser(partitionManager, "user2", "User2FN", "User2LN", "user2@email.org");
+            LDAPUtils.updatePassword(partitionManager, user2, "password2");
+            User user3 = LDAPUtils.addUser(partitionManager, "user3", "User3FN", "User3LN", "user3@email.org");
+            LDAPUtils.updatePassword(partitionManager, user3, "password3");
+            User user4 = LDAPUtils.addUser(partitionManager, "user4", "User4FN", "User4LN", "user4@email.org");
+            LDAPUtils.updatePassword(partitionManager, user4, "password4");
+            User user5 = LDAPUtils.addUser(partitionManager, "user5", "User5FN", "User5LN", "user5@email.org");
+            LDAPUtils.updatePassword(partitionManager, user5, "password5");
+
+            // Add properties provider
+            dummyModel = appRealm.addUserFederationProvider(DummyUserFederationProviderFactory.PROVIDER_NAME, new HashMap<String, String>(), 1, "test-dummy", -1, 1, 0);
+        }
+    });
+
+    @ClassRule
+    public static TestRule chain = RuleChain
+            .outerRule(ldapRule)
+            .around(keycloakRule);
+
+    @Test
+    public void testLDAPSync() {
+        KeycloakSession session = keycloakRule.startSession();
+        try {
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            UserFederationProviderFactory ldapFedFactory = (UserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, LDAPFederationProviderFactory.PROVIDER_NAME);
+            ldapFedFactory.syncAllUsers(sessionFactory, "test", ldapModel);
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+
+        // Assert users imported (model test)
+        session = keycloakRule.startSession();
+        try {
+            RealmModel testRealm = session.realms().getRealm("test");
+            UserProvider userProvider = session.userStorage();
+            assertUserImported(userProvider, testRealm, "user1", "User1FN", "User1LN", "user1@email.org");
+            assertUserImported(userProvider, testRealm, "user2", "User2FN", "User2LN", "user2@email.org");
+            assertUserImported(userProvider, testRealm, "user3", "User3FN", "User3LN", "user3@email.org");
+            assertUserImported(userProvider, testRealm, "user4", "User4FN", "User4LN", "user4@email.org");
+            assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
+
+            // wait a bit
+            sleep(1000);
+            Date beforeLDAPUpdate = new Date();
+
+            // Add user to LDAP and update 'user5' in LDAP
+            PartitionManager partitionManager = FederationProvidersIntegrationTest.getPartitionManager(session, ldapModel);
+            LDAPUtils.addUser(partitionManager, "user6", "User6FN", "User6LN", "user6@email.org");
+            LDAPUtils.updateUser(partitionManager, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
+
+            // Assert still old users in local provider
+            assertUserImported(userProvider, testRealm, "user5", "User5FN", "User5LN", "user5@email.org");
+            Assert.assertNull(userProvider.getUserByUsername("user6", testRealm));
+
+            // Trigger partial sync
+            KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory();
+            UserFederationProviderFactory ldapFedFactory = (UserFederationProviderFactory)sessionFactory.getProviderFactory(UserFederationProvider.class, LDAPFederationProviderFactory.PROVIDER_NAME);
+            ldapFedFactory.syncChangedUsers(sessionFactory, "test", ldapModel, beforeLDAPUpdate);
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+
+        session = keycloakRule.startSession();
+        try {
+            RealmModel testRealm = session.realms().getRealm("test");
+            UserProvider userProvider = session.userStorage();
+            // Assert users updated in local provider
+            assertUserImported(userProvider, testRealm, "user5", "User5FNUpdated", "User5LNUpdated", "user5Updated@email.org");
+            assertUserImported(userProvider, testRealm, "user6", "User6FN", "User6LN", "user6@email.org");
+        } finally {
+            keycloakRule.stopSession(session, false);
+        }
+    }
+
+    @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);
+        Assert.assertEquals(expectedFirstName, user.getFirstName());
+        Assert.assertEquals(expectedLastName, user.getLastName());
+        Assert.assertEquals(expectedEmail, user.getEmail());
+    }
+}
diff --git a/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java b/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java
index 627f00e..a8396de 100644
--- a/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java
+++ b/timer/api/src/main/java/org/keycloak/timer/TimerProvider.java
@@ -7,6 +7,8 @@ import org.keycloak.provider.Provider;
  */
 public interface TimerProvider extends Provider {
 
-    public void schedule(Runnable runnable, long interval);
+    public void schedule(Runnable runnable, long interval, String taskName);
+
+    public void cancelTask(String taskName);
 
 }
diff --git a/timer/basic/pom.xml b/timer/basic/pom.xml
index 268c76a..658e4c5 100755
--- a/timer/basic/pom.xml
+++ b/timer/basic/pom.xml
@@ -31,6 +31,11 @@
             <version>${project.version}</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java
index 267f17c..aa1a3f9 100644
--- a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java
+++ b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProvider.java
@@ -1,5 +1,6 @@
 package org.keycloak.timer.basic;
 
+import org.jboss.logging.Logger;
 import org.keycloak.timer.TimerProvider;
 
 import java.util.Timer;
@@ -10,15 +11,18 @@ import java.util.TimerTask;
  */
 public class BasicTimerProvider implements TimerProvider {
 
-    private Timer timer;
+    private static final Logger logger = Logger.getLogger(BasicTimerProvider.class);
 
-    public BasicTimerProvider(Timer timer) {
+    private final Timer timer;
+    private final BasicTimerProviderFactory factory;
 
+    public BasicTimerProvider(Timer timer, BasicTimerProviderFactory factory) {
         this.timer = timer;
+        this.factory = factory;
     }
 
     @Override
-    public void schedule(final Runnable runnable, final long interval) {
+    public void schedule(final Runnable runnable, final long interval, String taskName) {
         TimerTask task = new TimerTask() {
             @Override
             public void run() {
@@ -26,10 +30,26 @@ public class BasicTimerProvider implements TimerProvider {
             }
         };
 
+        TimerTask existingTask = factory.putTask(taskName, task);
+        if (existingTask != null) {
+            logger.infof("Existing timer task '%s' found. Cancelling it", taskName);
+            existingTask.cancel();
+        }
+
+        logger.infof("Starting task '%s' with interval '%d'", taskName, interval);
         timer.schedule(task, interval, interval);
     }
 
     @Override
+    public void cancelTask(String taskName) {
+        TimerTask existingTask = factory.removeTask(taskName);
+        if (existingTask != null) {
+            logger.infof("Cancelling task '%s'", taskName);
+            existingTask.cancel();
+        }
+    }
+
+    @Override
     public void close() {
         // do nothing
     }
diff --git a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java
index b0e5063..0069b2c 100644
--- a/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java
+++ b/timer/basic/src/main/java/org/keycloak/timer/basic/BasicTimerProviderFactory.java
@@ -6,6 +6,9 @@ import org.keycloak.timer.TimerProvider;
 import org.keycloak.timer.TimerProviderFactory;
 
 import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 
 /**
  * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
@@ -14,9 +17,11 @@ public class BasicTimerProviderFactory implements TimerProviderFactory {
 
     private Timer timer;
 
+    private ConcurrentMap<String, TimerTask> scheduledTasks = new ConcurrentHashMap<String, TimerTask>();
+
     @Override
     public TimerProvider create(KeycloakSession session) {
-        return new BasicTimerProvider(timer);
+        return new BasicTimerProvider(timer, this);
     }
 
     @Override
@@ -35,4 +40,12 @@ public class BasicTimerProviderFactory implements TimerProviderFactory {
         return "basic";
     }
 
+    protected TimerTask putTask(String taskName, TimerTask task) {
+        return scheduledTasks.put(taskName, task);
+    }
+
+    protected TimerTask removeTask(String taskName) {
+        return scheduledTasks.remove(taskName);
+    }
+
 }