You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:26:03 UTC

[sling-org-apache-sling-discovery-commons] annotated tag org.apache.sling.discovery.commons-1.0.0 created (now 6bc1083)

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a change to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git.


      at 6bc1083  (tag)
 tagging 39d89c521c0aa210ac788f283ce1c60bda112f0b (commit)
      by Stefan Egli
      on Mon Oct 26 16:10:47 2015 +0000

- Log -----------------------------------------------------------------
org.apache.sling.discovery.commons-1.0.0
-----------------------------------------------------------------------

This annotated tag includes the following new commits:

     new b8fe30c  SLING-4665 : adding patch provided by Timothee Maret, many thanks
     new 7eadb24  svn:ignore updated
     new 5943189  SLING-4685 : adding initial version of ViewStateManager - a shared implementation of TopologyEventListener-handling and sending of events based on activate/deactivate/changing/newView triggers - intended for use by implementors of the discovery.api (not clients of it)
     new f438f9e  SLING-4698 - Set parent.relativePath to empty for all modules
     new 576bc0d  Update svn:ignore
     new f93b193  Update to Sling Parent 23
     new 0035b7d  set parent version to 24 and add empty relativePath where missing
     new e599fa8  Update the main reactor to parent 25
     new 9a31ba9  SLING-5131 : introducing ConsistencyService and an oak-discovery-lite based implementation of it - plus SLING-4697 : support for PROPERTIES_CHANGED added to ViewStateManagerImpl
     new b33eee0  SLING-5131 : re-adding ViewStateManager which for some reason got removed by svn in 1707548
     new bb3d20d  SLING-4697 : support for PROPERTIES_CHANGED in ViewStateManagerImpl tested and thus fixed
     new 0e44e3d  SLING-5173 : introducing discovery.base which is the sharable parts of discovery.impl for discovery.oak - eg it includes topology connectors and base classes - plus it also includes many it-kind tests of discovery.impl
     new 3d923b4  SLING-5173 : rename commons impl packages to base as they are meant for reuse by discovery.impl and discovery.oak - plus avoid using abstract component class with scr annotations - use abstract getters instead - plus some more fine-tuning of log messages - plus make discovery.impl's Config also implement DiscoveryLiteConfig - plus properly handle binds happening before activate in DiscoveryServiceImpl
     new 6dd90e1  SLING-5173 : adding refactored SyncTokenOnlyConsistencyService again
     new dd9150c  SLING-4603 related : some fixes in class structure / syncToken handling
     new cf6bb53  SLING-5173 related : EventFactory renamed to EventHelper - and introduced toShortString() for a few base view classes to shorten and make the log output more readable
     new dcfc44f  SLING-5173 and SLING-4603 related : more syncToken log.info - plus always doing the syncToken thingy, independent of whether any instance left or joined the cluster as otherwise this thing wont work
     new 81a52c9  SLING-5173 : log.info fix
     new 8b4c590  SLING-5094 / SLING-4603 related : cancel ongoing sync explicitly in order to avoid dangerous CHANGED event when changes happen during sync
     new f957528  SLING-5173 : reducing visibility of isDelaying
     new 732f4d6  SLING-5173 : reduce log in cancel when already done
     new e88458a  SLING-4603 : more aggressively clearing the idMap-cache to avoid stale entries on slingId change - plus added getSyncHistory to BaseSyncTokenConsistencyService to allow adding it to the webconsole for debug - plus some cleanup in webconsole wrt discoveryLite info
     new 36ac966  SLING-5173 : introducing a more explicit chain concept for ConsistencyServices than the previous hidden/implicit one: ConsistencyServiceChain
     new 1a36029  SLING-5173 : added simple implementation for findInstances
     new 8d22485  SLING-5173 : added getInstance(slingId) to simplify things
     new 6dd354e  SLING-5173 : introducing a more explicit chain concept for ConsistencyServices than the previous hidden/implicit one: ConsistencyServiceChain
     new df6d865  SLING-5173 : bugfix for leader changes: leader change was treated as a properties change - which was very bad - now it is properly treated as a TOPOLOGY_CHANGED. Note that leader change should not happen in an otherwise unchanged topology - but it can if one instance's discovery.oak bundle for example is restarted, thus getting a lower leaderElectionId. Thus discovery.commons must account for this
     new 3edba1a  SLING-5173 : re-added complete consistency-history after introducing splitting them and using the ConsistencyServiceChain. Probably should be refactored into something slightly nicer though
     new 05027b8  SLING-5173 : minor code cleanup (duplicate logger removed)
     new 2e5938f  SLING-4603 : even more aggressively clearing the idMap-cache to avoid stale entries : now registering an EventHandler that listens on /var/discovery/../idMap and clears the cache on any change therein
     new 11a225d  SLING-4603 : minor fix to previous commit : turns out the path must be /var/xy/idMap not /var/xy/idMap/* as that would apply to children only
     new 5a5f5ee  SLING-5094 / SLING-5173 / SLING-4603 related : ensure that before invoking the ConsistencyService.sync no async events are still in the queue. This is achieved by enqueueing an async event too that once it gets triggered ensures that no async events are left. This mechanism ensures that before the syncToken is written, all TopologyEventListeners have received a TOPOLOGY_CHANGING - and only that guarantees that the syncToken mechanism carries a high guarantee.
     new 26e1a7b  SLING-5094 / SLING-5173 / SLING-4603 related : ensure that before invoking the ConsistencyService.sync no async events are still in the queue. This is achieved by enqueueing an async event too that once it gets triggered ensures that no async events are left. This mechanism ensures that before the syncToken is written, all TopologyEventListeners have received a TOPOLOGY_CHANGING - and only that guarantees that the syncToken mechanism carries a high guarantee.
     new fc2a805  SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService - plus making timeout/interval values for the same configurable in discovery.oak
     new 032f500  SLING-5094 related : more test stability by adding a wait time of 2sec
     new e5ded77  SLING-5094 / SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService
     new fdd2176  [maven-release-plugin] prepare release org.apache.sling.discovery.commons-1.0.0
     new 39d89c5  [maven-release-plugin] copy for tag org.apache.sling.discovery.commons-1.0.0

The 38 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


-- 
To stop receiving notification emails like this one, please contact
['"commits@sling.apache.org" <co...@sling.apache.org>'].

[sling-org-apache-sling-discovery-commons] 04/38: SLING-4698 - Set parent.relativePath to empty for all modules

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit f438f9ed32b65cc40d33215e9bc5b10441c42242
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu May 7 10:14:40 2015 +0000

    SLING-4698 - Set parent.relativePath to empty for all modules
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1678154 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 7a9f48e..da3aac1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
         <version>22</version>
-        <relativePath>../../../../parent/pom.xml</relativePath>
+        <relativePath/>
     </parent>
 
     <artifactId>org.apache.sling.discovery.commons</artifactId>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 19/38: SLING-5094 / SLING-4603 related : cancel ongoing sync explicitly in order to avoid dangerous CHANGED event when changes happen during sync

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 8b4c590b7c709fb77757f20022ff69e9eba10e38
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 15:37:35 2015 +0000

    SLING-5094 / SLING-4603 related : cancel ongoing sync explicitly in order to avoid dangerous CHANGED event when changes happen during sync
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709866 13f79535-47bb-0310-9956-ffa450edef68
---
 .../providers/base/ViewStateManagerImpl.java       |  6 +++
 .../commons/providers/spi/ConsistencyService.java  |  2 +
 .../spi/base/BaseSyncTokenConsistencyService.java  |  5 ++
 .../commons/providers/base/ClusterTest.java        |  5 ++
 .../providers/base/TestMinEventDelayHandler.java   |  5 ++
 .../providers/base/TestViewStateManager.java       | 57 +++++++++++++++++++++-
 6 files changed, 79 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index f2c3525..70fa33f 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -414,6 +414,12 @@ public class ViewStateManagerImpl implements ViewStateManager {
         if (!localInstance.isLocal()) {
             throw new IllegalStateException("newView's local instance is not isLocal - very unexpected - hence cannot be current");
         }
+        
+        // cancel any potentially ongoing sync
+        if (consistencyService != null) {
+            consistencyService.cancelSync();
+        }
+        
         logger.debug("handleNewView: newView is current, so trying with minEventDelayHandler...");
         if (minEventDelayHandler!=null) {
             if (minEventDelayHandler.handlesNewView(newView)) {
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
index 4678657..0aec6a8 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
@@ -81,4 +81,6 @@ public interface ConsistencyService {
      */
     void sync(BaseTopologyView view, Runnable callback);
     
+    void cancelSync();
+    
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
index f37b7a2..fd53e82 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
@@ -52,6 +52,11 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
     }
     
     @Override
+    public void cancelSync() {
+        cancelPreviousBackgroundCheck();
+    }
+    
+    @Override
     public void sync(BaseTopologyView view, Runnable callback) {
         // cancel the previous background-check if it's still running
         cancelPreviousBackgroundCheck();
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
index 7b921dc..2bf3705 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
@@ -64,6 +64,11 @@ public class ClusterTest {
             public void sync(BaseTopologyView view, Runnable callback) {
                 callback.run();
             }
+            
+            @Override
+            public void cancelSync() {
+                // nothing to cancel, we're auto-run
+            }
         });
         mgrList.add(mgr);
         return mgr;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
index 84ec22f..d28b912 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
@@ -61,6 +61,11 @@ public class TestMinEventDelayHandler {
             public void sync(BaseTopologyView view, Runnable callback) {
                 callback.run();
             }
+            
+            @Override
+            public void cancelSync() {
+                // nothing to cancel, we're auto-run
+            }
         });
         defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
         
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index df90dfc..13d586d 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -22,6 +22,8 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.Semaphore;
@@ -36,7 +38,6 @@ import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.EventHelper;
-import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
 import org.junit.Before;
@@ -72,6 +73,11 @@ public class TestViewStateManager {
             }
         }
         
+        @Override
+        public void cancelSync() {
+            // TODO not implemented yet
+        }
+        
     }
     
     private ViewStateManagerImpl mgr;
@@ -85,6 +91,11 @@ public class TestViewStateManager {
             public void sync(BaseTopologyView view, Runnable callback) {
                 callback.run();
             }
+            
+            @Override
+            public void cancelSync() {
+                // nothing to cancel, we're auto-run
+            }
         });
         defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
     }
@@ -206,6 +217,50 @@ public class TestViewStateManager {
     }
     
     @Test
+    public void testCancelSync() throws Exception {
+        final List<Runnable> syncCallbacks = new LinkedList<Runnable>();
+        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+            
+            public void sync(BaseTopologyView view, Runnable callback) {
+                synchronized(syncCallbacks) {
+                    syncCallbacks.add(callback);
+                }
+            }
+            
+            @Override
+            public void cancelSync() {
+                synchronized(syncCallbacks) {
+                    syncCallbacks.clear();
+                }
+            }
+        });
+        mgr.handleActivated();
+        final DummyListener listener = new DummyListener();
+        mgr.bind(listener);
+        mgr.handleChanging();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertTrue(mgr.waitForAsyncEvents(1000));
+        TestHelper.assertNoEvents(listener);
+        synchronized(syncCallbacks) {
+            assertEquals(1, syncCallbacks.size());
+        }
+        String id1 = UUID.randomUUID().toString();
+        String id2 = UUID.randomUUID().toString();
+        final BaseTopologyView view2 = TestHelper.newView(true, id1, id1, id1, id2); 
+        mgr.handleNewView(view2);
+        assertTrue(mgr.waitForAsyncEvents(1000));
+        TestHelper.assertNoEvents(listener);
+        synchronized(syncCallbacks) {
+            assertEquals(1, syncCallbacks.size());
+            syncCallbacks.get(0).run();
+            syncCallbacks.clear();
+        }
+        assertTrue(mgr.waitForAsyncEvents(1000));
+        assertEvents(listener, EventHelper.newInitEvent(view2));
+    }
+    
+    @Test
     public void testActivateBindChangingChanged() throws Exception {
         final DummyListener listener = new DummyListener();
         // first activate

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 06/38: Update to Sling Parent 23

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit f93b193516b9be5a2accf1a0c29e2d4b0db89681
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Thu Jun 25 13:08:16 2015 +0000

    Update to Sling Parent 23
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1687500 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index da3aac1..3878ea6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>22</version>
+        <version>23</version>
         <relativePath/>
     </parent>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 30/38: SLING-4603 : even more aggressively clearing the idMap-cache to avoid stale entries : now registering an EventHandler that listens on /var/discovery/../idMap and clears the cache on any change therein

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 2e5938fec21a783060cfb5f0c0c90f76caa46f97
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 08:48:17 2015 +0000

    SLING-4603 : even more aggressively clearing the idMap-cache to avoid stale entries : now registering an EventHandler that listens on /var/discovery/../idMap and clears the cache on any change therein
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710144 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   8 ++
 .../commons/providers/spi/base/IdMapService.java   | 117 +++++++++++++++++++--
 2 files changed, 116 insertions(+), 9 deletions(-)

diff --git a/pom.xml b/pom.xml
index e395218..438bd24 100644
--- a/pom.xml
+++ b/pom.xml
@@ -133,6 +133,14 @@
         	<scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.core</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
             <groupId>org.apache.jackrabbit</groupId>
             <artifactId>oak-core</artifactId>
             <version>1.3.7</version>
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
index 12e8db7..1ec4583 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
@@ -18,14 +18,20 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
+import java.util.Dictionary;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Hashtable;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.SlingConstants;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
@@ -36,6 +42,12 @@ import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.commons.json.JSONException;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
 
 /**
  * The IdMapService is responsible for storing a slingId-clusterNodeId
@@ -43,8 +55,8 @@ import org.apache.sling.settings.SlingSettingsService;
  * do the same can map clusterNodeIds to slingIds (or vice-versa)
  */
 @Component(immediate = false)
-@Service(value = IdMapService.class)
-public class IdMapService extends AbstractServiceWithBackgroundCheck {
+@Service(value = { IdMapService.class })
+public class IdMapService extends AbstractServiceWithBackgroundCheck implements EventHandler {
 
     @Reference
     private ResourceResolverFactory resourceResolverFactory;
@@ -61,8 +73,15 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
 
     private long me;
 
+    private final Map<Integer, String> oldIdMapCache = new HashMap<Integer, String>();
     private final Map<Integer, String> idMapCache = new HashMap<Integer, String>();
 
+    private long lastCacheInvalidation = -1;
+
+    private BundleContext bundleContext;
+
+    private ServiceRegistration eventHandlerRegistration;
+    
     /** test-only constructor **/
     public static IdMapService testConstructor(
             DiscoveryLiteConfig commonsConfig,
@@ -72,12 +91,15 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
         service.commonsConfig = commonsConfig;
         service.settingsService = settingsService;
         service.resourceResolverFactory = resourceResolverFactory;
-        service.activate();
+        service.activate(null);
         return service;
     }
 
     @Activate
-    protected void activate() {
+    protected void activate(BundleContext bundleContext) {
+        this.bundleContext = bundleContext;
+        registerEventHandler();
+        
         startBackgroundCheck("IdMapService-initializer", new BackgroundCheck() {
             
             @Override
@@ -92,6 +114,32 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
         }, null, -1, 1000 /* = 1sec interval */);
     }
     
+    @Deactivate
+    protected void deactivate() {
+        if (eventHandlerRegistration != null) {
+            eventHandlerRegistration.unregister();
+            eventHandlerRegistration = null;
+        }
+    }
+    
+    private void registerEventHandler() {
+        if (bundleContext == null) {
+            logger.info("registerEventHandler: bundleContext is null - cannot register");
+            return;
+        }
+        Dictionary<String,Object> properties = new Hashtable<String,Object>();
+        properties.put(Constants.SERVICE_DESCRIPTION, "IdMap Change Listener.");
+        String[] topics = new String[] {
+                SlingConstants.TOPIC_RESOURCE_ADDED,
+                SlingConstants.TOPIC_RESOURCE_CHANGED,
+                SlingConstants.TOPIC_RESOURCE_REMOVED };
+        properties.put(EventConstants.EVENT_TOPIC, topics);
+        String path = getIdMapPath().endsWith("/") ? getIdMapPath() + "*" : getIdMapPath() + "/*";
+        properties.put(EventConstants.EVENT_FILTER, "(&(path="+path+"))");
+        eventHandlerRegistration = bundleContext.registerService(
+                EventHandler.class.getName(), this, properties);
+    }
+
     /** Get or create a ResourceResolver **/
     private ResourceResolver getResourceResolver() throws LoginException {
         return resourceResolverFactory.getAdministrativeResourceResolver(null);
@@ -194,20 +242,55 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
     }
     
     public synchronized void clearCache() {
-        logger.info("clearCache: clearing idmap cache");
-        idMapCache.clear();
+        if (!idMapCache.isEmpty()) {
+            logger.debug("clearCache: clearing idmap cache");
+            oldIdMapCache.clear();
+            oldIdMapCache.putAll(idMapCache);
+            idMapCache.clear();
+        } else {
+            logger.debug("clearCache: cache was already emtpy");
+        }
+        lastCacheInvalidation = System.currentTimeMillis();
     }
 
     public synchronized String toSlingId(int clusterNodeId, ResourceResolver resourceResolver) throws PersistenceException {
+        if (System.currentTimeMillis() - lastCacheInvalidation > 30000) {
+            // since upon a restart of an instance it could opt to have changed
+            // the slingId, we might not be able to catch that change if we
+            // noticed the view change before that (the view change would
+            // force a cache invalidation).
+            // we can either rely on observation - or combine that with
+            // an invalidation of once per minute
+            // (note that this means we'll be reading 
+            // /var/discovery/oak/idMap once per minute - but that sounds
+            // perfectly fine)
+            clearCache();
+        }
         String slingId = idMapCache.get(clusterNodeId);
         if (slingId!=null) {
             // cache-hit
             return slingId;
         }
         // cache-miss
+        logger.debug("toSlingId: cache miss, refreshing idmap cache");
         Map<Integer, String> readMap = readIdMap(resourceResolver);
-        logger.info("toSlingId: cache miss, refreshing idmap cache");
-        idMapCache.putAll(readMap);
+        Set<Entry<Integer, String>> newEntries = readMap.entrySet();
+        for (Entry<Integer, String> newEntry : newEntries) {
+            String oldValue = oldIdMapCache.get(newEntry.getKey());
+            if (oldValue == null || !oldValue.equals(newEntry.getValue())) {
+                logger.info("toSlingId: mapping for "+newEntry.getKey()+" to "+newEntry.getValue() + " was newly added.");
+            } else if (!oldValue.equals(newEntry.getValue())) {
+                logger.info("toSlingId: mapping for "+newEntry.getKey()+" changed from "+oldValue+" to "+newEntry.getValue());
+            }
+            idMapCache.put(newEntry.getKey(), newEntry.getValue());
+        }
+        Set<Entry<Integer, String>> oldEntries = oldIdMapCache.entrySet();
+        for (Entry<Integer, String> oldEntry : oldEntries) {
+            if (!idMapCache.containsKey(oldEntry.getKey())) {
+                logger.info("toSlingId: mapping for "+oldEntry.getKey()+" to "+oldEntry.getValue()+" disappeared.");
+            }
+        }
+        
         return idMapCache.get(clusterNodeId);
     }
     
@@ -229,4 +312,20 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
         return commonsConfig.getIdMapPath();
     }
 
-}
+    @Override
+    public void handleEvent(Event event) {
+        final String resourcePath = (String) event.getProperty("path");
+        if (resourcePath == null) {
+            // not of my business
+            return;
+        }
+        
+        if (!resourcePath.startsWith(getIdMapPath())) {
+            // not of my business
+            return;
+        }
+        logger.debug("handleEvent: got event for path: {}, event: {}", resourcePath, event);
+        clearCache();
+    }
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 23/38: SLING-5173 : introducing a more explicit chain concept for ConsistencyServices than the previous hidden/implicit one: ConsistencyServiceChain

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 36ac9663903dee76f89f7472f5b1287db61f3344
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 15:31:45 2015 +0000

    SLING-5173 : introducing a more explicit chain concept for ConsistencyServices than the previous hidden/implicit one: ConsistencyServiceChain
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710035 13f79535-47bb-0310-9956-ffa450edef68
---
 .../base/AbstractServiceWithBackgroundCheck.java   |  65 ++++++++++
 .../spi/base/ConsistencyServiceChain.java          |  82 +++++++++++++
 ...vice.java => OakBacklogConsistencyService.java} |  47 ++++----
 ...rvice.java => SyncTokenConsistencyService.java} | 131 ++++++++++-----------
 .../spi/base/SyncTokenOnlyConsistencyService.java  | 101 ----------------
 5 files changed, 229 insertions(+), 197 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
index 8ccae9f..3e8b93b 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
@@ -18,6 +18,17 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -28,8 +39,21 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class AbstractServiceWithBackgroundCheck {
 
+    class HistoryEntry {
+        BaseTopologyView view;
+        String msg;
+        String fullLine;
+    }
+    
+    /** the date format used in the truncated log of topology events **/
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+
     protected final Logger logger = LoggerFactory.getLogger(getClass());
 
+    protected String slingId;
+
+    protected List<HistoryEntry> history = new LinkedList<HistoryEntry>();
+    
     /**
      * The BackgroundCheckRunnable implements the details of
      * calling BackgroundCheck.check and looping until it 
@@ -215,4 +239,45 @@ public abstract class AbstractServiceWithBackgroundCheck {
             backgroundOp.triggerCheck();
         }
     }
+    
+    public List<String> getSyncHistory() {
+        List<HistoryEntry> snapshot;
+        synchronized(history) {
+            snapshot = Collections.unmodifiableList(history);
+        }
+        List<String> result = new ArrayList<String>(snapshot.size());
+        for (HistoryEntry historyEntry : snapshot) {
+            result.add(historyEntry.fullLine);
+        }
+        return result;
+    }
+
+    protected void addHistoryEntry(BaseTopologyView view, String msg) {
+        synchronized(history) {
+            for(int i = history.size() - 1; i>=0; i--) {
+                HistoryEntry entry = history.get(i);
+                if (!entry.view.equals(view)) {
+                    // don't filter if the view starts differing,
+                    // only filter for the last few entries where
+                    // the view is equal
+                    break;
+                }
+                if (entry.msg.equals(msg)) {
+                    // if the view is equal and the msg matches
+                    // then this is a duplicate entry, so ignore
+                    return;
+                }
+            }
+            String fullLine = sdf.format(Calendar.getInstance().getTime()) + ": " + msg;
+            HistoryEntry newEntry = new HistoryEntry();
+            newEntry.view = view;
+            newEntry.fullLine = fullLine;
+            newEntry.msg = msg;
+            history.add(newEntry);
+            while (history.size() > 12) {
+                history.remove(0);
+            }
+        }
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyServiceChain.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyServiceChain.java
new file mode 100644
index 0000000..7695fe6
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyServiceChain.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.base;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Allows chaining of ConsistencyServices, itself implementing
+ * the ConsistencyService interface
+ */
+public class ConsistencyServiceChain implements ConsistencyService {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private final List<ConsistencyService> chain;
+
+    /**
+     * Creates a new chain of ConsistencyServices that calls a
+     * cascaded sync with the provided ConsistencyServices.
+     */
+    public ConsistencyServiceChain(ConsistencyService... chain) {
+        if (chain==null || chain.length==0) {
+            throw new IllegalArgumentException("chain must be 1 or more");
+        }
+        this.chain = Arrays.asList(chain);
+    }
+    
+    @Override
+    public void sync(BaseTopologyView view, Runnable callback) {
+        final Iterator<ConsistencyService> chainIt = chain.iterator();
+        chainedSync(view, callback, chainIt);
+    }
+
+    private void chainedSync(final BaseTopologyView view, final Runnable callback, 
+            final Iterator<ConsistencyService> chainIt) {
+        if (!chainIt.hasNext()) {
+            logger.debug("doSync: done with sync chain, invoking callback");
+            callback.run();
+            return;
+        }
+        ConsistencyService next = chainIt.next();
+        next.sync(view, new Runnable() {
+
+            @Override
+            public void run() {
+                chainedSync(view, callback, chainIt);
+            }
+            
+        });
+    }
+
+    @Override
+    public void cancelSync() {
+        for (ConsistencyService consistencyService : chain) {
+            consistencyService.cancelSync();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
similarity index 89%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
index 55c3411..48eb477 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
@@ -36,13 +36,12 @@ import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.apache.sling.settings.SlingSettingsService;
 
 /**
- * Inherits the 'sync-token' part from the SyncTokenConsistencyService
- * and adds the 'wait while backlog' part to it, based on
- * the Oak discovery-lite descriptor.
+ * The OakBacklogConsistencyService will wait until all instances
+ * in the local cluster are no longer in any backlog state.
  */
 @Component(immediate = false)
-@Service(value = { ConsistencyService.class, OakSyncTokenConsistencyService.class })
-public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyService {
+@Service(value = { ConsistencyService.class, OakBacklogConsistencyService.class })
+public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
     static enum BacklogStatus {
         UNDEFINED /* when there was an error retrieving the backlog status with oak */,
@@ -62,12 +61,12 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
     @Reference
     protected SlingSettingsService settingsService;
     
-    public static OakSyncTokenConsistencyService testConstructorAndActivate(
+    public static OakBacklogConsistencyService testConstructorAndActivate(
             final DiscoveryLiteConfig commonsConfig,
             final IdMapService idMapService,
             final SlingSettingsService settingsService,
             ResourceResolverFactory resourceResolverFactory) {
-        OakSyncTokenConsistencyService service = testConstructor(commonsConfig, idMapService, settingsService, resourceResolverFactory);
+        OakBacklogConsistencyService service = testConstructor(commonsConfig, idMapService, settingsService, resourceResolverFactory);
         service.activate();
         return service;
     }
@@ -84,12 +83,12 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
      * @throws LoginException when the login for initialization failed
      * @throws JSONException when the descriptor wasn't proper json at init time
      */
-    public static OakSyncTokenConsistencyService testConstructor(
+    public static OakBacklogConsistencyService testConstructor(
             final DiscoveryLiteConfig commonsConfig,
             final IdMapService idMapService,
             final SlingSettingsService settingsService,
             ResourceResolverFactory resourceResolverFactory) {
-        OakSyncTokenConsistencyService service = new OakSyncTokenConsistencyService();
+        OakBacklogConsistencyService service = new OakBacklogConsistencyService();
         if (commonsConfig == null) {
             throw new IllegalArgumentException("commonsConfig must not be null");
         }
@@ -112,6 +111,16 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
         logger.info("activate: activated with slingId="+slingId);
     }
     
+    /** Get or create a ResourceResolver **/
+    protected ResourceResolver getResourceResolver() throws LoginException {
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+    
+    @Override
+    public void cancelSync() {
+        cancelPreviousBackgroundCheck();
+    }
+
     @Override
     public void sync(final BaseTopologyView view, final Runnable callback) {
         // cancel the previous backgroundCheck if it's still running
@@ -119,16 +128,7 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
 
         // first do the wait-for-backlog part
         logger.info("sync: doing wait-for-backlog part for view="+view.toShortString());
-        waitWhileBacklog(view, new Runnable() {
-
-            @Override
-            public void run() {
-                // when done, then do the sync-token part
-                logger.info("sync: doing sync-token part for view="+view.toShortString());
-                syncToken(view, callback);
-            }
-            
-        });
+        waitWhileBacklog(view, callback);
     }
 
     private void waitWhileBacklog(final BaseTopologyView view, final Runnable runnable) {
@@ -234,19 +234,12 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
         }
     }
 
-    @Override
     protected DiscoveryLiteConfig getCommonsConfig() {
         return commonsConfig;
     }
 
-    @Override
-    protected ResourceResolverFactory getResourceResolverFactory() {
-        return resourceResolverFactory;
-    }
-
-    @Override
     protected SlingSettingsService getSettingsService() {
         return settingsService;
     }
-    
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
similarity index 72%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
index ba6d6ec..3750946 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
@@ -18,13 +18,10 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
@@ -37,43 +34,79 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.settings.SlingSettingsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
- * Implements the 'sync-token' part of the ConsistencyService,
- * but not the 'wait while backlog' part (which is left to subclasses
- * if needed).
+ * Implements the syncToken idea: each instance stores a key-value
+ * pair with key=stringId and value=discoveryLiteSequenceNumber
+ * under /var/discovery/oak/syncTokens - and then waits until it
+ * sees the same token from all other instances in the cluster.
+ * This way, once the syncToken is received the local instance
+ * knows that all instances in the cluster are now in TOPOLOGY_CHANGING state
+ * (thus all topology-dependent activity is now stalled and waiting)
+ * and are aware of the new discoveryLite view.
  */
-public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
+@Component(immediate = false)
+@Service(value = { ConsistencyService.class, SyncTokenConsistencyService.class })
+public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
-    class HistoryEntry {
-        BaseTopologyView view;
-        String msg;
-        String fullLine;
-    }
-    
-    /** the date format used in the truncated log of topology events **/
-    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-    protected String slingId;
+    @Reference
+    protected DiscoveryLiteConfig commonsConfig;
 
-    protected List<HistoryEntry> history = new LinkedList<HistoryEntry>();
-    
-    protected abstract DiscoveryLiteConfig getCommonsConfig();
+    @Reference
+    protected ResourceResolverFactory resourceResolverFactory;
 
-    protected abstract ResourceResolverFactory getResourceResolverFactory();
+    @Reference
+    protected SlingSettingsService settingsService;
 
-    protected abstract SlingSettingsService getSettingsService();
+    public static SyncTokenConsistencyService testConstructorAndActivate(
+            DiscoveryLiteConfig commonsConfig,
+            ResourceResolverFactory resourceResolverFactory,
+            SlingSettingsService settingsService) {
+        SyncTokenConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
+        service.activate();
+        return service;
+    }
+    
+    public static SyncTokenConsistencyService testConstructor(
+            DiscoveryLiteConfig commonsConfig,
+            ResourceResolverFactory resourceResolverFactory,
+            SlingSettingsService settingsService) {
+        SyncTokenConsistencyService service = new SyncTokenConsistencyService();
+        if (commonsConfig == null) {
+            throw new IllegalArgumentException("commonsConfig must not be null");
+        }
+        if (resourceResolverFactory == null) {
+            throw new IllegalArgumentException("resourceResolverFactory must not be null");
+        }
+        if (settingsService == null) {
+            throw new IllegalArgumentException("settingsService must not be null");
+        }
+        service.commonsConfig = commonsConfig;
+        service.resourceResolverFactory = resourceResolverFactory;
+        service.settingsService = settingsService;
+        return service;
+    }
+
+    @Activate
+    protected void activate() {
+        this.slingId = settingsService.getSlingId();
+        logger.info("activate: activated with slingId="+slingId);
+    }
     
     /** Get or create a ResourceResolver **/
     protected ResourceResolver getResourceResolver() throws LoginException {
-        return getResourceResolverFactory().getAdministrativeResourceResolver(null);
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
     }
     
     @Override
     public void cancelSync() {
         cancelPreviousBackgroundCheck();
     }
-    
+
     @Override
     public void sync(BaseTopologyView view, Runnable callback) {
         // cancel the previous background-check if it's still running
@@ -105,7 +138,7 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
                 // 2) then check if all others have done the same already
                 return seenAllSyncTokens(view);
             }
-        }, callback, getCommonsConfig().getBgTimeoutMillis(), getCommonsConfig().getBgIntervalMillis());
+        }, callback, commonsConfig.getBgTimeoutMillis(), commonsConfig.getBgIntervalMillis());
     }
 
     private boolean storeMySyncToken(String syncTokenId) {
@@ -151,7 +184,7 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
     }
 
     private String getSyncTokenPath() {
-        return getCommonsConfig().getSyncTokenPath();
+        return commonsConfig.getSyncTokenPath();
     }
 
     private boolean seenAllSyncTokens(BaseTopologyView view) {
@@ -212,44 +245,4 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
         }
     }
     
-    public List<String> getSyncHistory() {
-        List<HistoryEntry> snapshot;
-        synchronized(history) {
-            snapshot = Collections.unmodifiableList(history);
-        }
-        List<String> result = new ArrayList<String>(snapshot.size());
-        for (HistoryEntry historyEntry : snapshot) {
-            result.add(historyEntry.fullLine);
-        }
-        return result;
-    }
-
-    protected void addHistoryEntry(BaseTopologyView view, String msg) {
-        synchronized(history) {
-            for(int i = history.size() - 1; i>=0; i--) {
-                HistoryEntry entry = history.get(i);
-                if (!entry.view.equals(view)) {
-                    // don't filter if the view starts differing,
-                    // only filter for the last few entries where
-                    // the view is equal
-                    break;
-                }
-                if (entry.msg.equals(msg)) {
-                    // if the view is equal and the msg matches
-                    // then this is a duplicate entry, so ignore
-                    return;
-                }
-            }
-            String fullLine = sdf.format(Calendar.getInstance().getTime()) + ": " + msg;
-            HistoryEntry newEntry = new HistoryEntry();
-            newEntry.view = view;
-            newEntry.fullLine = fullLine;
-            newEntry.msg = msg;
-            history.add(newEntry);
-            while (history.size() > 12) {
-                history.remove(0);
-            }
-        }
-    }
-
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
deleted file mode 100644
index 32438ea..0000000
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.discovery.commons.providers.spi.base;
-
-import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
-import org.apache.sling.settings.SlingSettingsService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Implements the 'sync-token' part of the ConsistencyService,
- * but not the 'wait while backlog' part (which is left to subclasses
- * if needed).
- */
-@Component(immediate = false)
-@Service(value = { ConsistencyService.class })
-public class SyncTokenOnlyConsistencyService extends BaseSyncTokenConsistencyService {
-
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
-
-    @Reference
-    protected DiscoveryLiteConfig commonsConfig;
-
-    @Reference
-    protected ResourceResolverFactory resourceResolverFactory;
-
-    @Reference
-    protected SlingSettingsService settingsService;
-
-    public static BaseSyncTokenConsistencyService testConstructorAndActivate(
-            DiscoveryLiteConfig commonsConfig,
-            ResourceResolverFactory resourceResolverFactory,
-            SlingSettingsService settingsService) {
-        SyncTokenOnlyConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
-        service.activate();
-        return service;
-    }
-    
-    public static SyncTokenOnlyConsistencyService testConstructor(
-            DiscoveryLiteConfig commonsConfig,
-            ResourceResolverFactory resourceResolverFactory,
-            SlingSettingsService settingsService) {
-        SyncTokenOnlyConsistencyService service = new SyncTokenOnlyConsistencyService();
-        if (commonsConfig == null) {
-            throw new IllegalArgumentException("commonsConfig must not be null");
-        }
-        if (resourceResolverFactory == null) {
-            throw new IllegalArgumentException("resourceResolverFactory must not be null");
-        }
-        if (settingsService == null) {
-            throw new IllegalArgumentException("settingsService must not be null");
-        }
-        service.commonsConfig = commonsConfig;
-        service.resourceResolverFactory = resourceResolverFactory;
-        service.settingsService = settingsService;
-        return service;
-    }
-
-    @Activate
-    protected void activate() {
-        this.slingId = getSettingsService().getSlingId();
-        logger.info("activate: activated with slingId="+slingId);
-    }
-    
-    @Override
-    protected DiscoveryLiteConfig getCommonsConfig() {
-        return commonsConfig;
-    }
-
-    @Override
-    protected ResourceResolverFactory getResourceResolverFactory() {
-        return resourceResolverFactory;
-    }
-
-    @Override
-    protected SlingSettingsService getSettingsService() {
-        return settingsService;
-    }
-    
-}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 01/38: SLING-4665 : adding patch provided by Timothee Maret, many thanks

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit b8fe30ce3973f15a94a2115a3f39721ced7e6d37
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Apr 29 08:30:03 2015 +0000

    SLING-4665 : adding patch provided by Timothee Maret, many thanks
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1676687 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  82 ++++
 .../sling/discovery/commons/InstancesDiff.java     | 486 +++++++++++++++++++++
 .../sling/discovery/commons/package-info.java      |  29 ++
 .../sling/discovery/commons/InstancesDiffTest.java | 331 ++++++++++++++
 4 files changed, 928 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..7b74420
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.apache.sling</groupId>
+        <artifactId>sling</artifactId>
+        <version>22</version>
+        <relativePath>../../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.discovery.commons</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <name>Apache Sling Discovery Commons Bundle</name>
+    <description>
+        Commons services related to Sling Discovery.
+    </description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>biz.aQute</groupId>
+            <artifactId>bndlib</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.discovery.api</artifactId>
+            <version>1.0.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+        <!-- Testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/discovery/commons/InstancesDiff.java b/src/main/java/org/apache/sling/discovery/commons/InstancesDiff.java
new file mode 100644
index 0000000..61b2f9c
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/InstancesDiff.java
@@ -0,0 +1,486 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyView;
+
+/**
+ * The {@code InstancesDiff} allows to combine and filter two collections of {@code InstanceDescription} instances,
+ * an "old" collection and a "new" collection.<p>
+ *
+ * The comparison between {@code InstanceDescription} instances is done only on the basis of the Sling identifier.
+ * Two instances with the same Sling identifier are considered as equal.<p>
+ *
+ * <b>Note</b>: Each collection must contain only unique instances (no two instances with the same Sling identifier).
+ * Using the {@code InstancesDiff} with collections containing duplicated Sling id
+ * will throw an {@code IllegalArgumentException}.<p>
+ *
+ * @since 1.0.0
+ */
+public final class InstancesDiff {
+
+    /**
+     * A filter that keeps local instances (see {@link InstanceDescription#isLocal()}.
+     */
+    private static final InstanceFilter LOCAL_INSTANCE = new LocalInstanceFilter();
+
+    /**
+     * A filter that filters out local instances (see {@link InstanceDescription#isLocal()}.
+     */
+    private static final InstanceFilter NOT_LOCAL_INSTANCE = new NotFilter(LOCAL_INSTANCE);
+
+    /**
+     * A filter that keeps leader instances (see {@link InstanceDescription#isLeader()}.
+     */
+    private static final InstanceFilter LEADER_INSTANCE = new LeaderInstanceFilter();
+
+    /**
+     * A filter that filters out leader instances (see {@link InstanceDescription#isLeader()}.
+     */
+    private static final InstanceFilter NOT_LEADER_INSTANCE = new NotFilter(LEADER_INSTANCE);
+
+    /**
+     * Keeps track of the old {@code InstanceDescription} instances
+     *
+     * The map keys are the instance Sling identifiers and values are
+     * the {@code InstanceDescription} instances descriptions.
+     */
+    private final Map<String, InstanceDescription> oldInstances;
+
+    /**
+     * Keeps track of the new {@code InstanceDescription} instances
+     *
+     * The map keys are the instance Sling identifiers and values are
+     * the {@code InstanceDescription} instances descriptions.
+     */
+    private final Map<String, InstanceDescription> newInstances;
+
+    /**
+     * Create a new {@code InstancesDiff} based on the instances from the old and
+     * new {@code TopologyView} topology views contained in the {@code TopologyEvent} event provided.
+     *
+     * @param event the non {@code null} event from which the old and new topology views are used for computing.
+     *              If either of the topology views are {@code null}, then they will be substituted by an
+     *              empty collection of instances.
+     * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers.
+     */
+    public InstancesDiff(@Nonnull TopologyEvent event) {
+        this(instancesOrEmpty(event.getOldView()), instancesOrEmpty(event.getNewView()));
+    }
+
+    /**
+     * Create a new {@code InstancesDiff} based on the instances from the old and
+     * new {@code TopologyView} topology views provided.
+     *
+     * @param oldView the non {@code null} old topology view from which the old collection is used for computing.
+     * @param newView the non {@code null} new topology view form which the new collection is used for computing.
+     * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers.
+     */
+    public InstancesDiff(@Nonnull TopologyView oldView, @Nonnull TopologyView newView) {
+        this(oldView.getInstances(), newView.getInstances());
+    }
+
+    /**
+     * Create a new {@code InstancesDiff} based on the instances from the old and
+     * new {@code ClusterView} cluster views provided.
+     *
+     * @param oldView the non {@code null} old cluster view used for computing.
+     * @param newView the non {@code null} new cluster view used for computing.
+     * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers.
+     */
+    public InstancesDiff(@Nonnull ClusterView oldView, @Nonnull ClusterView newView) {
+        this(oldView.getInstances(), newView.getInstances());
+    }
+
+    /**
+     * Create a new {@code InstancesDiff} based on the provided old and
+     * new {@code Collection} collections of instances.
+     *
+     * @param oldInstances the non {@code null} old collection of instances used for computing.
+     * @param newInstances the non {@code null} new collection of instances used for computing.
+     * @param <T> the type of instance which must extend {@code InstanceDescription}.
+     * @throws IllegalArgumentException if either of the collections contains duplicated Sling identifiers.
+     */
+    public <T extends InstanceDescription> InstancesDiff(@Nonnull Collection<T> oldInstances, @Nonnull Collection<T> newInstances) {
+        this.newInstances = getInstancesMap(newInstances);
+        this.oldInstances = getInstancesMap(oldInstances);
+    }
+
+    /**
+     * Returns the {@code InstanceSet} set containing the {@code InstanceDescription} instances that are
+     * contained in either the old or the new collection.<p>
+     *
+     * For {@code InstanceDescription} instances contained in both the old and
+     * the new collections, the method will retain those from either of the collections
+     * depending on the parameter #retainFromNewView.<p>
+     *
+     * @param retainFromNewCollection {@code true} in order to retain the instances from the new collection ;
+     *                          {@code false} in order to retain the instances from the old collection.
+     * @return the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances
+     *         from both collections.
+     */
+    @Nonnull
+    public InstanceCollection all(boolean retainFromNewCollection) {
+        return new InstanceCollection(partitionAll(retainFromNewCollection));
+    }
+
+    /**
+     * Returns the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances that are
+     * contained in the new collection but not in the old collection.
+     *
+     * @return the {@code InstanceCollection} collection containing the instances in the new
+     *         topology collection but not in the old collection.
+     */
+    @Nonnull
+    public InstanceCollection added() {
+        return new InstanceCollection(partitionAdded());
+    }
+
+    /**
+     * Returns the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances that are
+     * contained in the old collection but not in the new collection.
+     *
+     * @return the {@code InstanceSet} set containing the instances in the old collection but not in the new collection.
+     */
+    @Nonnull
+    public InstanceCollection removed() {
+        return new InstanceCollection(partitionRemoved());
+    }
+
+    /**
+     * Returns the {@code InstanceSet} collection containing the {@code InstanceDescription} instances that are
+     * contained in both the old collection and the new collection.<p>
+     *
+     * The method will retain the {@code InstanceDescription} instances from either of the collections
+     * depending on the parameter #retainFromNewView.<p>
+     *
+     * @param retainFromNewCollection {@code true} in order to retain the instances from the new collection ;
+     *                                {@code false} in order to retain the instances from the old collection.
+     * @return the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances
+     *         contained in both collections.
+     */
+    @Nonnull
+    public InstanceCollection retained(boolean retainFromNewCollection) {
+        return new InstanceCollection(partitionRetained(retainFromNewCollection));
+    }
+
+    /**
+     * Returns the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances that are
+     * contained in both the old and the new collections.<p>
+     *
+     * The method will retain the {@code InstanceDescription} instances from either of the collections
+     * depending on the parameter #retainFromNewView.<p>
+     *
+     * @param retainFromNewCollection {@code true} in order to retain the instances from the new collection ;
+     *                                {@code false} in order to retain the instances from the old collection.
+     * @param propertyChanged {@code true} in order to keep only the instances which
+     *                        properties have not changed between the old and new collections ;
+     *                        {@code false} in order to keep only the instances which properties have changed.
+     * @return the {@code InstanceCollection} collection containing the {@code InstanceDescription} instances
+     *         contained in both views.
+     */
+    @Nonnull
+    public InstanceCollection retained(boolean retainFromNewCollection, boolean propertyChanged) {
+        return new InstanceCollection(partitionRetained(retainFromNewCollection, propertyChanged));
+    }
+
+    //
+
+    @Nonnull
+    private Map<String, InstanceDescription> partitionAll(boolean retainFromNewCollection) {
+        Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>();
+        if (retainFromNewCollection) {
+            partition.putAll(oldInstances);
+            partition.putAll(newInstances);
+        } else {
+            partition.putAll(newInstances);
+            partition.putAll(oldInstances);
+        }
+        return partition;
+    }
+
+    @Nonnull
+    private Map<String, InstanceDescription> partitionRemoved() {
+        Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(oldInstances);
+        partition.keySet().removeAll(newInstances.keySet());
+        return partition;
+    }
+
+    @Nonnull
+    private Map<String, InstanceDescription> partitionAdded() {
+        Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>(newInstances);
+        partition.keySet().removeAll(oldInstances.keySet());
+        return partition;
+    }
+
+    @Nonnull
+    private Map<String, InstanceDescription> partitionRetained(boolean retainFromNewCollection, boolean propertyChanged) {
+        Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>();
+        for (Map.Entry<String, InstanceDescription> oldEntry : oldInstances.entrySet()) {
+            String slingId = oldEntry.getKey();
+            InstanceDescription newDescription = newInstances.get(slingId);
+            if(newDescription != null) {
+                InstanceDescription oldDescription = oldEntry.getValue();
+                boolean propertiesSame = newDescription.getProperties().equals(oldDescription.getProperties());
+                if ((propertiesSame && ! propertyChanged) || (! propertiesSame && propertyChanged)) {
+                    partition.put(slingId, retainFromNewCollection ? newDescription : oldDescription);
+                }
+            }
+        }
+        return partition;
+    }
+
+    @Nonnull
+    private Map<String, InstanceDescription> partitionRetained(boolean retainFromNewCollection) {
+        Map<String, InstanceDescription> partition = new HashMap<String, InstanceDescription>();
+        if (retainFromNewCollection) {
+            partition.putAll(newInstances);
+            partition.keySet().retainAll(oldInstances.keySet());
+        } else {
+            partition.putAll(oldInstances);
+            partition.keySet().retainAll(newInstances.keySet());
+        }
+        return partition;
+    }
+
+    @Nonnull
+    private static Set<InstanceDescription> instancesOrEmpty(@Nullable TopologyView topologyView) {
+        return (topologyView != null) ? topologyView.getInstances() : Collections.<InstanceDescription>emptySet();
+    }
+
+    @Nonnull
+    private static <T extends InstanceDescription> Map<String, InstanceDescription> getInstancesMap(@Nonnull Collection<T> instances) {
+        Map<String, InstanceDescription> instancesMap = new HashMap<String, InstanceDescription>();
+        for (InstanceDescription instance : instances) {
+            String slingId = instance.getSlingId();
+            if (slingId != null) {
+                if (instancesMap.put(slingId, instance) != null) {
+                    throw new IllegalArgumentException(String.format("Duplicated instance found for slingId: %s", slingId));
+                }
+            }
+        }
+        return instancesMap;
+    }
+
+    private static final class NotFilter implements InstanceFilter {
+
+        final InstanceFilter filter;
+
+        private NotFilter(InstanceFilter filter) {
+            this.filter = filter;
+        }
+
+        public boolean accept(InstanceDescription instance) {
+            return ! filter.accept(instance);
+        }
+    }
+
+    private static final class LocalInstanceFilter implements InstanceFilter {
+
+        public boolean accept(InstanceDescription instance) {
+            return instance.isLocal();
+        }
+    }
+
+    private static final class LeaderInstanceFilter implements InstanceFilter {
+
+        public boolean accept(InstanceDescription instance) {
+            return instance.isLeader();
+        }
+    }
+
+    private static final class InClusterView implements InstanceFilter {
+
+        private final ClusterView view;
+
+        private InClusterView(ClusterView view) {
+            this.view = view;
+        }
+
+        public boolean accept(InstanceDescription instance) {
+            return view.getId().equals(instance.getClusterView().getId());
+        }
+    }
+
+    /**
+     * The {@code InstanceCollection} collection allows to filter the instances using a set of custom filter
+     * either implementing {@code InstanceFilter} or pre-defined ones.<p>
+     *
+     * Filters conditions are joined combined together using the logical operator "AND".<p>
+     */
+    public final class InstanceCollection {
+
+        /**
+         * Holds the instances to be filtered.
+         *
+         * The map keys are the instance Sling identifiers and values are
+         * the {@code InstanceDescription} instances descriptions.
+         */
+        private final Map<String, InstanceDescription> instances;
+
+        /**
+         * Holds the set of filters to be applied (ANDed).
+         */
+        private final Set<InstanceFilter> filters = new HashSet<InstanceFilter>();
+
+        /**
+         * Filter the instances with a custom {@code InstanceFilter} filter.
+         *
+         * @param filter the filter to be applied on the instances
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection filterWith(@Nullable InstanceFilter filter) {
+            if (filter != null) {
+                filters.add(filter);
+            }
+            return this;
+        }
+
+        /**
+         * Keep only the local instance (see {@link InstanceDescription#isLocal()}.
+         *
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection isLocal() {
+            filters.add(LOCAL_INSTANCE);
+            return this;
+        }
+
+        /**
+         * Filter out the local instances (see {@link InstanceDescription#isLocal()}.
+         *
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection isNotLocal() {
+            filters.add(NOT_LOCAL_INSTANCE);
+            return this;
+        }
+
+        /**
+         * Keep only the leader instances (see {@link InstanceDescription#isLeader()}.
+         *
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection isLeader() {
+            filters.add(LEADER_INSTANCE);
+            return this;
+        }
+
+        /**
+         * Filter out the leader instances (see {@link InstanceDescription#isLeader()}.
+         *
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection isNotLeader() {
+            filters.add(NOT_LEADER_INSTANCE);
+            return this;
+        }
+
+        /**
+         * Keep only the instances that are contained in the same {@code ClusterView} cluster view
+         * as the one provided.<p>
+         *
+         * The comparison between cluster views is done on the basis of the cluster
+         * view identifier. Two cluster views with the same identifier are considered equal.<p>
+         *
+         * @param clusterView the cluster view used to filter the instances
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection isInClusterView(@Nullable ClusterView clusterView) {
+            if (clusterView != null) {
+                filters.add(new InClusterView(clusterView));
+            }
+            return this;
+        }
+
+        /**
+         * Filter out the instances that are contained in the same {@code ClusterView} cluster view
+         * as the one provided.<p>
+         *
+         * The comparison between cluster views is done on the basis of the cluster
+         * view identifier. Two cluster views with the same identifier are considered equal.<p>
+         *
+         * @param clusterView the cluster view used to filter the instances
+         * @return {@code this}
+         */
+        @Nonnull
+        public InstanceCollection isNotInClusterView(@Nullable ClusterView clusterView) {
+            if (clusterView != null) {
+                filters.add(new NotFilter(new InClusterView(clusterView)));
+            }
+            return this;
+        }
+
+        /**
+         * Return the collection of {@code InstanceDescription} instances that have not been filtered out.
+         *
+         * @return the filtered collection of instances.
+         */
+        @Nonnull
+        public Collection<InstanceDescription> get() {
+            return applyFilters();
+        }
+
+        //
+
+        /**
+         * Instances of this class can only be obtained through the {@code InstancesDiff} class.
+         * @param instances the map of instances to be filtered
+         */
+        private InstanceCollection(@Nonnull Map<String, InstanceDescription> instances) {
+            this.instances = instances;
+        }
+
+        @Nonnull
+        private Collection<InstanceDescription> applyFilters() {
+            Iterator<Map.Entry<String, InstanceDescription>> entries = instances.entrySet().iterator();
+            for ( ; entries.hasNext() ; ) {
+                Map.Entry<String, InstanceDescription> entry = entries.next();
+                for (InstanceFilter filter : filters) {
+                    if (! filter.accept(entry.getValue())) {
+                        entries.remove();
+                        break;
+                    }
+                }
+            }
+            return Collections.<InstanceDescription>unmodifiableCollection(instances.values());
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/package-info.java b/src/main/java/org/apache/sling/discovery/commons/package-info.java
new file mode 100644
index 0000000..e104df0
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides commons utility for the Discovery API.
+ *
+ * @version 1.0.0
+ */
+@Version("1.0.0")
+package org.apache.sling.discovery.commons;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/test/java/org/apache/sling/discovery/commons/InstancesDiffTest.java b/src/test/java/org/apache/sling/discovery/commons/InstancesDiffTest.java
new file mode 100644
index 0000000..a2d5ddc
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/InstancesDiffTest.java
@@ -0,0 +1,331 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class InstancesDiffTest {
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDuplicatedSlingIds() {
+        List<Instance> old = Arrays.asList(new Instance("duplicated"), new Instance("one"), new Instance("duplicated"));
+        new InstancesDiff(old, empty());
+    }
+
+    @Test
+    public void testEmptyCollections() {
+        InstancesDiff diff = new InstancesDiff(empty(), empty());
+        TestCase.assertEquals(0, diff.all(true).get().size());
+        TestCase.assertEquals(0, diff.added().get().size());
+        TestCase.assertEquals(0, diff.removed().get().size());
+        TestCase.assertEquals(0, diff.retained(true).get().size());
+    }
+
+    // added
+
+    @Test
+    public void testAddedFromEmpty() throws Exception {
+        InstancesDiff diff = new InstancesDiff(empty(), Arrays.asList(new Instance("one"), new Instance("two")));
+        TestCase.assertEquals(2, diff.added().get().size());
+    }
+
+    @Test
+    public void testAddedWithEmpty() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), empty());
+        TestCase.assertEquals(0, diff.added().get().size());
+    }
+
+    @Test
+    public void testAddedWithoutIntersection() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Collections.singletonList(new Instance("one")),
+                Collections.singletonList(new Instance("two")));
+        TestCase.assertEquals(1, diff.added().get().size());
+        TestCase.assertEquals("two", diff.added().get().iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testAddedWithIntersection() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")),
+                Arrays.asList(new Instance("two"), new Instance("three")));
+        TestCase.assertEquals(1, diff.added().get().size());
+        TestCase.assertEquals("three", diff.added().get().iterator().next().getSlingId());
+    }
+
+    // all
+
+    @Test
+    public void testAll() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Collections.singletonList(new Instance("one")),
+                Arrays.asList(new Instance("two"), new Instance("three")));
+        TestCase.assertEquals(3, diff.all(true).get().size());
+    }
+
+    @Test
+    public void testAllRetainedCollection() throws Exception {
+        Instance oldInstance = new Instance("one");
+        Instance newInstance = new Instance("one");
+        InstancesDiff diff = new InstancesDiff(
+                Collections.singletonList(oldInstance),
+                Collections.singletonList(newInstance));
+        TestCase.assertEquals(1, diff.all(true).get().size());
+        TestCase.assertEquals(newInstance, diff.all(true).get().iterator().next());
+        TestCase.assertEquals(1, diff.all(false).get().size());
+        TestCase.assertEquals(oldInstance, diff.all(false).get().iterator().next());
+    }
+
+    // removed
+
+    @Test
+    public void testRemovedFromEmpty() throws Exception {
+        InstancesDiff diff = new InstancesDiff(empty(), Arrays.asList(new Instance("one"), new Instance("two")));
+        TestCase.assertEquals(0, diff.removed().get().size());
+    }
+
+    @Test
+    public void testRemovedWithEmpty() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")), empty());
+        TestCase.assertEquals(2, diff.removed().get().size());
+    }
+
+    @Test
+    public void testRemovedWithoutIntersection() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Collections.singletonList(new Instance("one")),
+                Collections.singletonList(new Instance("two")));
+        TestCase.assertEquals(1, diff.removed().get().size());
+        TestCase.assertEquals("one", diff.removed().get().iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testRemovedWithIntersection() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")),
+                Arrays.asList(new Instance("two"), new Instance("three")));
+        TestCase.assertEquals(1, diff.removed().get().size());
+        TestCase.assertEquals("one", diff.removed().get().iterator().next().getSlingId());
+    }
+
+    // retained
+
+    @Test
+    public void testRetainedWithoutIntersection() throws Exception {
+        InstancesDiff diff = new InstancesDiff(empty(), Arrays.asList(new Instance("one"), new Instance("two")));
+        TestCase.assertEquals(0, diff.retained(true).get().size());
+    }
+
+    @Test
+    public void testRetainedWithIntersection() throws Exception {
+        InstancesDiff diff = new InstancesDiff(Arrays.asList(new Instance("one"), new Instance("two")),
+                Arrays.asList(new Instance("two"), new Instance("three")));
+        TestCase.assertEquals(1, diff.retained(true).get().size());
+        TestCase.assertEquals("two", diff.retained(true).get().iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testRetainedCollection() throws Exception {
+        Instance oldInstance = new Instance("one");
+        Instance newInstance = new Instance("one");
+        InstancesDiff diff = new InstancesDiff(Collections.singletonList(oldInstance),
+                Collections.singletonList(newInstance));
+        TestCase.assertEquals(1, diff.retained(true).get().size());
+        TestCase.assertEquals(newInstance, diff.retained(true).get().iterator().next());
+        TestCase.assertEquals(oldInstance, diff.retained(false).get().iterator().next());
+    }
+
+    @Test
+    public void testRetainedByProperties() throws Exception {
+        InstancesDiff diff = new InstancesDiff(
+                Arrays.asList(new Instance("one", Collections.singletonMap("p1", "v1")), new Instance("two", Collections.singletonMap("p1", "v1"))),
+                Arrays.asList(new Instance("one", Collections.singletonMap("p1", "v2")), new Instance("two", Collections.singletonMap("p1", "v1"))));
+        TestCase.assertEquals(1, diff.retained(true, false).get().size());
+        TestCase.assertEquals("two", diff.retained(true, false).get().iterator().next().getSlingId());
+        TestCase.assertEquals(1, diff.retained(true, true).get().size());
+        TestCase.assertEquals("one", diff.retained(true, true).get().iterator().next().getSlingId());
+    }
+
+    // filters
+
+    @Test
+    public void testEmptyResult() throws Exception {
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one"), new Instance("two")), empty()).all(true).filterWith(new InstanceFilter() {
+            public boolean accept(InstanceDescription instanceDescription) {
+                return false;
+            }
+        }).get();
+        TestCase.assertEquals(0, instances.size());
+    }
+
+    @Test
+    public void testFilterWith() throws Exception {
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one"), new Instance("two")), empty()).all(true).filterWith(new InstanceFilter() {
+            public boolean accept(InstanceDescription instanceDescription) {
+                return "one".equals(instanceDescription.getSlingId());
+            }
+        }).get();
+        TestCase.assertEquals(1, instances.size());
+        TestCase.assertEquals("one", instances.iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testIsLeader() throws Exception {
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"),
+                new Instance("two", false, false, Collections.<String, String>emptyMap(), "viewId")), empty())
+                .all(true)
+                .isLeader()
+                .get();
+        TestCase.assertEquals(1, instances.size());
+        TestCase.assertEquals("one", instances.iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testIsNotLeader() throws Exception {
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"),
+                new Instance("two", false, false, Collections.<String, String>emptyMap(), "viewId")), empty())
+                .all(true)
+                .isNotLeader()
+                .get();
+        TestCase.assertEquals(1, instances.size());
+        TestCase.assertEquals("two", instances.iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testIsLocal() throws Exception {
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"),
+                new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty())
+                .all(true)
+                .isLocal()
+                .get();
+        TestCase.assertEquals(1, instances.size());
+        TestCase.assertEquals("two", instances.iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testIsNotLocal() throws Exception {
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one", true, false, Collections.<String, String>emptyMap(), "viewId"),
+                new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty())
+                .all(true)
+                .isNotLocal()
+                .get();
+        TestCase.assertEquals(1, instances.size());
+        TestCase.assertEquals("one", instances.iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testIsInClusterView() throws Exception {
+        ClusterView clusterView = clusterView("viewId");
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one", true, false, Collections.<String, String>emptyMap(), "otherView"),
+                new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty())
+                .all(true)
+                .isInClusterView(clusterView)
+                .get();
+        TestCase.assertEquals(1, instances.size());
+        TestCase.assertEquals("two", instances.iterator().next().getSlingId());
+    }
+
+    @Test
+    public void testIsNotInClusterView() throws Exception {
+        ClusterView clusterView = clusterView("yet-another-view");
+        Collection<InstanceDescription> instances = new InstancesDiff(Arrays.asList(
+                new Instance("one", true, false, Collections.<String, String>emptyMap(), "otherView"),
+                new Instance("two", false, true, Collections.<String, String>emptyMap(), "viewId")), empty())
+                .all(true)
+                .isInClusterView(clusterView)
+                .get();
+        TestCase.assertEquals(0, instances.size());
+    }
+
+    private List<Instance> empty() {
+        return Collections.<Instance>emptyList();
+    }
+
+    private class Instance implements InstanceDescription {
+
+        final String slingId;
+
+        final boolean leader;
+
+        final boolean local;
+
+        final Map<String, String> properties;
+
+        final ClusterView clusterView;
+
+        Instance(String slingId) {
+            this(slingId, false, false, Collections.<String, String>emptyMap(), "");
+        }
+
+        Instance(String slingId, Map<String, String> properties) {
+            this(slingId, false, false, properties, "");
+        }
+
+        Instance(String slingId, boolean leader, boolean local, Map<String, String> properties, String clusterViewId) {
+            this.slingId = slingId;
+            this.leader = leader;
+            this.local = local;
+            this.properties = properties;
+            clusterView = clusterView(clusterViewId);
+        }
+
+        public ClusterView getClusterView() {
+            return clusterView;
+        }
+
+        public boolean isLeader() {
+            return leader;
+        }
+
+        public boolean isLocal() {
+            return local;
+        }
+
+        public String getSlingId() {
+            return slingId;
+        }
+
+        public String getProperty(String name) {
+            return properties.get(name);
+        }
+
+        public Map<String, String> getProperties() {
+            return properties;
+        }
+    }
+
+    private ClusterView clusterView(String clusterViewId) {
+        ClusterView clusterView = Mockito.mock(ClusterView.class);
+        Mockito.when(clusterView.getId()).thenReturn(clusterViewId);
+        return clusterView;
+    }
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 29/38: SLING-5173 : minor code cleanup (duplicate logger removed)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 05027b8831d1d7d314c0706abb08711448859d2d
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 07:56:48 2015 +0000

    SLING-5173 : minor code cleanup (duplicate logger removed)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710133 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/spi/base/SyncTokenConsistencyService.java      | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
index b469f4a..1e0cfc2 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
@@ -34,8 +34,6 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.settings.SlingSettingsService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Implements the syncToken idea: each instance stores a key-value
@@ -51,8 +49,6 @@ import org.slf4j.LoggerFactory;
 @Service(value = { ConsistencyService.class, SyncTokenConsistencyService.class })
 public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
-
     @Reference
     protected DiscoveryLiteConfig commonsConfig;
 
@@ -144,7 +140,6 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
                     return false;
                 }
                 
-                
                 // 2) then check if all others have done the same already
                 return seenAllSyncTokens(view);
             }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 14/38: SLING-5173 : adding refactored SyncTokenOnlyConsistencyService again

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 6dd90e163b1b193267e379ff8212d06dc7cb5a8a
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 08:30:07 2015 +0000

    SLING-5173 : adding refactored SyncTokenOnlyConsistencyService again
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709752 13f79535-47bb-0310-9956-ffa450edef68
---
 .../spi/base/SyncTokenOnlyConsistencyService.java  | 102 +++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
new file mode 100644
index 0000000..8811458
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.base;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.settings.SlingSettingsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements the 'sync-token' part of the ConsistencyService,
+ * but not the 'wait while backlog' part (which is left to subclasses
+ * if needed).
+ */
+@Component(immediate = false)
+@Service(value = { ConsistencyService.class })
+public class SyncTokenOnlyConsistencyService extends BaseSyncTokenConsistencyService {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    @Reference
+    protected DiscoveryLiteConfig commonsConfig;
+
+    @Reference
+    protected ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    protected SlingSettingsService settingsService;
+
+    protected String slingId;
+
+    protected long syncTokenTimeoutMillis;
+    
+    protected long syncTokenIntervalMillis;
+
+    public static BaseSyncTokenConsistencyService testConstructorAndActivate(
+            DiscoveryLiteConfig commonsConfig,
+            ResourceResolverFactory resourceResolverFactory,
+            SlingSettingsService settingsService) {
+        BaseSyncTokenConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
+        service.activate();
+        return service;
+    }
+    
+    public static BaseSyncTokenConsistencyService testConstructor(
+            DiscoveryLiteConfig commonsConfig,
+            ResourceResolverFactory resourceResolverFactory,
+            SlingSettingsService settingsService) {
+        SyncTokenOnlyConsistencyService service = new SyncTokenOnlyConsistencyService();
+        if (commonsConfig == null) {
+            throw new IllegalArgumentException("commonsConfig must not be null");
+        }
+        if (resourceResolverFactory == null) {
+            throw new IllegalArgumentException("resourceResolverFactory must not be null");
+        }
+        if (settingsService == null) {
+            throw new IllegalArgumentException("settingsService must not be null");
+        }
+        service.commonsConfig = commonsConfig;
+        service.resourceResolverFactory = resourceResolverFactory;
+        service.syncTokenTimeoutMillis = commonsConfig.getBgTimeoutMillis();
+        service.syncTokenIntervalMillis = commonsConfig.getBgIntervalMillis();
+        service.settingsService = settingsService;
+        return service;
+    }
+
+    @Override
+    protected DiscoveryLiteConfig getCommonsConfig() {
+        return commonsConfig;
+    }
+
+    @Override
+    protected ResourceResolverFactory getResourceResolverFactory() {
+        return resourceResolverFactory;
+    }
+
+    @Override
+    protected SlingSettingsService getSettingsService() {
+        return settingsService;
+    }
+    
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 11/38: SLING-4697 : support for PROPERTIES_CHANGED in ViewStateManagerImpl tested and thus fixed

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit bb3d20df478702bb2f5b50f25ba3eab0a72bfe0c
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 8 15:33:06 2015 +0000

    SLING-4697 : support for PROPERTIES_CHANGED in ViewStateManagerImpl tested and thus fixed
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1707571 13f79535-47bb-0310-9956-ffa450edef68
---
 .../providers/impl/ViewStateManagerImpl.java       |  8 ++++---
 .../providers/impl/SimpleInstanceDescription.java  | 13 +++++++++--
 .../commons/providers/impl/SimpleTopologyView.java |  5 +++-
 .../providers/impl/TestViewStateManager.java       | 27 +++++++++++++++++++++-
 4 files changed, 46 insertions(+), 7 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
index 5462174..fe7e527 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
@@ -439,9 +439,11 @@ class ViewStateManagerImpl implements ViewStateManager {
                     logger.debug("handleNewViewNonDelayed: we were not in changing state and new view matches old, so - ignoring");
                     return false;
                 }
-                logger.debug("handleNewViewNonDelayed: implicitly triggering a handleChanging as we were not in changing state");
-                handleChanging();
-                logger.debug("handleNewViewNonDelayed: implicitly triggering of a handleChanging done");
+                if (previousView==null || !isPropertiesDiff(newView)) {
+                    logger.debug("handleNewViewNonDelayed: implicitly triggering a handleChanging as we were not in changing state");
+                    handleChanging();
+                    logger.debug("handleNewViewNonDelayed: implicitly triggering of a handleChanging done");
+                }
             }
                 
             if (!activated) {
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
index f5a485f..f6c8893 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
@@ -30,7 +30,7 @@ public class SimpleInstanceDescription implements InstanceDescription {
     private final boolean isLeader;
     private final boolean isLocal;
     private final String slingId;
-    private final Map<String, String> properties;
+    private Map<String, String> properties;
 
     public SimpleInstanceDescription(boolean isLeader, boolean isLocal, String slingId,
             Map<String, String> properties) {
@@ -65,7 +65,9 @@ public class SimpleInstanceDescription implements InstanceDescription {
         if (isLocal!=other.isLocal) {
             return false;
         }
-        return true;
+        Map<String, String> myProperties = getProperties();
+        Map<String, String> otherProperties = other.getProperties();
+        return (myProperties.equals(otherProperties));
     }
 
     public void setClusterView(ClusterView clusterView) {
@@ -105,4 +107,11 @@ public class SimpleInstanceDescription implements InstanceDescription {
         return new HashMap<String,String>(properties);
     }
 
+    public void setProperty(String key, String value) {
+        if (properties==null) {
+            properties = new HashMap<String, String>();
+        }
+        properties.put(key, value);
+    }
+
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
index ac9e292..e0add44 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
@@ -163,7 +163,7 @@ public class SimpleTopologyView extends BaseTopologyView {
         final boolean isLeader = artefact.isLeader();
         final boolean isLocal = artefact.isLocal();
         SimpleClusterView cluster = (SimpleClusterView) artefact.getClusterView();
-        final SimpleInstanceDescription instance = new SimpleInstanceDescription(isLeader, isLocal, slingId, null);
+        final SimpleInstanceDescription instance = new SimpleInstanceDescription(isLeader, isLocal, slingId, artefact.getProperties());
         instance.setClusterView(cluster);
         cluster.addInstanceDescription(instance);
         instances.add(instance);
@@ -201,6 +201,9 @@ public class SimpleTopologyView extends BaseTopologyView {
             clone.setClusterView(cluster);
             result.addInstance(clone);
         }
+        if (!view.isCurrent()) {
+            result.setNotCurrent();
+        }
         return result;
     }
     
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
index 48f88fb..1ef9050 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
@@ -30,6 +30,7 @@ import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
+import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.EventFactory;
@@ -219,6 +220,29 @@ public class TestViewStateManager {
     }
 
     @Test
+    public void testPropertiesChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        mgr.bind(listener);
+        mgr.handleChanging();
+        SimpleTopologyView oldView = new SimpleTopologyView().addInstance();
+        SimpleInstanceDescription localInstance = 
+                (SimpleInstanceDescription) oldView.getLocalInstance();
+        localInstance.setProperty("foo", "bar1");
+        mgr.handleNewView(oldView);
+        TopologyEvent initEvent = EventFactory.newInitEvent(oldView.clone());
+        assertEvents(listener, initEvent);
+        SimpleTopologyView newView = oldView.clone();
+        oldView.setNotCurrent();
+        localInstance = (SimpleInstanceDescription) newView.getLocalInstance();
+        localInstance.setProperty("foo", "bar2");
+        mgr.handleNewView(newView);
+        Thread.sleep(2000);
+        TopologyEvent propertiesChangedEvent = EventFactory.newPropertiesChangedEvent(oldView.clone(), newView.clone());
+        assertEvents(listener, propertiesChangedEvent);
+    }
+
+    @Test
     public void testActivateChangingBindChanged() throws Exception {
         final Listener listener = new Listener();
         // first activate
@@ -464,9 +488,10 @@ public class TestViewStateManager {
         serviceSemaphore.release(1);
         Thread.sleep(1000);
         assertEvents(listener, EventFactory.newInitEvent(view1));
+        final SimpleTopologyView view2 = view1.clone();
         mgr.handleChanging();
         assertEvents(listener, EventFactory.newChangingEvent(view1));
-        final SimpleTopologyView view2 = SimpleTopologyView.clone(view1).removeInstance(slingId2);
+        view2.removeInstance(slingId2);
         async(new Runnable() {
 
             public void run() {

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 36/38: SLING-5094 / SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit e5ded77b56e8cd935219dd94033a2f14e65eb597
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 09:35:55 2015 +0000

    SLING-5094 / SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710538 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/discovery/commons/providers/base/TestViewStateManager.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index 8dd1796..048b0cd 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -520,7 +520,7 @@ public class TestViewStateManager {
     }
     
     @Test
-    public void testConsistencyService_noConcurrency() throws Exception {
+    public void testClusterSyncService_noConcurrency() throws Exception {
         final org.apache.log4j.Logger commonsLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery.commons.providers");
         final org.apache.log4j.Level logLevel = commonsLogger.getLevel();
         commonsLogger.setLevel(Level.INFO); // change here to DEBUG in case of issues with this test
@@ -581,7 +581,7 @@ public class TestViewStateManager {
     }
 
     @Test
-    public void testConsistencyService_withConcurrency() throws Exception {
+    public void testClusterSyncService_withConcurrency() throws Exception {
         final org.apache.log4j.Logger commonsLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery.commons.providers");
         final org.apache.log4j.Level logLevel = commonsLogger.getLevel();
         commonsLogger.setLevel(Level.INFO); // change here to DEBUG in case of issues with this test

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 03/38: SLING-4685 : adding initial version of ViewStateManager - a shared implementation of TopologyEventListener-handling and sending of events based on activate/deactivate/changing/newView triggers - intended for use by implementors of the discovery.api (not clients of it)

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 594318990cdea7c1eda90c2fb9c7cedc59c2a465
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon May 4 10:11:10 2015 +0000

    SLING-4685 : adding initial version of ViewStateManager - a shared implementation of TopologyEventListener-handling and sending of events based on activate/deactivate/changing/newView triggers - intended for use by implementors of the discovery.api (not clients of it)
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1677574 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   5 +
 .../commons/providers/BaseTopologyView.java        |  52 ++
 .../commons/providers/ViewStateManager.java        | 352 +++++++++++++
 .../discovery/commons/providers/package-info.java  |  29 ++
 .../commons/providers/TestViewStateManager.java    | 569 +++++++++++++++++++++
 5 files changed, 1007 insertions(+)

diff --git a/pom.xml b/pom.xml
index 7b74420..7a9f48e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -67,6 +67,11 @@
             <artifactId>jsr305</artifactId>
             <version>2.0.0</version>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.6.1</version>
+        </dependency>
         <!-- Testing -->
         <dependency>
             <groupId>junit</groupId>
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
new file mode 100644
index 0000000..d8daefe
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import org.apache.sling.discovery.TopologyView;
+
+/**
+ * Very simple abstract base class for the TopologyView which
+ * comes with the 'setNotCurrent()' method - that allows the
+ * ViewStateManager to mark a topologyView as no longer current
+ * - and the isCurrent() is handled accordingly.
+ */
+public abstract class BaseTopologyView implements TopologyView {
+
+    /** Whether or not this topology is considered 'current' / ie currently valid **/
+    private volatile boolean current = true;
+    
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isCurrent() {
+        return current;
+    }
+    
+    /**
+     * Marks this view as no longer current - this typically
+     * results in a TOPOLOGY_CHANGING event to be sent.
+     * <p>
+     * Note that once marked as not current, it can no longer
+     * be reverted to current==true
+     */
+    public void setNotCurrent() {
+        current = false;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
new file mode 100644
index 0000000..5820f5d
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
@@ -0,0 +1,352 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The ViewStateManager is at the core of managing TopologyEventListeners,
+ * the 'view state' (changing vs changed) and sending out the appropriate
+ * and according TopologyEvents to the registered listeners.
+ * <p>
+ * Note that this class is completely unsynchronized and the idea is that
+ * (since this class is only of interest to other implementors/providers of 
+ * the discovery.api, not to users of the discovery.api however) that those
+ * other implementors take care of proper synchronization. Without synchronization
+ * this class is prone to threading issues! The most simple form of 
+ * synchronization is to synchronize all of the public (non-static..) methods
+ * with the same monitor object.
+ */
+public class ViewStateManager {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+    
+    /** 
+     * List of bound listeners that have already received their INIT event - others are in unInitializedEventListeners.
+     * @see ViewStateManager#unInitializedEventListeners
+     */
+    private List<TopologyEventListener> eventListeners = new ArrayList<TopologyEventListener>();
+
+    /**
+     * List of bound listeners that have not yet received their TOPOLOGY_INIT event - 
+     * once they are sent the TOPOLOGY_INIT event they are moved to eventListeners (and stay there).
+     * <p>
+     * This list becomes necessary for cases where the bind() happens before activate, or after activate but at a time
+     * when the topology is TOPOLOGY_CHANGING - at which point an TOPOLOGY_INIT event can not yet be sent.
+     * @see ViewStateManager#eventListeners
+     */
+    private List<TopologyEventListener> unInitializedEventListeners = new ArrayList<TopologyEventListener>();
+    
+    /** 
+     * Set true when the bundle.activate() was called, false if not yet or when deactivate() is called.
+     * <p>
+     * This controls whether handleChanging() and handleNewView() should cause any events
+     * to be sent - which they do not if called before handleActivated() (or after handleDeactivated())
+     * @see ViewStateManager#handleActivated()
+     * @see ViewStateManager#handleChanging()
+     * @see ViewStateManager#handleNewView(BaseTopologyView)
+     * @see ViewStateManager#handleDeactivated()
+     */
+    private boolean activated;
+    
+    /**
+     * Represents the 'newView' passed to handleNewTopologyView at the most recent invocation.
+     * <p>
+     * This is used for:
+     * <ul>
+     *  <li>sending with the TOPOLOGY_INIT event to newly bound listeners or at activate time</li>
+     *  <li>sending as oldView (marked not current) with the TOPOLOGY_CHANGING event</li>
+     *  <li>sending as oldView (marked not current in case handleChanging() was not invoked) with the TOPOLOGY_CHANGED event</li>
+     * </ul>
+     */
+    private BaseTopologyView previousView;
+    
+    /**
+     * Set to true when handleChanging is called - set to false in handleNewView.
+     * When this goes true, a TOPOLOGY_CHANGING is sent.
+     * When this goes false, a TOPOLOGY_CHANGED is sent.
+     */
+    private boolean isChanging;
+    
+    /** 
+     * Binds the given eventListener, sending it an INIT event if applicable.
+     * <p>
+     * Note: no synchronization done in ViewStateManager, <b>must</b> be done externally
+     * @param eventListener the eventListener that is to bind
+     */
+    public void bind(final TopologyEventListener eventListener) {
+
+        logger.debug("bind: Binding TopologyEventListener {}",
+                eventListener);
+        
+        if (eventListeners.contains(eventListener) || unInitializedEventListeners.contains(eventListener)) {
+            logger.info("bind: TopologyEventListener already registered: "+eventListener);
+            return;
+        }
+
+        if (activated) {
+            // check to see in which state we are
+            if (isChanging || (previousView==null)) {
+                // then we cannot send the TOPOLOGY_INIT at this point - need to delay this
+                unInitializedEventListeners.add(eventListener);
+            } else {
+                // otherwise we can send the TOPOLOGY_INIT now
+                sendEvent(eventListener, newInitEvent(previousView));
+                eventListeners.add(eventListener);
+            }
+        } else {
+            unInitializedEventListeners.add(eventListener);
+        }
+    }
+    
+    /** 
+     * Unbinds the given eventListener, returning whether or not it was bound at all.
+     * <p>
+     * Note: no synchronization done in ViewStateManager, <b>must</b> be done externally
+     * @param eventListener the eventListner that is to unbind
+     * @return whether or not the listener was added in the first place 
+     */
+    public boolean unbind(final TopologyEventListener eventListener) {
+
+        logger.debug("unbind: Releasing TopologyEventListener {}",
+                eventListener);
+
+        // even though a listener must always only ever exist in one of the two,
+        // the unbind we do - for safety-paranoia-reasons - remove them from both
+        final boolean a = eventListeners.remove(eventListener);
+        final boolean b = unInitializedEventListeners.remove(eventListener);
+        return a || b;
+    }
+    
+    /** Internal helper method that sends a given event to a list of listeners **/
+    private void sendEvent(final List<TopologyEventListener> audience, final TopologyEvent event) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("sendEvent: sending topologyEvent {}, to all ({}) listeners", event, audience.size());
+        }
+        for (Iterator<TopologyEventListener> it = audience.iterator(); it.hasNext();) {
+            TopologyEventListener topologyEventListener = it.next();
+            sendEvent(topologyEventListener, event);
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("sendEvent: sent topologyEvent {}, to all ({}) listeners", event, audience.size());
+        }
+    }
+    
+    /** Internal helper method that sends a given event to a particular listener **/
+    private void sendEvent(final TopologyEventListener da, final TopologyEvent event) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("sendEvent: sending topologyEvent {}, to {}", event, da);
+        }
+        try{
+            da.handleTopologyEvent(event);
+        } catch(final Exception e) {
+            logger.warn("sendEvent: handler threw exception. handler: "+da+", exception: "+e, e);
+        }
+    }
+
+    /** Note: no synchronization done in ViewStateManager, <b>must</b> be done externally **/
+    public void handleActivated() {
+        logger.debug("handleActivated: activating the ViewStateManager");
+        activated = true;
+        
+        if (previousView!=null && !isChanging) {
+            sendEvent(unInitializedEventListeners, newInitEvent(previousView));
+            eventListeners.addAll(unInitializedEventListeners);
+            unInitializedEventListeners.clear();
+        }
+        logger.debug("handleActivated: activated the ViewStateManager");
+    }
+
+    /** Simple factory method for creating a TOPOLOGY_INIT event with the given newView **/
+    public static TopologyEvent newInitEvent(final BaseTopologyView newView) {
+        if (newView==null) {
+            throw new IllegalStateException("newView must not be null");
+        }
+        if (!newView.isCurrent()) {
+            throw new IllegalStateException("newView must be current");
+        }
+        return new TopologyEvent(Type.TOPOLOGY_INIT, null, newView);
+    }
+    
+    /** Simple factory method for creating a TOPOLOGY_CHANGING event with the given oldView **/
+    public static TopologyEvent newChangingEvent(final BaseTopologyView oldView) {
+        if (oldView==null) {
+            throw new IllegalStateException("oldView must not be null");
+        }
+        if (oldView.isCurrent()) {
+            throw new IllegalStateException("oldView must not be current");
+        }
+        return new TopologyEvent(Type.TOPOLOGY_CHANGING, oldView, null);
+    }
+    
+    /** Simple factory method for creating a TOPOLOGY_CHANGED event with the given old and new views **/
+    public static TopologyEvent newChangedEvent(final BaseTopologyView oldView, final BaseTopologyView newView) {
+        if (oldView==null) {
+            throw new IllegalStateException("oldView must not be null");
+        }
+        if (oldView.isCurrent()) {
+            throw new IllegalStateException("oldView must not be current");
+        }
+        if (newView==null) {
+            throw new IllegalStateException("newView must not be null");
+        }
+        if (!newView.isCurrent()) {
+            throw new IllegalStateException("newView must be current");
+        }
+        return new TopologyEvent(Type.TOPOLOGY_CHANGED, oldView, newView);
+    }
+
+    /** 
+     * Must be called when the corresponding service (typically a DiscoveryService implementation)
+     * is deactivated.
+     * <p>
+     * Will mark this manager as deactivated and flags the last available view as not current.
+     * <p>
+     * Note: no synchronization done in ViewStateManager, <b>must</b> be done externally 
+     */
+    public void handleDeactivated() {
+        logger.debug("handleDeactivated: deactivating the ViewStateManager");
+        activated = false;
+
+        if (previousView!=null) {
+            previousView.setNotCurrent();
+            previousView = null;
+        }
+        isChanging = false;
+        
+        eventListeners.clear();
+        unInitializedEventListeners.clear();
+        logger.debug("handleDeactivated: deactivated the ViewStateManager");
+    }
+    
+    /** Note: no synchronization done in ViewStateManager, <b>must</b> be done externally **/
+    public void handleChanging() {
+        logger.debug("handleChanging: start");
+
+        if (isChanging) {
+            // if isChanging: then this is no news
+            // hence: return asap
+            logger.debug("handleChanging: was already changing - ignoring.");
+            return;
+        }
+        
+        // whether activated or not: set isChanging to true now
+        isChanging = true;
+        
+        if (!activated) {
+            // if not activated: we can only start sending events once activated
+            // hence returning here - after isChanging was set to true accordingly
+            
+            // note however, that if !activated, there should be no eventListeners yet
+            // all of them should be in unInitializedEventListeners at the moment
+            // waiting for activate() and handleNewTopologyView
+            logger.debug("handleChanging: not yet activated - ignoring.");
+            return;
+        }
+        
+        if (previousView==null) {
+            // then nothing further to do - this is a very early changing event
+            // before even the first view was available
+            logger.debug("handleChanging: no previousView set - ignoring.");
+            return;
+        }
+        
+        logger.debug("handleChanging: sending TOPOLOGY_CHANGING to initialized listeners");
+        previousView.setNotCurrent();
+        sendEvent(eventListeners, newChangingEvent(previousView));
+        logger.debug("handleChanging: end");
+    }
+    
+    /** Note: no synchronization done in ViewStateManager, <b>must</b> be done externally **/
+    public void handleNewView(BaseTopologyView newView) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("handleNewView: start, newView={}", newView);
+        }
+        if (!newView.isCurrent()) {
+            logger.error("handleNewView: newView must be current");
+            throw new IllegalArgumentException("newView must be current");
+        }
+        
+        if (!isChanging) {
+            // verify if there is actually a change between previousView and newView
+            // if there isn't, then there is not much point in sending a CHANGING/CHANGED tuple
+            // at all
+            if (previousView!=null && previousView.equals(newView)) {
+                // then nothing to send - the view has not changed, and we haven't
+                // sent the CHANGING event - so we should not do anything here
+                logger.debug("handleNewView: we were not in changing state and new view matches old, so - ignoring");
+                return;
+            }
+            logger.debug("handleNewView: simulating a handleChanging as we were not in changing state");
+            handleChanging();
+            logger.debug("handleNewView: simulation of a handleChanging done");
+        }
+
+        // whether activated or not: set isChanging to false, first thing
+        isChanging = false;
+        
+        if (!activated) {
+            // then all we can do is to pass this on to previoueView
+            previousView = newView;
+            // other than that, we can't currently send any event, before activate
+            logger.debug("handleNewView: not yet activated - ignoring");
+            return;
+        }
+        
+        if (previousView==null) {
+            // this is the first time handleNewTopologyView is called
+            
+            if (eventListeners.size()>0) {
+                logger.info("handleNewTopologyView: no previous view available even though listeners already got CHANGED event");
+            }
+            
+            // otherwise this is the normal case where there are uninitialized event listeners waiting below
+                
+        } else {
+            logger.debug("handleNewView: sending TOPOLOGY_CHANGED to initialized listeners");
+            previousView.setNotCurrent();
+            sendEvent(eventListeners, newChangedEvent(previousView, newView));
+        }
+        
+        if (unInitializedEventListeners.size()>0) {
+            // then there were bindTopologyEventListener calls coming in while
+            // we were in CHANGING state - so we must send those the INIT they were 
+            // waiting for oh so long
+            if (logger.isDebugEnabled()) {
+                logger.debug("handleNewView: sending TOPOLOGY_INIT to uninitialized listeners ({})", 
+                        unInitializedEventListeners.size());
+            }
+            sendEvent(unInitializedEventListeners, newInitEvent(newView));
+            eventListeners.addAll(unInitializedEventListeners);
+            unInitializedEventListeners.clear();
+        }
+        
+        previousView = newView;
+        logger.debug("handleNewView: end");
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
new file mode 100644
index 0000000..c254e70
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/**
+ * Provides commons utility for the Discovery API.
+ *
+ * @version 1.0.0
+ */
+@Version("1.0.0")
+package org.apache.sling.discovery.commons.providers;
+
+import aQute.bnd.annotation.Version;
+
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
new file mode 100644
index 0000000..3179438
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
@@ -0,0 +1,569 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.Set;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.ViewStateManager;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestViewStateManager {
+
+    private class Listener implements TopologyEventListener {
+
+        private List<TopologyEvent> events = new LinkedList<TopologyEvent>();
+        private TopologyEvent lastEvent;
+        
+        public synchronized void handleTopologyEvent(TopologyEvent event) {
+            events.add(event);
+            lastEvent = event;
+        }
+        
+        public synchronized List<TopologyEvent> getEvents() {
+            return Collections.unmodifiableList(events);
+        }
+
+        public synchronized int countEvents() {
+            return events.size();
+        }
+        
+        public synchronized TopologyEvent getLastEvent() {
+            return lastEvent;
+        }
+
+        public synchronized void clearEvents() {
+            events.clear();
+        }
+
+        public BaseTopologyView getLastView() {
+            if (lastEvent==null) {
+                return null;
+            } else {
+                switch(lastEvent.getType()) {
+                case TOPOLOGY_INIT:
+                case PROPERTIES_CHANGED:
+                case TOPOLOGY_CHANGED: {
+                    return (BaseTopologyView) lastEvent.getNewView();
+                }
+                case TOPOLOGY_CHANGING:{
+                    return (BaseTopologyView) lastEvent.getOldView();
+                }
+                default: {
+                    fail("no other types supported yet");
+                }
+                }
+            }
+            return null;
+        }
+        
+    }
+    
+    private class View extends BaseTopologyView {
+        
+        private final BaseTopologyView clonedView;
+
+        public View() {
+            clonedView = null;
+        }
+        
+        public View(BaseTopologyView clonedView) {
+            this.clonedView = clonedView;
+        }
+        
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof View)) {
+                return false;
+            }
+            final View other = (View) obj;
+            if (clonedView!=null) {
+                if (obj==clonedView) {
+                    return true;
+                }
+            } else if (other.clonedView==this) {
+                return true;
+            }
+            return super.equals(obj);
+        }
+        
+        @Override
+        public int hashCode() {
+            if (clonedView!=null) {
+                return clonedView.hashCode();
+            }
+            return super.hashCode();
+        }
+
+        public View addInstance() {
+            return this;
+        }
+
+        public InstanceDescription getLocalInstance() {
+            throw new IllegalStateException("not yet implemented");
+        }
+
+        public Set<InstanceDescription> getInstances() {
+            throw new IllegalStateException("not yet implemented");
+        }
+
+        public Set<InstanceDescription> findInstances(InstanceFilter filter) {
+            throw new IllegalStateException("not yet implemented");
+        }
+
+        public Set<ClusterView> getClusterViews() {
+            throw new IllegalStateException("not yet implemented");
+        }
+    }
+    
+    private ViewStateManager mgr;
+    
+    private Random defaultRandom;
+
+    @Before
+    public void setup() throws Exception {
+        mgr = new ViewStateManager();
+        defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
+    }
+    
+    @After
+    public void teardown() throws Exception {
+        mgr = null;
+        defaultRandom= null;
+    }
+    
+    private void assertNoEvents(Listener listener) {
+        assertEquals(0, listener.countEvents());
+    }
+    
+    private void assertEvents(Listener listener, TopologyEvent... events) {
+        assertEquals(events.length, listener.countEvents());
+        for (int i = 0; i < events.length; i++) {
+            TopologyEvent e = events[i];
+            assertEquals(e.getType(), listener.getEvents().get(i).getType());
+            switch(e.getType()) {
+            case TOPOLOGY_INIT: {
+                assertNull(listener.getEvents().get(i).getOldView());
+                assertEquals(e.getNewView(), listener.getEvents().get(i).getNewView());
+                break;
+            }
+            case TOPOLOGY_CHANGING: {
+                assertEquals(e.getOldView(), listener.getEvents().get(i).getOldView());
+                assertNull(listener.getEvents().get(i).getNewView());
+                break;
+            }
+            case PROPERTIES_CHANGED:
+            case TOPOLOGY_CHANGED: {
+                assertEquals(e.getOldView(), listener.getEvents().get(i).getOldView());
+                assertEquals(e.getNewView(), listener.getEvents().get(i).getNewView());
+                break;
+            }
+            default: {
+                fail("no other type supported yet");
+            }
+            }
+        }
+        listener.clearEvents();
+    }
+
+    /** does couple loops randomly calling handleChanging() (or not) and then handleNewView().
+     * Note: random is passed to allow customizing and not hardcoding this method to a particular random **/
+    private void randomEventLoop(final Random random, Listener... listeners) {
+        for(int i=0; i<100; i++) {
+            final boolean shouldCallChanging = random.nextBoolean();
+            if (shouldCallChanging) {
+                // dont always do a changing
+                mgr.handleChanging();
+                for(int j=0; j<listeners.length; j++) {
+                    assertEvents(listeners[j], ViewStateManager.newChangingEvent(listeners[j].getLastView()));
+                }
+            } else {
+                for(int j=0; j<listeners.length; j++) {
+                    assertNoEvents(listeners[j]);
+                }
+            }
+            final BaseTopologyView view = new View().addInstance();
+            BaseTopologyView[] lastViews = new BaseTopologyView[listeners.length];
+            for(int j=0; j<listeners.length; j++) {
+                lastViews[j] = listeners[j].getLastView();
+            }
+            mgr.handleNewView(view);
+            if (!shouldCallChanging) {
+                // in that case I should still get a CHANGING - by contract
+                for(int j=0; j<listeners.length; j++) {
+                    assertEvents(listeners[j], ViewStateManager.newChangingEvent(lastViews[j]), ViewStateManager.newChangedEvent(lastViews[j], view));
+                }
+            } else {
+                for(int j=0; j<listeners.length; j++) {
+                    assertEvents(listeners[j], ViewStateManager.newChangedEvent(lastViews[j], view));
+                }
+            }
+        }
+    }
+    
+    @Test
+    public void testDuplicateListeners() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        mgr.bind(listener); // we should be generous and allow duplicate registration
+        assertTrue(mgr.unbind(listener));
+        assertFalse(mgr.unbind(listener));
+        
+        mgr.handleActivated();
+        assertFalse(mgr.unbind(listener));
+        mgr.bind(listener);
+        mgr.bind(listener); // we should be generous and allow duplicate registration
+        assertTrue(mgr.unbind(listener));
+        assertFalse(mgr.unbind(listener));
+    }
+    
+    @Test
+    public void testBindActivateChangingChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangingActivateChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangingChangedActivate() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangingChangedChangingActivate() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view2 = new View().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, ViewStateManager.newInitEvent(view2));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangedChangingActivate() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view2 = new View().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, ViewStateManager.newInitEvent(view2));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testActivateBindChangingChanged() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        assertNoEvents(listener); // paranoia
+        // then bind
+        mgr.bind(listener);
+        assertNoEvents(listener); // there was no changing or changed yet
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+
+    @Test
+    public void testActivateChangingBindChanged() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        assertNoEvents(listener); // paranoia
+        mgr.handleChanging();
+        assertNoEvents(listener); // no listener yet
+        // then bind
+        mgr.bind(listener);
+        assertNoEvents(listener); // no changed event yet
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+
+    @Test
+    public void testActivateChangingChangedBind() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        assertNoEvents(listener); // paranoia
+        mgr.handleChanging();
+        assertNoEvents(listener); // no listener yet
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertNoEvents(listener); // no listener yet
+        // then bind
+        mgr.bind(listener);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindActivateBindChangingChanged() throws Exception {
+        final Listener listener1 = new Listener();
+        final Listener listener2 = new Listener();
+        
+        mgr.bind(listener1);
+        assertNoEvents(listener1);
+        mgr.handleActivated();
+        assertNoEvents(listener1);
+        mgr.bind(listener2);
+        assertNoEvents(listener1);
+        assertNoEvents(listener2);
+        mgr.handleChanging();
+        assertNoEvents(listener1);
+        assertNoEvents(listener2);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener1, ViewStateManager.newInitEvent(view));
+        assertEvents(listener2, ViewStateManager.newInitEvent(view));
+        
+        randomEventLoop(defaultRandom, listener1, listener2);
+    }
+
+    @Test
+    public void testBindActivateChangingBindChanged() throws Exception {
+        final Listener listener1 = new Listener();
+        final Listener listener2 = new Listener();
+        
+        mgr.bind(listener1);
+        assertNoEvents(listener1);
+        mgr.handleActivated();
+        assertNoEvents(listener1);
+        mgr.handleChanging();
+        assertNoEvents(listener1);
+        mgr.bind(listener2);
+        assertNoEvents(listener1);
+        assertNoEvents(listener2);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener1, ViewStateManager.newInitEvent(view));
+        assertEvents(listener2, ViewStateManager.newInitEvent(view));
+
+        randomEventLoop(defaultRandom, listener1, listener2);
+    }
+    
+    @Test
+    public void testActivateBindChangingDuplicateHandleNewView() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        mgr.bind(listener);
+        mgr.handleChanging();
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        mgr.handleNewView(clone(view));
+        assertNoEvents(listener);
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testActivateBindChangingChangedBindDuplicateHandleNewView() throws Exception {
+        final Listener listener1 = new Listener();
+        mgr.handleActivated();
+        mgr.bind(listener1);
+        mgr.handleChanging();
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener1, ViewStateManager.newInitEvent(view));
+        
+        final Listener listener2 = new Listener();
+        mgr.bind(listener2);
+        mgr.handleNewView(clone(view));
+        assertNoEvents(listener1);
+        assertEvents(listener2, ViewStateManager.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener1, listener2);
+    }
+    
+    @Test
+    public void testActivateChangedBindDuplicateHandleNewView() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new View().addInstance();
+        mgr.handleNewView(view);
+        assertNoEvents(listener);
+        mgr.bind(listener);
+        assertEvents(listener, ViewStateManager.newInitEvent(view));
+        mgr.handleNewView(clone(view));
+        assertNoEvents(listener);
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindActivateChangedChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view1 = new View().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, ViewStateManager.newInitEvent(view1));
+        final BaseTopologyView view2 = new View().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, ViewStateManager.newChangingEvent(view1), ViewStateManager.newChangedEvent(view1, view2));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindActivateChangedDeactivateChangingActivateChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view1 = new View().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, ViewStateManager.newInitEvent(view1));
+        mgr.handleDeactivated();
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        mgr.bind(listener); // need to bind again after deactivate
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view2 = new View().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, ViewStateManager.newInitEvent(view2));
+    }
+
+    @Test
+    public void testBindActivateChangedDeactivateChangedActivateChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view1 = new View().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, ViewStateManager.newInitEvent(view1));
+        mgr.handleDeactivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view2 = new View().addInstance();
+        mgr.handleNewView(view2);
+        assertNoEvents(listener);
+        mgr.bind(listener); // need to bind again after deactivate
+        mgr.handleActivated();
+        assertEvents(listener, ViewStateManager.newInitEvent(view2));
+        final BaseTopologyView view3 = new View().addInstance();
+        mgr.handleNewView(view3);
+        assertEvents(listener, ViewStateManager.newChangingEvent(view2), ViewStateManager.newChangedEvent(view2, view3));
+    }
+
+    @Test
+    public void testBindActivateChangedChangingDeactivateActivateChangingChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        assertNoEvents(listener);
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        final BaseTopologyView view1 = new View().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, ViewStateManager.newInitEvent(view1));
+        mgr.handleChanging();
+        assertEvents(listener, ViewStateManager.newChangingEvent(view1));
+        mgr.handleDeactivated();
+        assertNoEvents(listener);
+        mgr.bind(listener); // need to bind again after deactivate
+        mgr.handleActivated();
+        assertNoEvents(listener);
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view2 = new View().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, ViewStateManager.newInitEvent(view2));
+    }
+
+    private BaseTopologyView clone(final BaseTopologyView view) {
+        return new View(view);
+    }
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 07/38: set parent version to 24 and add empty relativePath where missing

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 0035b7d9c3f5dab4f417365fad80ff7eb413abe8
Author: Oliver Lietz <ol...@apache.org>
AuthorDate: Tue Jul 7 08:09:17 2015 +0000

    set parent version to 24 and add empty relativePath where missing
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1689593 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 3878ea6..814dabf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>23</version>
+        <version>24</version>
         <relativePath/>
     </parent>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 16/38: SLING-5173 related : EventFactory renamed to EventHelper - and introduced toShortString() for a few base view classes to shorten and make the log output more readable

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit cf6bb536e7010452d7a109892261d3f9bbb1ae94
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 11:03:11 2015 +0000

    SLING-5173 related : EventFactory renamed to EventHelper - and introduced toShortString() for a few base view classes to shorten and make the log output more readable
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709794 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/BaseTopologyView.java        | 20 +++++++
 .../{EventFactory.java => EventHelper.java}        | 32 +++++++++-
 .../providers/base/ViewStateManagerImpl.java       | 18 +++---
 .../commons/providers/EventFactoryTest.java        | 30 +++++-----
 .../commons/providers/base/TestHelper.java         |  8 +--
 .../providers/base/TestMinEventDelayHandler.java   | 10 ++--
 .../providers/base/TestViewStateManager.java       | 70 +++++++++++-----------
 7 files changed, 118 insertions(+), 70 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
index e8796b4..6464ffc 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.discovery.commons.providers;
 
+import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.TopologyView;
 
 /**
@@ -82,4 +83,23 @@ public abstract class BaseTopologyView implements TopologyView {
      */
     public abstract String getLocalClusterSyncTokenId();
 
+    public String toShortString() {
+        StringBuffer sb = new StringBuffer();
+        for (InstanceDescription instance : getInstances()) {
+            if (sb.length()!=0) {
+                sb.append(",");
+            }
+            sb.append(instance.getSlingId());
+            sb.append("[");
+            sb.append("local=");
+            sb.append(instance.isLocal());
+            sb.append(",leader=");
+            sb.append(instance.isLeader());
+            sb.append("]");
+        }
+        return "DefaultTopologyView[current=" + isCurrent() 
+            + ", num=" + getInstances().size() 
+            + ", instances=" + sb.toString() + "]";
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/EventFactory.java b/src/main/java/org/apache/sling/discovery/commons/providers/EventHelper.java
similarity index 71%
rename from src/main/java/org/apache/sling/discovery/commons/providers/EventFactory.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/EventHelper.java
index 9d95689..8be1482 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/EventFactory.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/EventHelper.java
@@ -20,9 +20,11 @@ package org.apache.sling.discovery.commons.providers;
 
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyView;
 
-/** Factory for creating TopologyEvents with BaseTopologyView **/
-public class EventFactory {
+/** Contains static factory methods for creating TopologyEvents with BaseTopologyView
+ *  as well as some TopologyEvent related helper methods**/
+public class EventHelper {
 
     /** Simple factory method for creating a TOPOLOGY_INIT event with the given newView **/
     public static TopologyEvent newInitEvent(final BaseTopologyView newView) {
@@ -78,5 +80,31 @@ public class EventFactory {
         }
         return new TopologyEvent(Type.PROPERTIES_CHANGED, oldView, newView);
     }
+    
+    /**
+     * Returns a shorter toString than the default TopologyEvent.toString()
+     * which can be rather large and unusable in log files
+     */
+    public static String toShortString(TopologyEvent event) {
+        final TopologyView oldView = event.getOldView();
+        final TopologyView newView = event.getNewView();
+        final String oldViewToString;
+        final String newViewtoString;
+        if (oldView instanceof BaseTopologyView) {
+            final BaseTopologyView baseOldView = (BaseTopologyView)oldView;
+            oldViewToString = baseOldView.toShortString();
+        } else {
+            oldViewToString = String.valueOf(oldView);
+        }
+        if (newView instanceof BaseTopologyView) {
+            final BaseTopologyView baseNewView = (BaseTopologyView)newView;
+            newViewtoString = baseNewView.toShortString();
+        } else {
+            newViewtoString = String.valueOf(newView);
+        }
+        return "TopologyEvent [type=" + event.getType() 
+            + ", oldView=" + oldViewToString
+            + ", newView=" + newViewtoString + "]";
+    }
 
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index 310fd7e..b5fa9fa 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -36,7 +36,7 @@ import org.apache.sling.discovery.TopologyEventListener;
 import org.apache.sling.discovery.commons.InstancesDiff;
 import org.apache.sling.discovery.commons.InstancesDiff.InstanceCollection;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
-import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.slf4j.Logger;
@@ -204,7 +204,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                     // otherwise we can send the TOPOLOGY_INIT now
                     logger.debug("bind: view is defined, sending INIT now to {}",
                             eventListener);
-                    enqueue(eventListener, EventFactory.newInitEvent(previousView), true);
+                    enqueue(eventListener, EventHelper.newInitEvent(previousView), true);
                     eventListeners.add(eventListener);
                 }
             } else {
@@ -258,7 +258,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
             return;
         }
         if (logInfo) {
-            logger.info("enqueue: enqueuing topologyEvent {}, to {}", event, da);
+            logger.info("enqueue: enqueuing topologyEvent {}, to {}", EventHelper.toShortString(event), da);
         } else {
             logger.debug("enqueue: enqueuing topologyEvent {}, to {}", event, da);
         }
@@ -269,7 +269,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
 
     /** Internal helper method that sends a given event to a list of listeners **/
     private void enqueueForAll(final List<TopologyEventListener> audience, final TopologyEvent event) {
-        logger.info("enqueueForAll: sending topologyEvent {}, to all ({}) listeners", event, audience.size());
+        logger.info("enqueueForAll: sending topologyEvent {}, to all ({}) listeners", EventHelper.toShortString(event), audience.size());
         for (Iterator<TopologyEventListener> it = audience.iterator(); it.hasNext();) {
             TopologyEventListener topologyEventListener = it.next();
             enqueue(topologyEventListener, event, false);
@@ -300,7 +300,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
             th.start();
 
             if (previousView!=null && !isChanging) {
-                enqueueForAll(unInitializedEventListeners, EventFactory.newInitEvent(previousView));
+                enqueueForAll(unInitializedEventListeners, EventHelper.newInitEvent(previousView));
                 eventListeners.addAll(unInitializedEventListeners);
                 unInitializedEventListeners.clear();
             }
@@ -387,7 +387,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
             
             logger.debug("handleChanging: sending TOPOLOGY_CHANGING to initialized listeners");
             previousView.setNotCurrent();
-            enqueueForAll(eventListeners, EventFactory.newChangingEvent(previousView));
+            enqueueForAll(eventListeners, EventHelper.newChangingEvent(previousView));
         } finally {
             lock.unlock();
             logger.trace("handleChanging: finally");
@@ -476,7 +476,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                 // and that one does not go via consistencyservice
                 logger.info("handleNewViewNonDelayed: properties changed to: "+newView);
                 previousView.setNotCurrent();
-                enqueueForAll(eventListeners, EventFactory.newPropertiesChangedEvent(previousView, newView));
+                enqueueForAll(eventListeners, EventHelper.newPropertiesChangedEvent(previousView, newView));
                 logger.trace("handleNewViewNonDelayed: setting previousView to {}", newView);
                 previousView = newView;
                 return true;
@@ -608,7 +608,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
         } else {
             logger.debug("doHandleConsistent: sending TOPOLOGY_CHANGED to initialized listeners");
             previousView.setNotCurrent();
-            enqueueForAll(eventListeners, EventFactory.newChangedEvent(previousView, newView));
+            enqueueForAll(eventListeners, EventHelper.newChangedEvent(previousView, newView));
         }
         
         if (unInitializedEventListeners.size()>0) {
@@ -617,7 +617,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
             // waiting for oh so long
             logger.debug("doHandleConsistent: sending TOPOLOGY_INIT to uninitialized listeners ({})", 
                     unInitializedEventListeners.size());
-            enqueueForAll(unInitializedEventListeners, EventFactory.newInitEvent(newView));
+            enqueueForAll(unInitializedEventListeners, EventHelper.newInitEvent(newView));
             eventListeners.addAll(unInitializedEventListeners);
             unInitializedEventListeners.clear();
         }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java
index eea02e1..42a3e57 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java
@@ -31,55 +31,55 @@ public class EventFactoryTest {
     @Test
     public void testInitEvent() throws Exception {
         try{
-            EventFactory.newInitEvent(null);
+            EventHelper.newInitEvent(null);
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
-        EventFactory.newInitEvent(newView());
+        EventHelper.newInitEvent(newView());
     }
     
     @Test
     public void testChangingEvent() throws Exception {
         try{
-            EventFactory.newChangingEvent(null);
+            EventHelper.newChangingEvent(null);
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newChangingEvent(newView());
+            EventHelper.newChangingEvent(newView());
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         BaseTopologyView view = newView();
         view.setNotCurrent();
-        EventFactory.newChangingEvent(view);
+        EventHelper.newChangingEvent(view);
     }
 
     @Test
     public void testChangedEvent() throws Exception {
         try{
-            EventFactory.newChangedEvent(null, null);
+            EventHelper.newChangedEvent(null, null);
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newChangedEvent(newView(), null);
+            EventHelper.newChangedEvent(newView(), null);
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newChangedEvent(null, newView());
+            EventHelper.newChangedEvent(null, newView());
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newChangedEvent(newView(), newView());
+            EventHelper.newChangedEvent(newView(), newView());
             fail("should complain");
         } catch(Exception e) {
             // ok
@@ -87,31 +87,31 @@ public class EventFactoryTest {
         BaseTopologyView oldView = newView();
         oldView.setNotCurrent();
         BaseTopologyView newView = newView();
-        EventFactory.newChangedEvent(oldView, newView);
+        EventHelper.newChangedEvent(oldView, newView);
     }
     
     @Test
     public void testPropertiesEvent() throws Exception {
         try{
-            EventFactory.newPropertiesChangedEvent(null, null);
+            EventHelper.newPropertiesChangedEvent(null, null);
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newPropertiesChangedEvent(newView(), null);
+            EventHelper.newPropertiesChangedEvent(newView(), null);
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newPropertiesChangedEvent(null, newView());
+            EventHelper.newPropertiesChangedEvent(null, newView());
             fail("should complain");
         } catch(Exception e) {
             // ok
         }
         try{
-            EventFactory.newPropertiesChangedEvent(newView(), newView());
+            EventHelper.newPropertiesChangedEvent(newView(), newView());
             fail("should complain");
         } catch(Exception e) {
             // ok
@@ -119,6 +119,6 @@ public class EventFactoryTest {
         BaseTopologyView oldView = newView();
         oldView.setNotCurrent();
         BaseTopologyView newView = newView();
-        EventFactory.newPropertiesChangedEvent(oldView, newView);
+        EventHelper.newPropertiesChangedEvent(oldView, newView);
     }
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
index f77dc21..23b6821 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
@@ -31,7 +31,7 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
-import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -110,7 +110,7 @@ public class TestHelper {
                 waitForAsyncEvents(mgr);
                 logger.debug("randomEventLoop: asserting CHANGING event was sent...");
                 for(int j=0; j<listeners.length; j++) {
-                    assertEvents(mgr, listeners[j], EventFactory.newChangingEvent(listeners[j].getLastView()));
+                    assertEvents(mgr, listeners[j], EventHelper.newChangingEvent(listeners[j].getLastView()));
                 }
             } else {
                 logger.debug("randomEventLoop: asserting no events...");
@@ -138,12 +138,12 @@ public class TestHelper {
                 // in that case I should still get a CHANGING - by contract
                 logger.debug("randomEventLoop: asserting CHANGING, CHANGED events were sent");
                 for(int j=0; j<listeners.length; j++) {
-                    assertEvents(mgr, listeners[j], EventFactory.newChangingEvent(lastViews[j]), EventFactory.newChangedEvent(lastViews[j], view));
+                    assertEvents(mgr, listeners[j], EventHelper.newChangingEvent(lastViews[j]), EventHelper.newChangedEvent(lastViews[j], view));
                 }
             } else {
                 logger.debug("randomEventLoop: asserting CHANGED event was sent");
                 for(int j=0; j<listeners.length; j++) {
-                    assertEvents(mgr, listeners[j], EventFactory.newChangedEvent(lastViews[j], clonedView));
+                    assertEvents(mgr, listeners[j], EventHelper.newChangedEvent(lastViews[j], clonedView));
                 }
             }
         }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
index a4974df..84ec22f 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
@@ -30,7 +30,7 @@ import org.apache.log4j.LogManager;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
-import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
@@ -102,7 +102,7 @@ public class TestMinEventDelayHandler {
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         logger.info("testNormalDelaying: calling handleNewView...");
         mgr.handleNewView(view);
-        TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
+        TestHelper.assertEvents(mgr, listener, EventHelper.newInitEvent(view));
         for(int i=0; i<7; i++) {
             logger.info("testNormalDelaying: calling randomEventLoop...");
             TestHelper.randomEventLoop(mgr, sds, 4, 1500, defaultRandom, listener);
@@ -124,7 +124,7 @@ public class TestMinEventDelayHandler {
         assertNoEvents(listener);
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
+        TestHelper.assertEvents(mgr, listener, EventHelper.newInitEvent(view));
         for(int i=0; i<7; i++) {
             TestHelper.randomEventLoop(mgr, sds, 100, -1, defaultRandom, listener);
             Thread.sleep(1000);
@@ -150,13 +150,13 @@ public class TestMinEventDelayHandler {
         DummyTopologyView clonedView = view.clone();
         logger.info("testLongMinDelay: calling handleNewView...");
         mgr.handleNewView(view);
-        TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
+        TestHelper.assertEvents(mgr, listener, EventHelper.newInitEvent(view));
         final DummyTopologyView view2 = new DummyTopologyView().addInstance();
         view2.addInstance(UUID.randomUUID().toString(), (DefaultClusterView) view2.getLocalInstance().getClusterView(), false, false);
         logger.info("testLongMinDelay: calling handleNewView...");
         clonedView.setNotCurrent();
         mgr.handleNewView(view2);
-        TestHelper.assertEvents(mgr, listener, EventFactory.newChangingEvent(clonedView));
+        TestHelper.assertEvents(mgr, listener, EventHelper.newChangingEvent(clonedView));
         assertFalse(view.isCurrent());
     }
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index 5901c11..df90dfc 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -35,7 +35,7 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
-import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
@@ -133,7 +133,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -148,7 +148,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -163,7 +163,7 @@ public class TestViewStateManager {
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -183,7 +183,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
-        assertEvents(listener, EventFactory.newInitEvent(view2));
+        assertEvents(listener, EventHelper.newInitEvent(view2));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -201,7 +201,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
-        assertEvents(listener, EventFactory.newInitEvent(view2));
+        assertEvents(listener, EventHelper.newInitEvent(view2));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -218,7 +218,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
     }
 
@@ -233,7 +233,7 @@ public class TestViewStateManager {
                 (DefaultInstanceDescription) oldView.getLocalInstance();
         localInstance.setProperty("foo", "bar1");
         mgr.handleNewView(oldView);
-        TopologyEvent initEvent = EventFactory.newInitEvent(oldView.clone());
+        TopologyEvent initEvent = EventHelper.newInitEvent(oldView.clone());
         assertEvents(listener, initEvent);
         DummyTopologyView newView = oldView.clone();
         oldView.setNotCurrent();
@@ -241,7 +241,7 @@ public class TestViewStateManager {
         localInstance.setProperty("foo", "bar2");
         mgr.handleNewView(newView);
         Thread.sleep(2000);
-        TopologyEvent propertiesChangedEvent = EventFactory.newPropertiesChangedEvent(oldView.clone(), newView.clone());
+        TopologyEvent propertiesChangedEvent = EventHelper.newPropertiesChangedEvent(oldView.clone(), newView.clone());
         assertEvents(listener, propertiesChangedEvent);
     }
 
@@ -258,7 +258,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener); // no changed event yet
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
     }
 
@@ -275,7 +275,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener); // no listener yet
         // then bind
         mgr.bind(listener);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -296,8 +296,8 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener2);
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener1, EventFactory.newInitEvent(view));
-        assertEvents(listener2, EventFactory.newInitEvent(view));
+        assertEvents(listener1, EventHelper.newInitEvent(view));
+        assertEvents(listener2, EventHelper.newInitEvent(view));
         
         randomEventLoop(defaultRandom, listener1, listener2);
     }
@@ -318,8 +318,8 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener2);
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener1, EventFactory.newInitEvent(view));
-        assertEvents(listener2, EventFactory.newInitEvent(view));
+        assertEvents(listener1, EventHelper.newInitEvent(view));
+        assertEvents(listener2, EventHelper.newInitEvent(view));
 
         randomEventLoop(defaultRandom, listener1, listener2);
     }
@@ -332,7 +332,7 @@ public class TestViewStateManager {
         mgr.handleChanging();
         final DummyTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         mgr.handleNewView(DummyTopologyView.clone(view));
         TestHelper.assertNoEvents(listener);
         randomEventLoop(defaultRandom, listener);
@@ -346,13 +346,13 @@ public class TestViewStateManager {
         mgr.handleChanging();
         final DummyTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertEvents(listener1, EventFactory.newInitEvent(view));
+        assertEvents(listener1, EventHelper.newInitEvent(view));
         
         final DummyListener listener2 = new DummyListener();
         mgr.bind(listener2);
         mgr.handleNewView(DummyTopologyView.clone(view));
         TestHelper.assertNoEvents(listener1);
-        assertEvents(listener2, EventFactory.newInitEvent(view));
+        assertEvents(listener2, EventHelper.newInitEvent(view));
         randomEventLoop(defaultRandom, listener1, listener2);
     }
     
@@ -365,7 +365,7 @@ public class TestViewStateManager {
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener);
         mgr.bind(listener);
-        assertEvents(listener, EventFactory.newInitEvent(view));
+        assertEvents(listener, EventHelper.newInitEvent(view));
         mgr.handleNewView(DummyTopologyView.clone(view));
         TestHelper.assertNoEvents(listener);
         randomEventLoop(defaultRandom, listener);
@@ -382,10 +382,10 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
-        assertEvents(listener, EventFactory.newInitEvent(view1));
+        assertEvents(listener, EventHelper.newInitEvent(view1));
         final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
-        assertEvents(listener, EventFactory.newChangingEvent(view1), EventFactory.newChangedEvent(view1, view2));
+        assertEvents(listener, EventHelper.newChangingEvent(view1), EventHelper.newChangedEvent(view1, view2));
         randomEventLoop(defaultRandom, listener);
     }
     
@@ -398,7 +398,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
-        assertEvents(listener, EventFactory.newInitEvent(view1));
+        assertEvents(listener, EventHelper.newInitEvent(view1));
         mgr.handleDeactivated();
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
@@ -408,7 +408,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
-        assertEvents(listener, EventFactory.newInitEvent(view2));
+        assertEvents(listener, EventHelper.newInitEvent(view2));
     }
 
     @Test
@@ -420,7 +420,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
-        assertEvents(listener, EventFactory.newInitEvent(view1));
+        assertEvents(listener, EventHelper.newInitEvent(view1));
         mgr.handleDeactivated();
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view2 = new DummyTopologyView().addInstance();
@@ -428,10 +428,10 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         mgr.bind(listener); // need to bind again after deactivate
         mgr.handleActivated();
-        assertEvents(listener, EventFactory.newInitEvent(view2));
+        assertEvents(listener, EventHelper.newInitEvent(view2));
         final BaseTopologyView view3 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view3);
-        assertEvents(listener, EventFactory.newChangingEvent(view2), EventFactory.newChangedEvent(view2, view3));
+        assertEvents(listener, EventHelper.newChangingEvent(view2), EventHelper.newChangedEvent(view2, view3));
     }
 
     @Test
@@ -443,9 +443,9 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
-        assertEvents(listener, EventFactory.newInitEvent(view1));
+        assertEvents(listener, EventHelper.newInitEvent(view1));
         mgr.handleChanging();
-        assertEvents(listener, EventFactory.newChangingEvent(view1));
+        assertEvents(listener, EventHelper.newChangingEvent(view1));
         mgr.handleDeactivated();
         TestHelper.assertNoEvents(listener);
         mgr.bind(listener); // need to bind again after deactivate
@@ -455,7 +455,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
-        assertEvents(listener, EventFactory.newInitEvent(view2));
+        assertEvents(listener, EventHelper.newInitEvent(view2));
     }
     
     @Test
@@ -490,10 +490,10 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         serviceSemaphore.release(1);
         Thread.sleep(1000);
-        assertEvents(listener, EventFactory.newInitEvent(view1));
+        assertEvents(listener, EventHelper.newInitEvent(view1));
         final DummyTopologyView view2 = view1.clone();
         mgr.handleChanging();
-        assertEvents(listener, EventFactory.newChangingEvent(view1));
+        assertEvents(listener, EventHelper.newChangingEvent(view1));
         view2.removeInstance(slingId2);
         async(new Runnable() {
 
@@ -511,7 +511,7 @@ public class TestViewStateManager {
         logger.debug("run: waiting 1sec");
         Thread.sleep(1000);
         logger.debug("run: asserting 1 event");
-        assertEvents(listener, EventFactory.newChangedEvent(view1, view2));
+        assertEvents(listener, EventHelper.newChangedEvent(view1, view2));
         commonsLogger.setLevel(Level.INFO); // back to default
     }
 
@@ -556,9 +556,9 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         serviceSemaphore.release(1); // release the first one only
         Thread.sleep(1000);
-        assertEvents(listener, EventFactory.newInitEvent(view1));
+        assertEvents(listener, EventHelper.newInitEvent(view1));
         mgr.handleChanging();
-        assertEvents(listener, EventFactory.newChangingEvent(view1));
+        assertEvents(listener, EventHelper.newChangingEvent(view1));
         async(new Runnable() {
 
             public void run() {
@@ -610,7 +610,7 @@ public class TestViewStateManager {
         logger.debug("run: waiting 1sec");
         Thread.sleep(1000);
         logger.debug("run: asserting 1 event");
-        final TopologyEvent changedEvent = EventFactory.newChangedEvent(view1, view3);
+        final TopologyEvent changedEvent = EventHelper.newChangedEvent(view1, view3);
         assertEvents(listener, changedEvent);
         commonsLogger.setLevel(Level.INFO); // back to default
     }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 25/38: SLING-5173 : added getInstance(slingId) to simplify things

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 8d224852e1a62a42143a007fbd15196dc5ceb2af
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 15:33:07 2015 +0000

    SLING-5173 : added getInstance(slingId) to simplify things
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710038 13f79535-47bb-0310-9956-ffa450edef68
---
 .../discovery/commons/providers/BaseTopologyView.java      | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
index 6464ffc..67127a2 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
@@ -102,4 +102,18 @@ public abstract class BaseTopologyView implements TopologyView {
             + ", instances=" + sb.toString() + "]";
     }
 
+    /**
+     * Simple getter for a particular slingId
+     * @param slingId the slingId for which to lookup the InstanceDescription
+     * @return the InstanceDescription matching the provided slingId - or null if it doesn't exist
+     */
+    public InstanceDescription getInstance(String slingId) {
+        for (InstanceDescription instance : getInstances()) {
+            if (instance.getSlingId().equals(slingId)) {
+                return instance;
+            }
+        }
+        return null;
+    }
+
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 33/38: SLING-5094 / SLING-5173 / SLING-4603 related : ensure that before invoking the ConsistencyService.sync no async events are still in the queue. This is achieved by enqueueing an async event too that once it gets triggered ensures that no async events are left. This mechanism ensures that before the syncToken is written, all TopologyEventListeners have received a TOPOLOGY_CHANGING - and only that guarantees that the syncToken mechanism carries a high guarantee.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 26e1a7b053883381b26f52fa201da16aabc257ea
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 11:28:41 2015 +0000

    SLING-5094 / SLING-5173 / SLING-4603 related : ensure that before invoking the ConsistencyService.sync no async events are still in the queue. This is achieved by enqueueing an async event too that once it gets triggered ensures that no async events are left. This mechanism ensures that before the syncToken is written, all TopologyEventListeners have received a TOPOLOGY_CHANGING - and only that guarantees that the syncToken mechanism carries a high guarantee.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710176 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/base/AsyncEvent.java         | 33 ++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
new file mode 100644
index 0000000..8c3f17f
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.base;
+
+/**
+ * An AsyncEvent can be enqueued to the AsyncEventSender for
+ * later, asynchronous triggering
+ */
+public interface AsyncEvent {
+
+    /**
+     * As soon as this AsyncEvent is at the front of AsyncEventSender's
+     * FIFO queue it will be invoked via this trigger() method.
+     */
+    void trigger();
+    
+}

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 02/38: svn:ignore updated

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 7eadb2427fdab96fc3ee6ad2f41cb0ec955ac8be
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Apr 30 12:09:53 2015 +0000

    svn:ignore updated
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1676968 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 34/38: SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService - plus making timeout/interval values for the same configurable in discovery.oak

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit fc2a80519003cb565b5df55a30956d3fb27b333d
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 11:50:05 2015 +0000

    SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService - plus making timeout/interval values for the same configurable in discovery.oak
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710183 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/BaseTopologyView.java        |  4 +--
 .../commons/providers/ViewStateManager.java        |  2 +-
 .../providers/base/ViewStateManagerFactory.java    |  4 +--
 .../providers/base/ViewStateManagerImpl.java       | 34 +++++++++++-----------
 ...istencyService.java => ClusterSyncService.java} |  6 ++--
 ...istencyHistory.java => ClusterSyncHistory.java} |  2 +-
 ...viceChain.java => ClusterSyncServiceChain.java} | 24 +++++++--------
 .../providers/spi/base/DiscoveryLiteConfig.java    |  4 +--
 ...vice.java => OakBacklogClusterSyncService.java} | 26 ++++++++---------
 ...nsistencyService.java => SyncTokenService.java} | 34 +++++++++++-----------
 .../commons/providers/base/ClusterTest.java        |  4 +--
 .../providers/base/TestMinEventDelayHandler.java   |  4 +--
 .../providers/base/TestViewStateManager.java       | 18 ++++++------
 ...cyService.java => TestOakSyncTokenService.java} | 14 ++++-----
 14 files changed, 90 insertions(+), 90 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
index 67127a2..ccd9988 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
@@ -52,7 +52,7 @@ public abstract class BaseTopologyView implements TopologyView {
 
     /**
      * Returns the id that shall be used in the syncToken
-     * by the ConsistencyService.
+     * by the ClusterSyncService.
      * <p>
      * The clusterSyncId uniquely identifies each change
      * of the local cluster for all participating instances. 
@@ -72,7 +72,7 @@ public abstract class BaseTopologyView implements TopologyView {
      * of the TopologyView for all participating instances
      * in the whole topology).
      * <p>
-     * This id can further safely be used by the ConsistencyService
+     * This id can further safely be used by the ClusterSyncService
      * to identify a syncToken that it writes and that all
      * other instances in the lcoal cluster wait for, before
      * sending a TOPOLOGY_CHANGED event.
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
index 16a70c5..50f20a0 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
@@ -26,7 +26,7 @@ import org.apache.sling.discovery.TopologyEventListener;
  * The ViewStateManager is at the core of managing TopologyEventListeners,
  * the 'view state' (changing vs changed) and sending out the appropriate
  * and according TopologyEvents to the registered listeners - depending
- * on the implementation it also supports the ConsistencyService, which is
+ * on the implementation it also supports the ClusterSyncService, which is
  * invoked on handleNewView.
  */
 public interface ViewStateManager {
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java
index 0b9ad02..5df0eea 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java
@@ -21,7 +21,7 @@ package org.apache.sling.discovery.commons.providers.base;
 import java.util.concurrent.locks.Lock;
 
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 
 /**
  * Used to create an implementation classes of type ViewStateManager
@@ -31,7 +31,7 @@ import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 public class ViewStateManagerFactory {
 
     public static ViewStateManager newViewStateManager(Lock lock, 
-            ConsistencyService consistencyService) {
+            ClusterSyncService consistencyService) {
         return new ViewStateManagerImpl(lock, consistencyService);
     }
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index 61b39f6..5930022 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -36,7 +36,7 @@ import org.apache.sling.discovery.TopologyEventListener;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -47,7 +47,7 @@ import org.slf4j.LoggerFactory;
  * <p>
  * Note re synchronization: this class rquires a lock object to be passed
  * in the constructor - this will be applied to all public methods
- * appropriately. Additionally, the ConsistencyService callback will
+ * appropriately. Additionally, the ClusterSyncService callback will
  * also be locked using the provided lock object.
  */
 public class ViewStateManagerImpl implements ViewStateManager {
@@ -107,12 +107,12 @@ public class ViewStateManagerImpl implements ViewStateManager {
     protected final Lock lock;
 
     /**
-     * An optional ConsistencyService can be provided in the constructor which, when set, will
+     * An optional ClusterSyncService can be provided in the constructor which, when set, will
      * be invoked upon a new view becoming available (in handleNewView) and the actual
-     * TOPOLOGY_CHANGED event will only be sent once the ConsistencyService.sync method
+     * TOPOLOGY_CHANGED event will only be sent once the ClusterSyncService.sync method
      * does the according callback (which can be synchronous or asynchronous again).
      */
-    private final ConsistencyService consistencyService;
+    private final ClusterSyncService consistencyService;
     
     /** 
      * A modification counter that increments on each of the following methods:
@@ -123,7 +123,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
      *  <li>handleNewView()</li>
      * </ul>
      * with the intent that - when a consistencyService is set - the callback from the
-     * ConsistencyService can check if any of the above methods was invoked - and if so,
+     * ClusterSyncService can check if any of the above methods was invoked - and if so,
      * it does not send the TOPOLOGY_CHANGED event due to those new facts that happened
      * while it was synching with the repository.
      */
@@ -139,13 +139,13 @@ public class ViewStateManagerImpl implements ViewStateManager {
 
     /**
      * Creates a new ViewStateManager which synchronizes each method with the given
-     * lock and which optionally uses the given ConsistencyService to sync the repository
+     * lock and which optionally uses the given ClusterSyncService to sync the repository
      * upon handling a new view where an instances leaves the local cluster.
      * @param lock the lock to be used - must not be null
-     * @param consistencyService optional (ie can be null) - the ConsistencyService to 
+     * @param consistencyService optional (ie can be null) - the ClusterSyncService to 
      * sync the repository upon handling a new view where an instances leaves the local cluster.
      */
-    ViewStateManagerImpl(Lock lock, ConsistencyService consistencyService) {
+    ViewStateManagerImpl(Lock lock, ClusterSyncService consistencyService) {
         if (lock==null) {
             throw new IllegalArgumentException("lock must not be null");
         }
@@ -486,10 +486,10 @@ public class ViewStateManagerImpl implements ViewStateManager {
                 return true;
             }
             
-            final boolean invokeConsistencyService;
+            final boolean invokeClusterSyncService;
             if (consistencyService==null) {
-                logger.info("handleNewViewNonDelayed: no consistencyService set - continuing directly.");
-                invokeConsistencyService = false;
+                logger.info("handleNewViewNonDelayed: no ClusterSyncService set - continuing directly.");
+                invokeClusterSyncService = false;
             } else {
                 // there used to be a distinction between:
                 // * if no previousView is set, then we should invoke the consistencyService
@@ -503,16 +503,16 @@ public class ViewStateManagerImpl implements ViewStateManager {
                 //
                 // which is a long way of saying: if the consistencyService is configured,
                 // then we always use it, hence:
-                logger.info("handleNewViewNonDelayed: consistencyService set - invoking consistencyService");
-                invokeConsistencyService = true;
+                logger.info("handleNewViewNonDelayed: ClusterSyncService set - invoking...");
+                invokeClusterSyncService = true;
             }
                         
-            if (invokeConsistencyService) {
+            if (invokeClusterSyncService) {
                 // if "instances from the local cluster have been removed"
                 // then:
                 // run the set consistencyService
                 final int lastModCnt = modCnt;
-                logger.info("handleNewViewNonDelayed: invoking waitForAsyncEvents, then consistencyService (modCnt={})", modCnt);
+                logger.info("handleNewViewNonDelayed: invoking waitForAsyncEvents, then clusterSyncService (modCnt={})", modCnt);
                 asyncEventSender.enqueue(new AsyncEvent() {
                     
                     @Override
@@ -567,7 +567,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                     
                 });
             } else {
-                // otherwise we're either told not to use any ConsistencyService
+                // otherwise we're either told not to use any ClusterSyncService
                 // or using it is not applicable at this stage - so continue
                 // with sending the TOPOLOGY_CHANGED (or TOPOLOGY_INIT if there
                 // are any newly bound topology listeners) directly
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/ClusterSyncService.java
similarity index 95%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/ClusterSyncService.java
index 0aec6a8..7bf9ce1 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/ClusterSyncService.java
@@ -21,7 +21,7 @@ package org.apache.sling.discovery.commons.providers.spi;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 
 /**
- * The ConsistencyService can be used to establish strong
+ * The ClusterSyncService can be used to establish strong
  * consistency with the underlying (eventually consistent) repository in use.
  * <p>
  * The issue is described in length in SLING-4627 - the short
@@ -41,7 +41,7 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
  * to settle before the topology-dependent activity can continue
  * </li>
  * </ul>
- * Both of these two aspects are handled by this ConsistencyService.
+ * Both of these two aspects are handled by this ClusterSyncService.
  * The former one by introducing a 'sync token' that gets written
  * to the repository and on receiving it by the peers they know
  * that the writing instance is aware of the ongoing change, that
@@ -54,7 +54,7 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
  * is still being deactivated (eg has backlog). So this second
  * part is repository dependent.
  */
-public interface ConsistencyService {
+public interface ClusterSyncService {
 
     /**
      * Starts the synchronization process and calls the provided
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyHistory.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ClusterSyncHistory.java
similarity index 98%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyHistory.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ClusterSyncHistory.java
index a6e793b..6526eb2 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyHistory.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ClusterSyncHistory.java
@@ -27,7 +27,7 @@ import java.util.List;
 
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 
-public class ConsistencyHistory {
+public class ClusterSyncHistory {
 
     class HistoryEntry {
         BaseTopologyView view;
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyServiceChain.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ClusterSyncServiceChain.java
similarity index 74%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyServiceChain.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ClusterSyncServiceChain.java
index 7695fe6..1094f1c 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyServiceChain.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ClusterSyncServiceChain.java
@@ -23,25 +23,25 @@ import java.util.Iterator;
 import java.util.List;
 
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * Allows chaining of ConsistencyServices, itself implementing
- * the ConsistencyService interface
+ * Allows chaining of ClusterSyncService, itself implementing
+ * the ClusterSyncService interface
  */
-public class ConsistencyServiceChain implements ConsistencyService {
+public class ClusterSyncServiceChain implements ClusterSyncService {
 
     protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-    private final List<ConsistencyService> chain;
+    private final List<ClusterSyncService> chain;
 
     /**
-     * Creates a new chain of ConsistencyServices that calls a
-     * cascaded sync with the provided ConsistencyServices.
+     * Creates a new chain of ClusterSyncService that calls a
+     * cascaded sync with the provided ClusterSyncService.
      */
-    public ConsistencyServiceChain(ConsistencyService... chain) {
+    public ClusterSyncServiceChain(ClusterSyncService... chain) {
         if (chain==null || chain.length==0) {
             throw new IllegalArgumentException("chain must be 1 or more");
         }
@@ -50,18 +50,18 @@ public class ConsistencyServiceChain implements ConsistencyService {
     
     @Override
     public void sync(BaseTopologyView view, Runnable callback) {
-        final Iterator<ConsistencyService> chainIt = chain.iterator();
+        final Iterator<ClusterSyncService> chainIt = chain.iterator();
         chainedSync(view, callback, chainIt);
     }
 
     private void chainedSync(final BaseTopologyView view, final Runnable callback, 
-            final Iterator<ConsistencyService> chainIt) {
+            final Iterator<ClusterSyncService> chainIt) {
         if (!chainIt.hasNext()) {
             logger.debug("doSync: done with sync chain, invoking callback");
             callback.run();
             return;
         }
-        ConsistencyService next = chainIt.next();
+        ClusterSyncService next = chainIt.next();
         next.sync(view, new Runnable() {
 
             @Override
@@ -74,7 +74,7 @@ public class ConsistencyServiceChain implements ConsistencyService {
 
     @Override
     public void cancelSync() {
-        for (ConsistencyService consistencyService : chain) {
+        for (ClusterSyncService consistencyService : chain) {
             consistencyService.cancelSync();
         }
     }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java
index fe811b1..2114a50 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java
@@ -39,12 +39,12 @@ public interface DiscoveryLiteConfig {
      * Returns the timeout (in milliseconds) to be used when waiting for the sync tokens or id mapping
      * @return the timeout (in milliseconds) to be used when waiting for the sync tokens or id mapping
      */
-    long getBgTimeoutMillis();
+    long getClusterSyncServiceTimeoutMillis();
     
     /**
      * Returns the interval (in milliseconds) to be used when waiting for the sync tokens or id mapping
      * @return the interval (in milliseconds) to be used when waiting for the sync tokens or id mapping
      */
-    long getBgIntervalMillis();
+    long getClusterSyncServiceIntervalMillis();
 
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
similarity index 90%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
index e972b05..fa07268 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogClusterSyncService.java
@@ -33,16 +33,16 @@ import org.apache.sling.commons.json.JSONException;
 import org.apache.sling.discovery.ClusterView;
 import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.apache.sling.settings.SlingSettingsService;
 
 /**
- * The OakBacklogConsistencyService will wait until all instances
+ * The OakBacklogClusterSyncService will wait until all instances
  * in the local cluster are no longer in any backlog state.
  */
 @Component(immediate = false)
-@Service(value = { ConsistencyService.class, OakBacklogConsistencyService.class })
-public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
+@Service(value = { ClusterSyncService.class, OakBacklogClusterSyncService.class })
+public class OakBacklogClusterSyncService extends AbstractServiceWithBackgroundCheck implements ClusterSyncService {
 
     static enum BacklogStatus {
         UNDEFINED /* when there was an error retrieving the backlog status with oak */,
@@ -62,14 +62,14 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
     @Reference
     protected SlingSettingsService settingsService;
 
-    private ConsistencyHistory consistencyHistory = new ConsistencyHistory();
+    private ClusterSyncHistory consistencyHistory = new ClusterSyncHistory();
     
-    public static OakBacklogConsistencyService testConstructorAndActivate(
+    public static OakBacklogClusterSyncService testConstructorAndActivate(
             final DiscoveryLiteConfig commonsConfig,
             final IdMapService idMapService,
             final SlingSettingsService settingsService,
             ResourceResolverFactory resourceResolverFactory) {
-        OakBacklogConsistencyService service = testConstructor(commonsConfig, idMapService, settingsService, resourceResolverFactory);
+        OakBacklogClusterSyncService service = testConstructor(commonsConfig, idMapService, settingsService, resourceResolverFactory);
         service.activate();
         return service;
     }
@@ -86,12 +86,12 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
      * @throws LoginException when the login for initialization failed
      * @throws JSONException when the descriptor wasn't proper json at init time
      */
-    public static OakBacklogConsistencyService testConstructor(
+    public static OakBacklogClusterSyncService testConstructor(
             final DiscoveryLiteConfig commonsConfig,
             final IdMapService idMapService,
             final SlingSettingsService settingsService,
             ResourceResolverFactory resourceResolverFactory) {
-        OakBacklogConsistencyService service = new OakBacklogConsistencyService();
+        OakBacklogClusterSyncService service = new OakBacklogClusterSyncService();
         if (commonsConfig == null) {
             throw new IllegalArgumentException("commonsConfig must not be null");
         }
@@ -114,11 +114,11 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
         logger.info("activate: activated with slingId="+slingId);
     }
     
-    public void setConsistencyHistory(ConsistencyHistory consistencyHistory) {
+    public void setConsistencyHistory(ClusterSyncHistory consistencyHistory) {
         this.consistencyHistory = consistencyHistory;
     }
     
-    public ConsistencyHistory getConsistencyHistory() {
+    public ClusterSyncHistory getConsistencyHistory() {
         return consistencyHistory;
     }
     
@@ -145,7 +145,7 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
     private void waitWhileBacklog(final BaseTopologyView view, final Runnable runnable) {
         // start backgroundChecking until the backlogStatus 
         // is NO_BACKLOG
-        startBackgroundCheck("OakSyncTokenConsistencyService-backlog-waiting", new BackgroundCheck() {
+        startBackgroundCheck("OakBacklogClusterSyncService-backlog-waiting", new BackgroundCheck() {
             
             @Override
             public boolean check() {
@@ -173,7 +173,7 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
                     return false;
                 }
             }
-        }, runnable, getCommonsConfig().getBgTimeoutMillis(), getCommonsConfig().getBgIntervalMillis());
+        }, runnable, getCommonsConfig().getClusterSyncServiceTimeoutMillis(), getCommonsConfig().getClusterSyncServiceIntervalMillis());
     }
     
     private BacklogStatus getBacklogStatus(BaseTopologyView view) {
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
similarity index 88%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
index 1e0cfc2..23ac970 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenService.java
@@ -31,7 +31,7 @@ import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.settings.SlingSettingsService;
 
@@ -46,8 +46,8 @@ import org.apache.sling.settings.SlingSettingsService;
  * and are aware of the new discoveryLite view.
  */
 @Component(immediate = false)
-@Service(value = { ConsistencyService.class, SyncTokenConsistencyService.class })
-public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
+@Service(value = { ClusterSyncService.class, SyncTokenService.class })
+public class SyncTokenService extends AbstractServiceWithBackgroundCheck implements ClusterSyncService {
 
     @Reference
     protected DiscoveryLiteConfig commonsConfig;
@@ -58,22 +58,22 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
     @Reference
     protected SlingSettingsService settingsService;
 
-    protected ConsistencyHistory consistencyHistory = new ConsistencyHistory();
+    protected ClusterSyncHistory clusterSyncHistory = new ClusterSyncHistory();
 
-    public static SyncTokenConsistencyService testConstructorAndActivate(
+    public static SyncTokenService testConstructorAndActivate(
             DiscoveryLiteConfig commonsConfig,
             ResourceResolverFactory resourceResolverFactory,
             SlingSettingsService settingsService) {
-        SyncTokenConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
+        SyncTokenService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
         service.activate();
         return service;
     }
     
-    public static SyncTokenConsistencyService testConstructor(
+    public static SyncTokenService testConstructor(
             DiscoveryLiteConfig commonsConfig,
             ResourceResolverFactory resourceResolverFactory,
             SlingSettingsService settingsService) {
-        SyncTokenConsistencyService service = new SyncTokenConsistencyService();
+        SyncTokenService service = new SyncTokenService();
         if (commonsConfig == null) {
             throw new IllegalArgumentException("commonsConfig must not be null");
         }
@@ -95,12 +95,12 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
         logger.info("activate: activated with slingId="+slingId);
     }
     
-    public void setConsistencyHistory(ConsistencyHistory consistencyHistory) {
-        this.consistencyHistory = consistencyHistory;
+    public void setConsistencyHistory(ClusterSyncHistory consistencyHistory) {
+        this.clusterSyncHistory = consistencyHistory;
     }
     
-    public ConsistencyHistory getConsistencyHistory() {
-        return consistencyHistory;
+    public ClusterSyncHistory getClusterSyncHistory() {
+        return clusterSyncHistory;
     }
     
     /** Get or create a ResourceResolver **/
@@ -125,7 +125,7 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
 
     protected void syncToken(final BaseTopologyView view, final Runnable callback) {
         
-        startBackgroundCheck("SyncTokenConsistencyService", new BackgroundCheck() {
+        startBackgroundCheck("SyncTokenService", new BackgroundCheck() {
             
             @Override
             public boolean check() {
@@ -136,14 +136,14 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
                     // that they will have to wait until the timeout hits
                     
                     // so to try to avoid this, retry storing my sync token later:
-                    consistencyHistory.addHistoryEntry(view, "storing my syncToken ("+localClusterSyncTokenId+")");
+                    clusterSyncHistory.addHistoryEntry(view, "storing my syncToken ("+localClusterSyncTokenId+")");
                     return false;
                 }
                 
                 // 2) then check if all others have done the same already
                 return seenAllSyncTokens(view);
             }
-        }, callback, commonsConfig.getBgTimeoutMillis(), commonsConfig.getBgIntervalMillis());
+        }, callback, commonsConfig.getClusterSyncServiceTimeoutMillis(), commonsConfig.getClusterSyncServiceIntervalMillis());
     }
 
     private boolean storeMySyncToken(String syncTokenId) {
@@ -227,10 +227,10 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
             }
             if (!success) {
                 logger.info("seenAllSyncTokens: not yet seen all expected syncTokens (see above for details)");
-                consistencyHistory.addHistoryEntry(view, historyEntry.toString());
+                clusterSyncHistory.addHistoryEntry(view, historyEntry.toString());
                 return false;
             } else {
-                consistencyHistory.addHistoryEntry(view, "seen all syncTokens");
+                clusterSyncHistory.addHistoryEntry(view, "seen all syncTokens");
             }
             
             resourceResolver.commit();
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
index 2bf3705..18ad1ba 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
@@ -31,7 +31,7 @@ import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -59,7 +59,7 @@ public class ClusterTest {
     }
     
     private ViewStateManagerImpl newMgr() {
-        ViewStateManagerImpl mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+        ViewStateManagerImpl mgr = new ViewStateManagerImpl(new ReentrantLock(), new ClusterSyncService() {
             
             public void sync(BaseTopologyView view, Runnable callback) {
                 callback.run();
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
index d28b912..fc78c44 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
@@ -32,7 +32,7 @@ import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Ignore;
@@ -56,7 +56,7 @@ public class TestMinEventDelayHandler {
 
     @Before
     public void setup() throws Exception {
-        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ClusterSyncService() {
             
             public void sync(BaseTopologyView view, Runnable callback) {
                 callback.run();
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index e80268c..8dd1796 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -38,7 +38,7 @@ import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.EventHelper;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -49,12 +49,12 @@ public class TestViewStateManager {
 
     private static final Logger logger = LoggerFactory.getLogger(TestViewStateManager.class);
 
-    private class ConsistencyServiceWithSemaphore implements ConsistencyService {
+    private class ClusterSyncServiceWithSemaphore implements ClusterSyncService {
 
         private final Semaphore semaphore;
         private final Lock lock;
 
-        public ConsistencyServiceWithSemaphore(Lock lock, Semaphore semaphore) {
+        public ClusterSyncServiceWithSemaphore(Lock lock, Semaphore semaphore) {
             this.lock = lock;
             this.semaphore = semaphore;
         }
@@ -63,9 +63,9 @@ public class TestViewStateManager {
             try {
                 lock.unlock();
                 try{
-                    logger.info("ConsistencyServiceWithSemaphore.sync: acquiring lock ...");
+                    logger.info("ClusterSyncServiceWithSemaphore.sync: acquiring lock ...");
                     semaphore.acquire();
-                    logger.info("ConsistencyServiceWithSemaphore.sync: lock acquired.");
+                    logger.info("ClusterSyncServiceWithSemaphore.sync: lock acquired.");
                 } finally {
                     lock.lock();
                 }
@@ -88,7 +88,7 @@ public class TestViewStateManager {
 
     @Before
     public void setup() throws Exception {
-        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ClusterSyncService() {
             
             public void sync(BaseTopologyView view, Runnable callback) {
                 callback.run();
@@ -225,7 +225,7 @@ public class TestViewStateManager {
     @Test
     public void testCancelSync() throws Exception {
         final List<Runnable> syncCallbacks = new LinkedList<Runnable>();
-        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ClusterSyncService() {
             
             public void sync(BaseTopologyView view, Runnable callback) {
                 synchronized(syncCallbacks) {
@@ -526,7 +526,7 @@ public class TestViewStateManager {
         commonsLogger.setLevel(Level.INFO); // change here to DEBUG in case of issues with this test
         final Semaphore serviceSemaphore = new Semaphore(0);
         final ReentrantLock lock = new ReentrantLock();
-        final ConsistencyServiceWithSemaphore cs = new ConsistencyServiceWithSemaphore(lock, serviceSemaphore );
+        final ClusterSyncServiceWithSemaphore cs = new ClusterSyncServiceWithSemaphore(lock, serviceSemaphore );
         mgr = new ViewStateManagerImpl(lock, cs);
         final DummyListener listener = new DummyListener();
         mgr.bind(listener);
@@ -588,7 +588,7 @@ public class TestViewStateManager {
         final Semaphore serviceSemaphore = new Semaphore(0);
         final Semaphore testSemaphore = new Semaphore(0);
         final ReentrantLock lock = new ReentrantLock();
-        final ConsistencyServiceWithSemaphore cs = new ConsistencyServiceWithSemaphore(lock, serviceSemaphore );
+        final ClusterSyncServiceWithSemaphore cs = new ClusterSyncServiceWithSemaphore(lock, serviceSemaphore );
         mgr = new ViewStateManagerImpl(lock, cs);
         final DummyListener listener = new DummyListener();
         mgr.bind(listener);
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
similarity index 95%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
index 357c8a5..591fea8 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
@@ -34,13 +34,13 @@ import org.apache.sling.discovery.commons.providers.base.TestHelper;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
 import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
 import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
-import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogClusterSyncService;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-public class TestOakSyncTokenConsistencyService {
+public class TestOakSyncTokenService {
 
     private static final String SYNCTOKEN_PATH = "/var/discovery/commons/synctokens";
 
@@ -71,12 +71,12 @@ public class TestOakSyncTokenConsistencyService {
         }
 
         @Override
-        public long getBgTimeoutMillis() {
+        public long getClusterSyncServiceTimeoutMillis() {
             return bgTimeoutMillis;
         }
 
         @Override
-        public long getBgIntervalMillis() {
+        public long getClusterSyncServiceIntervalMillis() {
             return bgIntervalMillis;
         }
 
@@ -119,7 +119,7 @@ public class TestOakSyncTokenConsistencyService {
     public void testOneNode() throws Exception {
         DummyTopologyView one = TestHelper.newView(true, slingId1, slingId1, slingId1);
         Lock lock = new ReentrantLock();
-        OakBacklogConsistencyService cs = OakBacklogConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
+        OakBacklogClusterSyncService cs = OakBacklogClusterSyncService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
         ViewStateManager vsm = ViewStateManagerFactory.newViewStateManager(lock, cs);
         DummyListener l = new DummyListener();
         assertEquals(0, l.countEvents());
@@ -145,7 +145,7 @@ public class TestOakSyncTokenConsistencyService {
         String slingId2 = UUID.randomUUID().toString();
         DummyTopologyView two1 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
         Lock lock1 = new ReentrantLock();
-        OakBacklogConsistencyService cs1 = OakBacklogConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
+        OakBacklogClusterSyncService cs1 = OakBacklogClusterSyncService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
         ViewStateManager vsm1 = ViewStateManagerFactory.newViewStateManager(lock1, cs1);
         DummyListener l = new DummyListener();
         vsm1.bind(l);
@@ -161,7 +161,7 @@ public class TestOakSyncTokenConsistencyService {
         Lock lock2 = new ReentrantLock();
         IdMapService idMapService2 = IdMapService.testConstructor(
                 new SimpleCommonsConfig(), new DummySlingSettingsService(slingId2), factory2);
-        OakBacklogConsistencyService cs2 = OakBacklogConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService2, new DummySlingSettingsService(slingId2), factory2);
+        OakBacklogClusterSyncService cs2 = OakBacklogClusterSyncService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService2, new DummySlingSettingsService(slingId2), factory2);
         ViewStateManager vsm2 = ViewStateManagerFactory.newViewStateManager(lock2, cs2);
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 24/38: SLING-5173 : added simple implementation for findInstances

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 1a3602926c75e1f6e3c15346886de33bdfff4b81
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 15:32:29 2015 +0000

    SLING-5173 : added simple implementation for findInstances
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710036 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/discovery/commons/providers/DummyTopologyView.java      | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java b/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
index d1962f8..b5314ad 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
@@ -121,7 +121,13 @@ public class DummyTopologyView extends BaseTopologyView {
 
     @Override
     public Set<InstanceDescription> findInstances(InstanceFilter filter) {
-        throw new IllegalStateException("not yet implemented");
+        Set<InstanceDescription> result = new HashSet<InstanceDescription>();
+        for (InstanceDescription instanceDescription : instances) {
+            if (filter.accept(instanceDescription)) {
+                result.add(instanceDescription);
+            }
+        }
+        return result;
     }
 
     @Override

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 13/38: SLING-5173 : rename commons impl packages to base as they are meant for reuse by discovery.impl and discovery.oak - plus avoid using abstract component class with scr annotations - use abstract getters instead - plus some more fine-tuning of log messages - plus make discovery.impl's Config also implement DiscoveryLiteConfig - plus properly handle binds happening before activate in DiscoveryServiceImpl

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 3d923b4fbe7ede4708126811d87fd4d8030bfd42
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 08:29:08 2015 +0000

    SLING-5173 : rename commons impl packages to base as they are meant for reuse by discovery.impl and discovery.oak - plus avoid using abstract component class with scr annotations - use abstract getters instead - plus some more fine-tuning of log messages - plus make discovery.impl's Config also implement DiscoveryLiteConfig - plus properly handle binds happening before activate in DiscoveryServiceImpl
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709751 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  8 +++
 .../providers/{impl => base}/AsyncEvent.java       |  2 +-
 .../providers/{impl => base}/AsyncEventSender.java |  2 +-
 .../{impl => base}/MinEventDelayHandler.java       | 10 ++--
 .../{impl => base}/ViewStateManagerFactory.java    |  2 +-
 .../{impl => base}/ViewStateManagerImpl.java       | 22 +++++---
 .../providers/{impl => base}/package-info.java     |  2 +-
 .../AbstractServiceWithBackgroundCheck.java        |  2 +-
 .../BaseSyncTokenConsistencyService.java}          | 60 ++++------------------
 .../spi/{impl => base}/DiscoveryLiteConfig.java    |  2 +-
 .../{impl => base}/DiscoveryLiteDescriptor.java    |  2 +-
 .../providers/spi/{impl => base}/IdMapService.java |  4 +-
 .../OakSyncTokenConsistencyService.java            | 28 +++++++++-
 .../providers/spi/{impl => base}/package-info.java |  2 +-
 .../providers/{impl => base}/ClusterTest.java      |  3 +-
 .../{impl => base}/DummyDiscoveryService.java      |  2 +-
 .../providers/{impl => base}/DummyListener.java    |  2 +-
 .../providers/{impl => base}/DummyScheduler.java   |  2 +-
 .../providers/{impl => base}/TestHelper.java       |  5 +-
 .../{impl => base}/TestMinEventDelayHandler.java   |  5 +-
 .../{impl => base}/TestViewStateManager.java       |  5 +-
 .../spi/{impl => base}/DescriptorHelper.java       |  3 +-
 .../DiscoveryLiteDescriptorBuilder.java            |  2 +-
 .../{impl => base}/DummySlingSettingsService.java  |  2 +-
 .../spi/{impl => base}/MockedResource.java         |  2 +-
 .../spi/{impl => base}/MockedResourceResolver.java |  2 +-
 .../spi/{impl => base}/RepositoryTestHelper.java   |  2 +-
 .../TestOakSyncTokenConsistencyService.java        | 11 ++--
 28 files changed, 103 insertions(+), 93 deletions(-)

diff --git a/pom.xml b/pom.xml
index 2d595d3..e395218 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,6 +47,10 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <extensions>true</extensions>
             </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
 			<!-- discovery.commons exports a few test classes for reuse.
                  In order for others to use these, the test-jar must be built/installed too.
                  Note that 'mvn -Dmaven.test.skip=true' does NOT build the test-jar,
@@ -66,6 +70,10 @@
     </build>
     <dependencies>
         <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+        </dependency>
+        <dependency>
             <groupId>biz.aQute</groupId>
             <artifactId>bndlib</artifactId>
         </dependency>
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEvent.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
similarity index 96%
rename from src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEvent.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
index f814dfd..1a73908 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEvent.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEventListener;
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEventSender.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java
similarity index 99%
rename from src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEventSender.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java
index c463350..014f522 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEventSender.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import java.util.LinkedList;
 import java.util.List;
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
similarity index 95%
rename from src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
index f236f21..d3a9b6a 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import java.util.Date;
 import java.util.concurrent.locks.Lock;
@@ -93,7 +93,9 @@ class MinEventDelayHandler {
         }
         
         if (viewStateManager.unchanged(newView)) {
-            logger.info("handlesNewView: view is unchanged, hence no delaying applicable");
+            // this will be the most frequent case
+            // hence log only with trace
+            logger.trace("handlesNewView: view is unchanged, hence no delaying applicable");
             return false;
         }
         
@@ -133,10 +135,10 @@ class MinEventDelayHandler {
                     BaseTopologyView topology = (BaseTopologyView) t;
                     
                     if (topology.isCurrent()) {
-                        logger.info("asyncDelay.run: got new view: "+topology);
+                        logger.debug("asyncDelay.run: got new view: ", topology);
                         viewStateManager.handleNewViewNonDelayed(topology);
                     } else {
-                        logger.info("asyncDelay.run: new view (still) not current, delaying again");
+                        logger.info("asyncDelay.run: new view (still/again) not current, delaying again");
                         triggerAsyncDelaying(topology);
                         // we're actually not interested in the result here
                         // if the async part failed, then we have to rely
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerFactory.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java
similarity index 95%
rename from src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerFactory.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java
index 41d3ec7..0b9ad02 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerFactory.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerFactory.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import java.util.concurrent.locks.Lock;
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
similarity index 97%
rename from src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index 0b31949..310fd7e 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -204,7 +204,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                     // otherwise we can send the TOPOLOGY_INIT now
                     logger.debug("bind: view is defined, sending INIT now to {}",
                             eventListener);
-                    enqueue(eventListener, EventFactory.newInitEvent(previousView));
+                    enqueue(eventListener, EventFactory.newInitEvent(previousView), true);
                     eventListeners.add(eventListener);
                 }
             } else {
@@ -240,7 +240,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
         }
     }
     
-    private void enqueue(final TopologyEventListener da, final TopologyEvent event) {
+    private void enqueue(final TopologyEventListener da, final TopologyEvent event, boolean logInfo) {
         logger.trace("enqueue: start: topologyEvent {}, to {}", event, da);
         if (asyncEventSender==null) {
             // this should never happen - sendTopologyEvent should only be called
@@ -250,10 +250,18 @@ public class ViewStateManagerImpl implements ViewStateManager {
         }
         if (lastEventMap.get(da)==event.getType() && event.getType()==Type.TOPOLOGY_CHANGING) {
             // don't sent TOPOLOGY_CHANGING twice
-            logger.debug("enqueue: listener already got TOPOLOGY_CHANGING: {}", da);
+            if (logInfo) {
+                logger.info("enqueue: listener already got TOPOLOGY_CHANGING: {}", da);
+            } else {
+                logger.debug("enqueue: listener already got TOPOLOGY_CHANGING: {}", da);
+            }
             return;
         }
-        logger.debug("enqueue: enqueuing topologyEvent {}, to {}", event, da);
+        if (logInfo) {
+            logger.info("enqueue: enqueuing topologyEvent {}, to {}", event, da);
+        } else {
+            logger.debug("enqueue: enqueuing topologyEvent {}, to {}", event, da);
+        }
         asyncEventSender.enqueue(da, event);
         lastEventMap.put(da, event.getType());
         logger.trace("enqueue: sending topologyEvent {}, to {}", event, da);
@@ -261,10 +269,10 @@ public class ViewStateManagerImpl implements ViewStateManager {
 
     /** Internal helper method that sends a given event to a list of listeners **/
     private void enqueueForAll(final List<TopologyEventListener> audience, final TopologyEvent event) {
-        logger.debug("enqueueForAll: sending topologyEvent {}, to all ({}) listeners", event, audience.size());
+        logger.info("enqueueForAll: sending topologyEvent {}, to all ({}) listeners", event, audience.size());
         for (Iterator<TopologyEventListener> it = audience.iterator(); it.hasNext();) {
             TopologyEventListener topologyEventListener = it.next();
-            enqueue(topologyEventListener, event);
+            enqueue(topologyEventListener, event, false);
         }
         logger.trace("enqueueForAll: sent topologyEvent {}, to all ({}) listeners", event, audience.size());
     }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
similarity index 94%
rename from src/main/java/org/apache/sling/discovery/commons/providers/impl/package-info.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
index 3456fd5..394e219 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/package-info.java
@@ -23,7 +23,7 @@
  * @version 1.0.0
  */
 @Version("1.0.0")
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/AbstractServiceWithBackgroundCheck.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
similarity index 99%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/AbstractServiceWithBackgroundCheck.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
index 62d163c..bd87dea 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/AbstractServiceWithBackgroundCheck.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
similarity index 73%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
index 42ec63a..5746d8b 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
@@ -16,12 +16,9 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import org.apache.felix.scr.annotations.Activate;
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Reference;
-import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
@@ -42,67 +39,30 @@ import org.slf4j.LoggerFactory;
  * but not the 'wait while backlog' part (which is left to subclasses
  * if needed).
  */
-@Component(immediate = false)
-@Service(value = { ConsistencyService.class })
-public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
+public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
     protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-    /** the config to be used by this class **/
-    @Reference
-    protected DiscoveryLiteConfig commonsConfig;
-
-    @Reference
-    protected ResourceResolverFactory resourceResolverFactory;
-
-    @Reference
-    protected SlingSettingsService settingsService;
-
     protected String slingId;
 
     protected long syncTokenTimeoutMillis;
     
     protected long syncTokenIntervalMillis;
 
-    public static SyncTokenConsistencyService testConstructorAndActivate(
-            DiscoveryLiteConfig commonsConfig,
-            ResourceResolverFactory resourceResolverFactory,
-            SlingSettingsService settingsService) {
-        SyncTokenConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
-        service.activate();
-        return service;
-    }
-    
-    public static SyncTokenConsistencyService testConstructor(
-            DiscoveryLiteConfig commonsConfig,
-            ResourceResolverFactory resourceResolverFactory,
-            SlingSettingsService settingsService) {
-        SyncTokenConsistencyService service = new SyncTokenConsistencyService();
-        if (commonsConfig == null) {
-            throw new IllegalArgumentException("commonsConfig must not be null");
-        }
-        if (resourceResolverFactory == null) {
-            throw new IllegalArgumentException("resourceResolverFactory must not be null");
-        }
-        if (settingsService == null) {
-            throw new IllegalArgumentException("settingsService must not be null");
-        }
-        service.commonsConfig = commonsConfig;
-        service.resourceResolverFactory = resourceResolverFactory;
-        service.syncTokenTimeoutMillis = commonsConfig.getBgTimeoutMillis();
-        service.syncTokenIntervalMillis = commonsConfig.getBgIntervalMillis();
-        service.settingsService = settingsService;
-        return service;
-    }
+    protected abstract DiscoveryLiteConfig getCommonsConfig();
+
+    protected abstract ResourceResolverFactory getResourceResolverFactory();
+
+    protected abstract SlingSettingsService getSettingsService();
     
     @Activate
     protected void activate() {
-        this.slingId = settingsService.getSlingId();
+        this.slingId = getSettingsService().getSlingId();
     }
     
     /** Get or create a ResourceResolver **/
     protected ResourceResolver getResourceResolver() throws LoginException {
-        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+        return getResourceResolverFactory().getAdministrativeResourceResolver(null);
     }
     
     @Override
@@ -161,7 +121,7 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
     }
 
     private String getSyncTokenPath() {
-        return commonsConfig.getSyncTokenPath();
+        return getCommonsConfig().getSyncTokenPath();
     }
 
     private boolean seenAllSyncTokens(BaseTopologyView view) {
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteConfig.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java
similarity index 96%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteConfig.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java
index b43a6a4..fe811b1 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteConfig.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteConfig.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 /**
  * Provides the configuration of a few paths needed by discovery-lite processing services
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptor.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteDescriptor.java
similarity index 98%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptor.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteDescriptor.java
index eeb1591..906ab27 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptor.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteDescriptor.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import javax.jcr.Session;
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/IdMapService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
similarity index 98%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/IdMapService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
index da5a191..20ad3bc 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/IdMapService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -42,7 +42,7 @@ import org.apache.sling.settings.SlingSettingsService;
  * do the same can map clusterNodeIds to slingIds (or vice-versa)
  */
 @Component(immediate = false)
-@Service
+@Service(value = IdMapService.class)
 public class IdMapService extends AbstractServiceWithBackgroundCheck {
 
     @Reference
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
similarity index 93%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
index bfec3b3..3a3a40b 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -41,7 +41,7 @@ import org.apache.sling.settings.SlingSettingsService;
  */
 @Component(immediate = false)
 @Service(value = { ConsistencyService.class })
-public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService {
+public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyService {
 
     static enum BacklogStatus {
         UNDEFINED /* when there was an error retrieving the backlog status with oak */,
@@ -56,6 +56,15 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
     @Reference
     private IdMapService idMapService;
     
+    @Reference
+    protected DiscoveryLiteConfig commonsConfig;
+
+    @Reference
+    protected ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    protected SlingSettingsService settingsService;
+
     public static OakSyncTokenConsistencyService testConstructorAndActivate(
             final DiscoveryLiteConfig commonsConfig,
             final IdMapService idMapService,
@@ -219,6 +228,21 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
             }
         }
     }
+
+    @Override
+    protected DiscoveryLiteConfig getCommonsConfig() {
+        return commonsConfig;
+    }
+
+    @Override
+    protected ResourceResolverFactory getResourceResolverFactory() {
+        return resourceResolverFactory;
+    }
+
+    @Override
+    protected SlingSettingsService getSettingsService() {
+        return settingsService;
+    }
     
 
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/package-info.java
similarity index 93%
rename from src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/package-info.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/spi/base/package-info.java
index 6eeff70..aa1fe77 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/package-info.java
@@ -23,7 +23,7 @@
  * @version 1.0.0
  */
 @Version("1.0.0")
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
similarity index 98%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
index 6ad305b..7b921dc 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/ClusterTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import static org.junit.Assert.assertEquals;
 
@@ -30,6 +30,7 @@ import java.util.concurrent.locks.ReentrantLock;
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
 import org.junit.Before;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyDiscoveryService.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/DummyDiscoveryService.java
similarity index 95%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyDiscoveryService.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/DummyDiscoveryService.java
index 2ad8a61..4c6d35e 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyDiscoveryService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/DummyDiscoveryService.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import org.apache.sling.discovery.DiscoveryService;
 import org.apache.sling.discovery.TopologyView;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyListener.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/DummyListener.java
similarity index 97%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyListener.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/DummyListener.java
index d2fb4aa..2a38101 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyListener.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/DummyListener.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import static org.junit.Assert.fail;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyScheduler.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/DummyScheduler.java
similarity index 98%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyScheduler.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/DummyScheduler.java
index b9a149b..09ac453 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyScheduler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/DummyScheduler.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import java.io.Serializable;
 import java.util.Date;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
similarity index 98%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
index 4ea03b1..f77dc21 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
@@ -30,8 +30,9 @@ import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
-import org.apache.sling.discovery.commons.providers.EventFactory;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
similarity index 97%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
index 82e03fb..a4974df 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestMinEventDelayHandler.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -29,8 +29,9 @@ import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.DefaultClusterView;
-import org.apache.sling.discovery.commons.providers.EventFactory;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
 import org.junit.Before;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
similarity index 99%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index 9eda40d..5901c11 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers.base;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -34,8 +34,9 @@ import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
-import org.apache.sling.discovery.commons.providers.EventFactory;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.base.ViewStateManagerImpl;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
 import org.junit.Before;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DescriptorHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DescriptorHelper.java
similarity index 95%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DescriptorHelper.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DescriptorHelper.java
index b1b2f8e..5fc6cae 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DescriptorHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DescriptorHelper.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -30,6 +30,7 @@ import org.apache.jackrabbit.commons.SimpleValueFactory;
 import org.apache.jackrabbit.oak.util.GenericDescriptors;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
 
 public class DescriptorHelper {
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptorBuilder.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteDescriptorBuilder.java
similarity index 97%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptorBuilder.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteDescriptorBuilder.java
index 5ef9eb1..a95a7aa 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptorBuilder.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DiscoveryLiteDescriptorBuilder.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.util.Arrays;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DummySlingSettingsService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DummySlingSettingsService.java
similarity index 96%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DummySlingSettingsService.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DummySlingSettingsService.java
index 46fad54..c5fc700 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DummySlingSettingsService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/DummySlingSettingsService.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.net.URL;
 import java.util.Set;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResource.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/MockedResource.java
similarity index 99%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResource.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/MockedResource.java
index 5063bbe..229328d 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResource.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/MockedResource.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.util.Calendar;
 import java.util.Collection;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResourceResolver.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/MockedResourceResolver.java
similarity index 99%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResourceResolver.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/MockedResourceResolver.java
index 0350fd2..a86a380 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResourceResolver.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/MockedResourceResolver.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.io.IOException;
 import java.util.Calendar;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryTestHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/RepositoryTestHelper.java
similarity index 99%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryTestHelper.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/RepositoryTestHelper.java
index ede5412..9e4fcfd 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryTestHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/RepositoryTestHelper.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.io.IOException;
 import java.util.HashMap;
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
similarity index 94%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
index 4260cdb..174a2e5 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.spi.impl;
+package org.apache.sling.discovery.commons.providers.spi.base;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
@@ -29,9 +29,12 @@ import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.sling.api.resource.ResourceResolverFactory;
 import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
-import org.apache.sling.discovery.commons.providers.impl.DummyListener;
-import org.apache.sling.discovery.commons.providers.impl.TestHelper;
-import org.apache.sling.discovery.commons.providers.impl.ViewStateManagerFactory;
+import org.apache.sling.discovery.commons.providers.base.DummyListener;
+import org.apache.sling.discovery.commons.providers.base.TestHelper;
+import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
+import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
+import org.apache.sling.discovery.commons.providers.spi.base.OakSyncTokenConsistencyService;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.junit.After;
 import org.junit.Before;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 21/38: SLING-5173 : reduce log in cancel when already done

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 732f4d69afb86b7f5c15004df308577733a50ee1
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 17:12:06 2015 +0000

    SLING-5173 : reduce log in cancel when already done
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709889 13f79535-47bb-0310-9956-ffa450edef68
---
 .../providers/spi/base/AbstractServiceWithBackgroundCheck.java        | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
index bd87dea..8ccae9f 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
@@ -122,7 +122,9 @@ public abstract class AbstractServiceWithBackgroundCheck {
         }
 
         void cancel() {
-            logger.info("cancel: "+threadName);
+            if (!done) {
+                logger.info("cancel: "+threadName);
+            }
             cancelled = true;
         }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 22/38: SLING-4603 : more aggressively clearing the idMap-cache to avoid stale entries on slingId change - plus added getSyncHistory to BaseSyncTokenConsistencyService to allow adding it to the webconsole for debug - plus some cleanup in webconsole wrt discoveryLite info

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit e88458a352bf66b2fa682cb2011fa38e25b10288
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 12:33:15 2015 +0000

    SLING-4603 : more aggressively clearing the idMap-cache to avoid stale entries on slingId change - plus added getSyncHistory to BaseSyncTokenConsistencyService to allow adding it to the webconsole for debug - plus some cleanup in webconsole wrt discoveryLite info
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710003 13f79535-47bb-0310-9956-ffa450edef68
---
 .../spi/base/BaseSyncTokenConsistencyService.java  | 84 ++++++++++++++++++++--
 .../commons/providers/spi/base/IdMapService.java   | 40 ++++++++++-
 .../spi/base/OakSyncTokenConsistencyService.java   |  9 ++-
 3 files changed, 125 insertions(+), 8 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
index fd53e82..ba6d6ec 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
@@ -18,6 +18,13 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
@@ -38,8 +45,19 @@ import org.apache.sling.settings.SlingSettingsService;
  */
 public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
+    class HistoryEntry {
+        BaseTopologyView view;
+        String msg;
+        String fullLine;
+    }
+    
+    /** the date format used in the truncated log of topology events **/
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+
     protected String slingId;
 
+    protected List<HistoryEntry> history = new LinkedList<HistoryEntry>();
+    
     protected abstract DiscoveryLiteConfig getCommonsConfig();
 
     protected abstract ResourceResolverFactory getResourceResolverFactory();
@@ -73,11 +91,13 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
             @Override
             public boolean check() {
                 // 1) first storing my syncToken
-                if (!storeMySyncToken(view.getLocalClusterSyncTokenId())) {
+                final String localClusterSyncTokenId = view.getLocalClusterSyncTokenId();
+                if (!storeMySyncToken(localClusterSyncTokenId)) {
                     // if anything goes wrong above, then this will mean for the others
                     // that they will have to wait until the timeout hits
                     
                     // so to try to avoid this, retry storing my sync token later:
+                    addHistoryEntry(view, "storing my syncToken ("+localClusterSyncTokenId+")");
                     return false;
                 }
                 
@@ -144,20 +164,35 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
             String syncToken = view.getLocalClusterSyncTokenId();
             
             boolean success = true;
+            StringBuffer historyEntry = new StringBuffer();
             for (InstanceDescription instance : view.getLocalInstance().getClusterView().getInstances()) {
                 Object currentValue = syncTokens.get(instance.getSlingId());
                 if (currentValue == null) {
-                    logger.info("seenAllSyncTokens: no syncToken of "+instance.getSlingId());
+                    String msg = "no syncToken yet of "+instance.getSlingId();
+                    logger.info("seenAllSyncTokens: " + msg);
+                    if (historyEntry.length() != 0) {
+                        historyEntry.append(",");
+                    }
+                    historyEntry.append(msg);
                     success = false;
                 } else if (!syncToken.equals(currentValue)) {
-                    logger.info("seenAllSyncTokens: old syncToken of " + instance.getSlingId()
-                            + " : expected=" + syncToken + " got="+currentValue);
+                    String msg = "syncToken of " + instance.getSlingId()
+                                                + " is " + currentValue
+                                                + " waiting for " + syncToken;
+                    logger.info("seenAllSyncTokens: " + msg);
+                    if (historyEntry.length() != 0) {
+                        historyEntry.append(",");
+                    }
+                    historyEntry.append(msg);
                     success = false;
                 }
             }
             if (!success) {
                 logger.info("seenAllSyncTokens: not yet seen all expected syncTokens (see above for details)");
+                addHistoryEntry(view, historyEntry.toString());
                 return false;
+            } else {
+                addHistoryEntry(view, "seen all syncTokens");
             }
             
             resourceResolver.commit();
@@ -176,4 +211,45 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
             }
         }
     }
+    
+    public List<String> getSyncHistory() {
+        List<HistoryEntry> snapshot;
+        synchronized(history) {
+            snapshot = Collections.unmodifiableList(history);
+        }
+        List<String> result = new ArrayList<String>(snapshot.size());
+        for (HistoryEntry historyEntry : snapshot) {
+            result.add(historyEntry.fullLine);
+        }
+        return result;
+    }
+
+    protected void addHistoryEntry(BaseTopologyView view, String msg) {
+        synchronized(history) {
+            for(int i = history.size() - 1; i>=0; i--) {
+                HistoryEntry entry = history.get(i);
+                if (!entry.view.equals(view)) {
+                    // don't filter if the view starts differing,
+                    // only filter for the last few entries where
+                    // the view is equal
+                    break;
+                }
+                if (entry.msg.equals(msg)) {
+                    // if the view is equal and the msg matches
+                    // then this is a duplicate entry, so ignore
+                    return;
+                }
+            }
+            String fullLine = sdf.format(Calendar.getInstance().getTime()) + ": " + msg;
+            HistoryEntry newEntry = new HistoryEntry();
+            newEntry.view = view;
+            newEntry.fullLine = fullLine;
+            newEntry.msg = msg;
+            history.add(newEntry);
+            while (history.size() > 12) {
+                history.remove(0);
+            }
+        }
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
index 20ad3bc..12e8db7 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
@@ -19,6 +19,7 @@
 package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 
 import org.apache.felix.scr.annotations.Activate;
@@ -141,9 +142,42 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
             long me = descriptor.getMyId();
             final Resource resource = ResourceHelper.getOrCreateResource(resourceResolver, getIdMapPath());
             ModifiableValueMap idmap = resource.adaptTo(ModifiableValueMap.class);
-            idmap.put(slingId, me);
+            // check to see if either my slingId is already mapped to another clusterNodeId
+            // or when my clusterNodeId is already mapped to another slingId
+            // in both cases: clean that up
+            boolean foundMe = false;
+            for (String aKey : new HashSet<String>(idmap.keySet())) {
+                Object value = idmap.get(aKey);
+                if (value instanceof Number) {
+                    Number n = (Number)value;
+                    if (n.longValue()==me) {
+                        // my clusterNodeId is already mapped to
+                        // let's check if the key is my slingId
+                        if (aKey.equals(slingId)) {
+                            // perfect
+                            foundMe = true;
+                        } else {
+                            // cleanup necessary
+                            logger.info("init: my clusterNodeId is already mapped to by another slingId, deleting entry: key="+aKey+" mapped to "+value);
+                            idmap.remove(aKey);
+                        }
+                    } else if (aKey.equals(slingId)) {
+                        // cleanup necessary
+                        logger.info("init: my slingId is already mapped to by another clusterNodeId, deleting entry: key="+aKey+" mapped to "+value);
+                        idmap.remove(aKey);
+                    } else {
+                        // that's just some other slingId-clusterNodeId mapping
+                        // leave it unchanged
+                    }
+                }
+            }
+            if (!foundMe) {
+                logger.info("init: added the following mapping: slingId="+slingId+" to discovery-lite id="+me);
+                idmap.put(slingId, me);
+            } else {
+                logger.info("init: mapping already existed, left unchanged: slingId="+slingId+" to discovery-lite id="+me);
+            }
             resourceResolver.commit();
-            logger.info("init: mapped slingId="+slingId+" to discovery-lite id="+me);
             this.me = me;
             initialized = true;
             notifyAll();
@@ -160,6 +194,7 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
     }
     
     public synchronized void clearCache() {
+        logger.info("clearCache: clearing idmap cache");
         idMapCache.clear();
     }
 
@@ -171,6 +206,7 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck {
         }
         // cache-miss
         Map<Integer, String> readMap = readIdMap(resourceResolver);
+        logger.info("toSlingId: cache miss, refreshing idmap cache");
         idMapCache.putAll(readMap);
         return idMapCache.get(clusterNodeId);
     }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
index 24c12dc..55c3411 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
@@ -61,7 +61,7 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
 
     @Reference
     protected SlingSettingsService settingsService;
-
+    
     public static OakSyncTokenConsistencyService testConstructorAndActivate(
             final DiscoveryLiteConfig commonsConfig,
             final IdMapService idMapService,
@@ -141,18 +141,24 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
                 try {
                     if (!idMapService.isInitialized()) {
                         logger.info("waitWhileBacklog: could not initialize...");
+                        addHistoryEntry(view, "could not initialize idMapService");
                         return false;
                     }
                 } catch (Exception e) {
                     logger.error("waitWhileBacklog: could not initialized due to "+e, e);
+                    addHistoryEntry(view, "got Exception while initializing idMapService ("+e+")");
                     return false;
                 }
                 BacklogStatus backlogStatus = getBacklogStatus(view);
                 if (backlogStatus == BacklogStatus.NO_BACKLOG) {
                     logger.info("waitWhileBacklog: no backlog (anymore), done.");
+                    addHistoryEntry(view, "no backlog (anymore)");
                     return true;
                 } else {
                     logger.info("waitWhileBacklog: backlogStatus still "+backlogStatus);
+                    // clear the cache to make sure to get the latest version in case something changed
+                    idMapService.clearCache();
+                    addHistoryEntry(view, "backlog status "+backlogStatus);
                     return false;
                 }
             }
@@ -243,5 +249,4 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
         return settingsService;
     }
     
-
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 38/38: [maven-release-plugin] copy for tag org.apache.sling.discovery.commons-1.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 39d89c521c0aa210ac788f283ce1c60bda112f0b
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 16:10:47 2015 +0000

    [maven-release-plugin] copy for tag org.apache.sling.discovery.commons-1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.discovery.commons-1.0.0@1710643 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 28/38: SLING-5173 : re-added complete consistency-history after introducing splitting them and using the ConsistencyServiceChain. Probably should be refactored into something slightly nicer though

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 3edba1a24d6bce459cfb50cd9743e2d2de985c11
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 16:05:09 2015 +0000

    SLING-5173 : re-added complete consistency-history after introducing splitting them and using the ConsistencyServiceChain. Probably should be refactored into something slightly nicer though
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710047 13f79535-47bb-0310-9956-ffa450edef68
---
 .../base/AbstractServiceWithBackgroundCheck.java   | 63 ----------------
 .../providers/spi/base/ConsistencyHistory.java     | 83 ++++++++++++++++++++++
 .../spi/base/OakBacklogConsistencyService.java     | 23 ++++--
 .../spi/base/SyncTokenConsistencyService.java      | 16 ++++-
 4 files changed, 115 insertions(+), 70 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
index 3e8b93b..29088b1 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/AbstractServiceWithBackgroundCheck.java
@@ -18,17 +18,6 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.ResourceResolver;
-import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -39,21 +28,10 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class AbstractServiceWithBackgroundCheck {
 
-    class HistoryEntry {
-        BaseTopologyView view;
-        String msg;
-        String fullLine;
-    }
-    
-    /** the date format used in the truncated log of topology events **/
-    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
-
     protected final Logger logger = LoggerFactory.getLogger(getClass());
 
     protected String slingId;
 
-    protected List<HistoryEntry> history = new LinkedList<HistoryEntry>();
-    
     /**
      * The BackgroundCheckRunnable implements the details of
      * calling BackgroundCheck.check and looping until it 
@@ -239,45 +217,4 @@ public abstract class AbstractServiceWithBackgroundCheck {
             backgroundOp.triggerCheck();
         }
     }
-    
-    public List<String> getSyncHistory() {
-        List<HistoryEntry> snapshot;
-        synchronized(history) {
-            snapshot = Collections.unmodifiableList(history);
-        }
-        List<String> result = new ArrayList<String>(snapshot.size());
-        for (HistoryEntry historyEntry : snapshot) {
-            result.add(historyEntry.fullLine);
-        }
-        return result;
-    }
-
-    protected void addHistoryEntry(BaseTopologyView view, String msg) {
-        synchronized(history) {
-            for(int i = history.size() - 1; i>=0; i--) {
-                HistoryEntry entry = history.get(i);
-                if (!entry.view.equals(view)) {
-                    // don't filter if the view starts differing,
-                    // only filter for the last few entries where
-                    // the view is equal
-                    break;
-                }
-                if (entry.msg.equals(msg)) {
-                    // if the view is equal and the msg matches
-                    // then this is a duplicate entry, so ignore
-                    return;
-                }
-            }
-            String fullLine = sdf.format(Calendar.getInstance().getTime()) + ": " + msg;
-            HistoryEntry newEntry = new HistoryEntry();
-            newEntry.view = view;
-            newEntry.fullLine = fullLine;
-            newEntry.msg = msg;
-            history.add(newEntry);
-            while (history.size() > 12) {
-                history.remove(0);
-            }
-        }
-    }
-
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyHistory.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyHistory.java
new file mode 100644
index 0000000..a6e793b
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/ConsistencyHistory.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.base;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+
+public class ConsistencyHistory {
+
+    class HistoryEntry {
+        BaseTopologyView view;
+        String msg;
+        String fullLine;
+    }
+    
+    /** the date format used in the truncated log of topology events **/
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+
+    protected List<HistoryEntry> history = new LinkedList<HistoryEntry>();
+    
+    public List<String> getSyncHistory() {
+        List<HistoryEntry> snapshot;
+        synchronized(history) {
+            snapshot = Collections.unmodifiableList(history);
+        }
+        List<String> result = new ArrayList<String>(snapshot.size());
+        for (HistoryEntry historyEntry : snapshot) {
+            result.add(historyEntry.fullLine);
+        }
+        return result;
+    }
+
+    protected void addHistoryEntry(BaseTopologyView view, String msg) {
+        synchronized(history) {
+            for(int i = history.size() - 1; i>=0; i--) {
+                HistoryEntry entry = history.get(i);
+                if (!entry.view.equals(view)) {
+                    // don't filter if the view starts differing,
+                    // only filter for the last few entries where
+                    // the view is equal
+                    break;
+                }
+                if (entry.msg.equals(msg)) {
+                    // if the view is equal and the msg matches
+                    // then this is a duplicate entry, so ignore
+                    return;
+                }
+            }
+            String fullLine = sdf.format(Calendar.getInstance().getTime()) + ": " + msg;
+            HistoryEntry newEntry = new HistoryEntry();
+            newEntry.view = view;
+            newEntry.fullLine = fullLine;
+            newEntry.msg = msg;
+            history.add(newEntry);
+            while (history.size() > 12) {
+                history.remove(0);
+            }
+        }
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
index 48eb477..e972b05 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakBacklogConsistencyService.java
@@ -19,6 +19,7 @@
 package org.apache.sling.discovery.commons.providers.spi.base;
 
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import org.apache.felix.scr.annotations.Activate;
@@ -60,6 +61,8 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
 
     @Reference
     protected SlingSettingsService settingsService;
+
+    private ConsistencyHistory consistencyHistory = new ConsistencyHistory();
     
     public static OakBacklogConsistencyService testConstructorAndActivate(
             final DiscoveryLiteConfig commonsConfig,
@@ -111,6 +114,14 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
         logger.info("activate: activated with slingId="+slingId);
     }
     
+    public void setConsistencyHistory(ConsistencyHistory consistencyHistory) {
+        this.consistencyHistory = consistencyHistory;
+    }
+    
+    public ConsistencyHistory getConsistencyHistory() {
+        return consistencyHistory;
+    }
+    
     /** Get or create a ResourceResolver **/
     protected ResourceResolver getResourceResolver() throws LoginException {
         return resourceResolverFactory.getAdministrativeResourceResolver(null);
@@ -141,24 +152,24 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
                 try {
                     if (!idMapService.isInitialized()) {
                         logger.info("waitWhileBacklog: could not initialize...");
-                        addHistoryEntry(view, "could not initialize idMapService");
+                        consistencyHistory.addHistoryEntry(view, "could not initialize idMapService");
                         return false;
                     }
                 } catch (Exception e) {
                     logger.error("waitWhileBacklog: could not initialized due to "+e, e);
-                    addHistoryEntry(view, "got Exception while initializing idMapService ("+e+")");
+                    consistencyHistory.addHistoryEntry(view, "got Exception while initializing idMapService ("+e+")");
                     return false;
                 }
                 BacklogStatus backlogStatus = getBacklogStatus(view);
                 if (backlogStatus == BacklogStatus.NO_BACKLOG) {
                     logger.info("waitWhileBacklog: no backlog (anymore), done.");
-                    addHistoryEntry(view, "no backlog (anymore)");
+                    consistencyHistory.addHistoryEntry(view, "no backlog (anymore)");
                     return true;
                 } else {
                     logger.info("waitWhileBacklog: backlogStatus still "+backlogStatus);
                     // clear the cache to make sure to get the latest version in case something changed
                     idMapService.clearCache();
-                    addHistoryEntry(view, "backlog status "+backlogStatus);
+                    consistencyHistory.addHistoryEntry(view, "backlog status "+backlogStatus);
                     return false;
                 }
             }
@@ -242,4 +253,8 @@ public class OakBacklogConsistencyService extends AbstractServiceWithBackgroundC
         return settingsService;
     }
 
+    public List<String> getSyncHistory() {
+        return consistencyHistory.getSyncHistory();
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
index 3750946..b469f4a 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenConsistencyService.java
@@ -62,6 +62,8 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
     @Reference
     protected SlingSettingsService settingsService;
 
+    protected ConsistencyHistory consistencyHistory = new ConsistencyHistory();
+
     public static SyncTokenConsistencyService testConstructorAndActivate(
             DiscoveryLiteConfig commonsConfig,
             ResourceResolverFactory resourceResolverFactory,
@@ -97,6 +99,14 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
         logger.info("activate: activated with slingId="+slingId);
     }
     
+    public void setConsistencyHistory(ConsistencyHistory consistencyHistory) {
+        this.consistencyHistory = consistencyHistory;
+    }
+    
+    public ConsistencyHistory getConsistencyHistory() {
+        return consistencyHistory;
+    }
+    
     /** Get or create a ResourceResolver **/
     protected ResourceResolver getResourceResolver() throws LoginException {
         return resourceResolverFactory.getAdministrativeResourceResolver(null);
@@ -130,7 +140,7 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
                     // that they will have to wait until the timeout hits
                     
                     // so to try to avoid this, retry storing my sync token later:
-                    addHistoryEntry(view, "storing my syncToken ("+localClusterSyncTokenId+")");
+                    consistencyHistory.addHistoryEntry(view, "storing my syncToken ("+localClusterSyncTokenId+")");
                     return false;
                 }
                 
@@ -222,10 +232,10 @@ public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCh
             }
             if (!success) {
                 logger.info("seenAllSyncTokens: not yet seen all expected syncTokens (see above for details)");
-                addHistoryEntry(view, historyEntry.toString());
+                consistencyHistory.addHistoryEntry(view, historyEntry.toString());
                 return false;
             } else {
-                addHistoryEntry(view, "seen all syncTokens");
+                consistencyHistory.addHistoryEntry(view, "seen all syncTokens");
             }
             
             resourceResolver.commit();

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 18/38: SLING-5173 : log.info fix

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 81a52c9d906ee82865ce5d790a87143716bc2144
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 13:12:57 2015 +0000

    SLING-5173 : log.info fix
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709828 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/discovery/commons/providers/base/MinEventDelayHandler.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
index 82c18eb..cb37adf 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
@@ -135,7 +135,7 @@ class MinEventDelayHandler {
                     BaseTopologyView topology = (BaseTopologyView) t;
                     
                     if (topology.isCurrent()) {
-                        logger.info("asyncDelay.run: done delaying. got new view: ", topology.toShortString());
+                        logger.info("asyncDelay.run: done delaying. got new view: "+ topology.toShortString());
                         viewStateManager.handleNewViewNonDelayed(topology);
                     } else {
                         logger.info("asyncDelay.run: done delaying. new view (still/again) not current, delaying again");

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 08/38: Update the main reactor to parent 25

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit e599fa8f5fce06be02677755a5910a16023ee57b
Author: Robert Munteanu <ro...@apache.org>
AuthorDate: Mon Oct 5 10:03:45 2015 +0000

    Update the main reactor to parent 25
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1706780 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pom.xml b/pom.xml
index 814dabf..c8b314d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>24</version>
+        <version>25</version>
         <relativePath/>
     </parent>
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 27/38: SLING-5173 : bugfix for leader changes: leader change was treated as a properties change - which was very bad - now it is properly treated as a TOPOLOGY_CHANGED. Note that leader change should not happen in an otherwise unchanged topology - but it can if one instance's discovery.oak bundle for example is restarted, thus getting a lower leaderElectionId. Thus discovery.commons must account for this

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit df6d8658e503e7eb5efdefc2b52fb3a7638485c2
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 15:35:44 2015 +0000

    SLING-5173 : bugfix for leader changes: leader change was treated as a properties change - which was very bad - now it is properly treated as a TOPOLOGY_CHANGED. Note that leader change should not happen in an otherwise unchanged topology - but it can if one instance's discovery.oak bundle for example is restarted, thus getting a lower leaderElectionId. Thus discovery.commons must account for this
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710040 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/base/MinEventDelayHandler.java       |  2 +-
 .../commons/providers/base/ViewStateManagerImpl.java       | 14 +++++++++-----
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
index 068cf7e..a8a0066 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
@@ -87,7 +87,7 @@ class MinEventDelayHandler {
             return false;
         }
         
-        if (viewStateManager.isPropertiesDiff(newView)) {
+        if (viewStateManager.onlyDiffersInProperties(newView)) {
             logger.info("handlesNewView: only properties differ, hence no delaying applicable");
             return false;
         }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index 70fa33f..9fa980b 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -454,7 +454,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                     logger.debug("handleNewViewNonDelayed: we were not in changing state and new view matches old, so - ignoring");
                     return false;
                 }
-                if (previousView==null || !isPropertiesDiff(newView)) {
+                if (previousView==null || !onlyDiffersInProperties(newView)) {
                     logger.debug("handleNewViewNonDelayed: implicitly triggering a handleChanging as we were not in changing state");
                     handleChanging();
                     logger.debug("handleNewViewNonDelayed: implicitly triggering of a handleChanging done");
@@ -475,7 +475,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
             }
             
             // now check if the view indeed changed or if it was just the properties
-            if (!isChanging && isPropertiesDiff(newView)) {
+            if (!isChanging && onlyDiffersInProperties(newView)) {
                 // well then send a properties changed event only
                 // and that one does not go via consistencyservice
                 logger.info("handleNewViewNonDelayed: properties changed to: "+newView);
@@ -552,7 +552,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
         }
     }
 
-    protected boolean isPropertiesDiff(BaseTopologyView newView) {
+    protected boolean onlyDiffersInProperties(BaseTopologyView newView) {
         if (previousView==null) {
             return false;
         }
@@ -571,13 +571,17 @@ public class ViewStateManagerImpl implements ViewStateManager {
         }
         
         for(InstanceDescription oldInstance : previousView.getInstances()) {
-            if (!newIds.contains(oldInstance.getSlingId())) {
+            InstanceDescription newInstance = newView.getInstance(oldInstance.getSlingId());
+            if (newInstance == null) {
+                return false;
+            }
+            if (oldInstance.isLeader() != newInstance.isLeader()) {
                 return false;
             }
         }
         return true;
     }
-
+    
     private void doHandleConsistent(BaseTopologyView newView) {
         logger.trace("doHandleConsistent: start");
         

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 12/38: SLING-5173 : introducing discovery.base which is the sharable parts of discovery.impl for discovery.oak - eg it includes topology connectors and base classes - plus it also includes many it-kind tests of discovery.impl

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 0e44e3d4e98ffc5859ca73e01ac189306aca7c2b
Author: Stefan Egli <st...@apache.org>
AuthorDate: Tue Oct 20 14:12:31 2015 +0000

    SLING-5173 : introducing discovery.base which is the sharable parts of discovery.impl for discovery.oak - eg it includes topology connectors and base classes - plus it also includes many it-kind tests of discovery.impl
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709601 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  27 ++-
 .../sling/discovery/commons/package-info.java      |   2 +-
 .../commons/providers/DefaultClusterView.java      | 180 +++++++++++++++++
 .../providers/DefaultInstanceDescription.java      | 202 ++++++++++++++++++
 ...-info.java => NonLocalInstanceDescription.java} |  21 +-
 .../commons/providers/ViewStateManager.java        |  10 +
 .../providers/impl/MinEventDelayHandler.java       |  33 ++-
 .../providers/impl/ViewStateManagerImpl.java       |  31 ++-
 .../commons/providers/{ => impl}/package-info.java |   4 +-
 .../discovery/commons/providers/package-info.java  |   2 +-
 .../LocalClusterView.java}                         |  23 ++-
 .../impl/AbstractServiceWithBackgroundCheck.java   | 216 ++++++++++++++++++++
 .../providers/spi/impl/DiscoveryLiteConfig.java    |  50 +++++
 .../spi/impl/DiscoveryLiteDescriptor.java          | 151 ++++++++++++++
 .../commons/providers/spi/impl/IdMapService.java   | 196 ++++++++++++++++++
 .../spi/impl/OakSyncTokenConsistencyService.java   | 195 ++++++------------
 .../spi/impl/SyncTokenConsistencyService.java      | 225 +++++----------------
 .../providers/{ => spi/impl}/package-info.java     |   4 +-
 .../commons/providers/{ => spi}/package-info.java  |   4 +-
 .../PropertyNameHelper.java}                       |  18 +-
 .../commons/providers/util/ResourceHelper.java     | 140 +++++++++++++
 .../commons/providers/{ => util}/package-info.java |   4 +-
 .../commons/providers/BaseTopologyViewTest.java    |  69 +++++++
 .../commons/providers/DefaultClusterViewTest.java  | 135 +++++++++++++
 .../providers/DefaultInstanceDescriptionTest.java  | 122 +++++++++++
 ...pleTopologyView.java => DummyTopologyView.java} |  74 +++----
 .../commons/providers/EventFactoryTest.java        | 124 ++++++++++++
 .../NonLocalInstanceDescriptionTest.java}          |  30 ++-
 .../commons/providers/impl/ClusterTest.java        |   6 +-
 ...veryService.java => DummyDiscoveryService.java} |   2 +-
 .../impl/{Listener.java => DummyListener.java}     |   4 +-
 .../{SimpleScheduler.java => DummyScheduler.java}  |   2 +-
 .../commons/providers/impl/SimpleClusterView.java  |  65 ------
 .../providers/impl/SimpleInstanceDescription.java  | 117 -----------
 .../commons/providers/impl/TestHelper.java         |  29 +--
 .../providers/impl/TestMinEventDelayHandler.java   |  56 ++++-
 .../providers/impl/TestViewStateManager.java       | 134 ++++++------
 .../providers/spi/impl/DescriptorHelper.java       |  79 ++++++++
 ...te.java => DiscoveryLiteDescriptorBuilder.java} |  38 +++-
 .../spi/impl/DummySlingSettingsService.java        |  65 ++++++
 .../commons/providers/spi/impl/MockFactory.java    |  85 --------
 ...sitoryHelper.java => RepositoryTestHelper.java} | 139 +++++--------
 .../impl/TestOakSyncTokenConsistencyService.java   | 163 +++++++--------
 43 files changed, 2341 insertions(+), 935 deletions(-)

diff --git a/pom.xml b/pom.xml
index d1a7e8e..2d595d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -31,10 +31,8 @@
     <packaging>bundle</packaging>
     <version>1.0.0-SNAPSHOT</version>
 
-    <name>Apache Sling Discovery Commons Bundle</name>
-    <description>
-        Commons services related to Sling Discovery.
-    </description>
+    <name>Apache Sling Discovery Commons</name>
+    <description>Common services related to Sling Discovery</description>
 
     <scm>
         <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</connection>
@@ -49,6 +47,21 @@
                 <artifactId>maven-bundle-plugin</artifactId>
                 <extensions>true</extensions>
             </plugin>
+			<!-- discovery.commons exports a few test classes for reuse.
+                 In order for others to use these, the test-jar must be built/installed too.
+                 Note that 'mvn -Dmaven.test.skip=true' does NOT build the test-jar,
+                 however 'mvn -DskipTests' does. -->
+             <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
         </plugins>
     </build>
     <dependencies>
@@ -175,5 +188,11 @@
         	<type>bundle</type>
         	<scope>test</scope>
         </dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.settings</artifactId>
+			<version>1.2.2</version>
+            <scope>provided</scope>
+		</dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/discovery/commons/package-info.java b/src/main/java/org/apache/sling/discovery/commons/package-info.java
index e104df0..8ed120a 100644
--- a/src/main/java/org/apache/sling/discovery/commons/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/package-info.java
@@ -18,7 +18,7 @@
  */
 
 /**
- * Provides commons utility for the Discovery API.
+ * Provides commons utility for users for the Discovery API.
  *
  * @version 1.0.0
  */
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/DefaultClusterView.java b/src/main/java/org/apache/sling/discovery/commons/providers/DefaultClusterView.java
new file mode 100644
index 0000000..fd1120b
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/DefaultClusterView.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+
+/**
+ * Default implementation of the ClusterView interface.
+ * <p>
+ * Besides implementing the interface methods it also
+ * adds add/remove of InstanceDescriptions as well as 
+ * implementing equals and hashCode.
+ */
+public class DefaultClusterView implements ClusterView {
+
+    /** the id of this cluster view **/
+    private final String id;
+
+    /** the list of instances as part of this cluster **/
+    private final List<InstanceDescription> instances = new LinkedList<InstanceDescription>();
+
+    public DefaultClusterView(final String id) {
+        if (id == null || id.length() == 0) {
+            throw new IllegalArgumentException("id must not be null");
+        }
+        this.id = id;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null || !(obj instanceof DefaultClusterView)) {
+            return false;
+        }
+        final DefaultClusterView other = (DefaultClusterView) obj;
+        if (!this.id.equals(other.id)) {
+            return false;
+        }
+        if (!this.getLeader().equals(other.getLeader())) {
+            return false;
+        }
+        if (this.instances.size() != other.instances.size()) {
+            return false;
+        }
+        for (Iterator<InstanceDescription> it = instances.iterator(); it
+                .hasNext();) {
+            InstanceDescription instance = it.next();
+            if (!other.instances.contains(instance)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    @Override
+    public String toString() {
+        if (instances.size() == 0) {
+            return "a ClusterView[no instances]";
+        } else if (instances.size() == 1) {
+            return "a ClusterView[1 instance: "+instances.get(0).getSlingId()+"]";
+        } else {
+            StringBuffer sb = new StringBuffer();
+            for (InstanceDescription id : instances) {
+                if (sb.length() != 0) {
+                    sb.append(", ");
+                }
+                sb.append(id.getSlingId());
+            }
+            return "a ClusterView[" + instances.size() + " instances: " + sb.toString() + "]";
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Add the given instance to this cluster and set the cluster on the instance (back pointer)
+     * @param instance the instance to add to this cluster
+     */
+    public void addInstanceDescription(final DefaultInstanceDescription instance) {
+        if (instances.contains(instance)) {
+            throw new IllegalArgumentException("cannot add same instance twice");
+        }
+        if (instance.isLeader() && doGetLeader() != null) {
+            throw new IllegalArgumentException(
+                    "cannot add another leader. there already is one");
+        }
+        instances.add(instance);
+        instance.setClusterView(this);
+    }
+
+    public List<InstanceDescription> getInstances() {
+        if (instances.size() == 0) {
+            throw new IllegalStateException("no instance was ever added");
+        }
+        return Collections.unmodifiableList(instances);
+    }
+
+    public InstanceDescription getLeader() {
+        final InstanceDescription result = doGetLeader();
+        if (result != null) {
+            return result;
+        }
+        throw new IllegalStateException("no leader was added");
+    }
+
+    /**
+     * Lookup the leader of this cluster
+     * @return the leader of this cluster - should never return null
+     */
+    private InstanceDescription doGetLeader() {
+        for (Iterator<InstanceDescription> it = instances.iterator(); it
+                .hasNext();) {
+            InstanceDescription anInstance = it.next();
+            if (anInstance.isLeader()) {
+                return anInstance;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Removes the given instance from this cluster.
+     * <p>
+     * Note that the instance will still have a pointer to this cluster however.
+     * @param instance the instance to remove from this cluster
+     */
+    public boolean removeInstanceDescription(InstanceDescription instance) {
+        return instances.remove(instance);
+    }
+    
+    /**
+     * Returns the local InstanceDescription or null if no local instance is listed
+     * @return the local InstanceDescription or null if no local instance is listed
+     * @throws IllegalStateException if multiple local instances are listed
+     */
+    public InstanceDescription getLocalInstance() {
+        InstanceDescription local = null;
+        for (Iterator<InstanceDescription> it = getInstances().iterator(); 
+                it.hasNext();) {
+            InstanceDescription instance = it.next();
+            if (instance.isLocal()) {
+                if (local!=null) {
+                    throw new IllegalStateException("found multiple local instances!?");
+                }
+                local = instance;
+                break;
+            }
+        }
+        return local;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/DefaultInstanceDescription.java b/src/main/java/org/apache/sling/discovery/commons/providers/DefaultInstanceDescription.java
new file mode 100644
index 0000000..1f09149
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/DefaultInstanceDescription.java
@@ -0,0 +1,202 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.commons.providers.util.PropertyNameHelper;
+
+/**
+ * Base implementation for the InstanceDescription interface.
+ * <p>
+ * Allows creation of the object with clusterview and/or properties null - to be
+ * set later but before usage!
+ * <p>
+ */
+public class DefaultInstanceDescription implements InstanceDescription {
+
+    /** the cluster view of which this instance is part of **/
+    private ClusterView clusterView;
+
+    /** whether this instance is the leader in the cluster **/
+    private boolean isLeader;
+
+    /** whether this instance is the local/own one **/
+    private boolean isLocal;
+
+    /** the sling id of this instance **/
+    private String slingId;
+
+    /** the properties of this instance **/
+    private Map<String, String> properties;
+
+    public DefaultInstanceDescription(final DefaultClusterView clusterView,
+            final boolean isLeader, final boolean isOwn, final String slingId,
+            final Map<String, String> properties) {
+        // slingId must not be null - clusterView and properties can be null though
+        if (slingId == null || slingId.length() == 0) {
+            throw new IllegalArgumentException("slingId must not be null");
+        }
+        this.isLeader = isLeader;
+        this.isLocal = isOwn;
+        this.slingId = slingId;
+        this.properties = filterValidProperties(properties);
+        if (clusterView != null) {
+            clusterView.addInstanceDescription(this);
+            if (this.clusterView == null) {
+                throw new IllegalStateException(
+                        "clusterView should have been set by now");
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+    	final String clusterInfo;
+    	if (clusterView==null) {
+    		clusterInfo = "";
+    	} else {
+    		clusterInfo = ", clusterViewId="+clusterView.getId();
+    	}
+        return "an InstanceDescription[slindId=" + slingId + ", isLeader="
+                + isLeader + ", isOwn=" + isLocal + clusterInfo + ", properties=" + this.properties + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        return slingId.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null || !(obj instanceof DefaultInstanceDescription)) {
+            return false;
+        }
+        final DefaultInstanceDescription other = (DefaultInstanceDescription) obj;
+        if (!this.slingId.equals(other.slingId)) {
+            return false;
+        }
+        if (!this.slingId.equals(other.slingId)) {
+            return false;
+        }
+        if (!properties.equals(other.properties)) {
+            return false;
+        }
+        if (!this.getClusterView().getId()
+                .equals(other.getClusterView().getId())) {
+            return false;
+        }
+        return true;
+    }
+
+    public ClusterView getClusterView() {
+        if (clusterView == null) {
+            throw new IllegalStateException("clusterView was never set");
+        }
+        return clusterView;
+    }
+
+    /**
+     * Sets the cluster on this instance
+     * @param clusterView
+     */
+    void setClusterView(ClusterView clusterView) {
+        if (this.clusterView != null) {
+            throw new IllegalStateException("can only set clusterView once");
+        }
+        if (clusterView == null) {
+            throw new IllegalArgumentException("clusterView must not be null");
+        }
+        this.clusterView = clusterView;
+    }
+
+    public boolean isLeader() {
+        return isLeader;
+    }
+
+    public boolean isLocal() {
+        return isLocal;
+    }
+
+    public String getSlingId() {
+        return slingId;
+    }
+
+    public String getProperty(final String name) {
+        if (properties == null) {
+            throw new IllegalStateException("properties were never set");
+        }
+        return properties.get(name);
+    }
+
+    public Map<String, String> getProperties() {
+        if (properties == null) {
+            throw new IllegalStateException("properties were never set");
+        }
+        return Collections.unmodifiableMap(properties);
+    }
+    
+    /**
+     * Sets the properties of this instance
+     * @param properties
+     */
+    protected void setProperties(final Map<String, String> properties) {
+        if (properties == null) {
+            throw new IllegalArgumentException("properties must not be null");
+        }
+        this.properties = filterValidProperties(properties);
+    }
+
+    /** SLING-2883 : filter (pass-through) valid properties only **/
+	private Map<String, String> filterValidProperties(
+			Map<String, String> rawProps) {
+		if (rawProps==null) {
+			return null;
+		}
+
+		final HashMap<String, String> filteredProps = new HashMap<String, String>();
+		final Set<Entry<String, String>> entries = rawProps.entrySet();
+		final Iterator<Entry<String, String>> it = entries.iterator();
+		while(it.hasNext()) {
+			final Entry<String, String> anEntry = it.next();
+			if (PropertyNameHelper.isValidPropertyName(anEntry.getKey())) {
+				filteredProps.put(anEntry.getKey(), anEntry.getValue());
+			}
+		}
+		return filteredProps;
+	}
+
+	/** for testing only! **/
+    public void setProperty(String key, String value) {
+        if (!PropertyNameHelper.isValidPropertyName(key)) {
+            throw new IllegalArgumentException("key is not a valid property name: "+key);
+        }
+        if (properties == null) {
+            properties = new HashMap<String, String>();
+        }
+        properties.put(key, value);
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/NonLocalInstanceDescription.java
similarity index 58%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/NonLocalInstanceDescription.java
index c254e70..c79c98b 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/NonLocalInstanceDescription.java
@@ -16,14 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.sling.discovery.commons.providers;
+
+import java.util.Map;
+
+import org.apache.sling.discovery.commons.providers.DefaultClusterView;
+import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 
 /**
- * Provides commons utility for the Discovery API.
- *
- * @version 1.0.0
+ * InstanceDescription which represents an instance that is explicitly
+ * not local, ie isOwn==false.
  */
-@Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+public class NonLocalInstanceDescription extends DefaultInstanceDescription {
 
-import aQute.bnd.annotation.Version;
+    public NonLocalInstanceDescription(final DefaultClusterView cluster,
+            final boolean isLeader, final String slingId, final Map<String, String> properties) {
+        // isOwn==false
+        super(cluster, isLeader, false, slingId, properties);
+    }
 
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
index 5ab8fc4..8c71603 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
@@ -88,4 +88,14 @@ public interface ViewStateManager {
      */
     void handleNewView(BaseTopologyView newView);
 
+    /** 
+     * for testing only: wait for any potentially queued async events to be delivered 
+     * before returning.
+     * <p>
+     * @param timeout time in millis to wait for at max - 0 to not wait at all - -1 
+     * to wait indefinitely
+     * @return true if no more async events exist, false if the timeout hit early 
+     */
+    boolean waitForAsyncEvents(long timeout);
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
index d032a8b..f236f21 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
@@ -58,6 +58,9 @@ class MinEventDelayHandler {
             throw new IllegalArgumentException("discoveryService must not be null");
         }
         this.discoveryService = discoveryService;
+        if (scheduler==null) {
+            throw new IllegalArgumentException("scheduler must not be null");
+        }
         this.scheduler = scheduler;
         if (minEventDelaySecs<=0) {
             throw new IllegalArgumentException("minEventDelaySecs must be greater than 0 (is "+minEventDelaySecs+")");
@@ -79,10 +82,18 @@ class MinEventDelayHandler {
             return true;
         }
         
-        if (!viewStateManager.hadPreviousView() 
-                || viewStateManager.isPropertiesDiff(newView) 
-                || viewStateManager.unchanged(newView)) {
-            logger.info("handleNewView: we never had a previous view, so we mustn't delay");
+        if (!viewStateManager.hadPreviousView()) {
+            logger.info("handlesNewView: never had a previous view, hence no delaying applicable");
+            return false;
+        }
+        
+        if (viewStateManager.isPropertiesDiff(newView)) {
+            logger.info("handlesNewView: only properties differ, hence no delaying applicable");
+            return false;
+        }
+        
+        if (viewStateManager.unchanged(newView)) {
+            logger.info("handlesNewView: view is unchanged, hence no delaying applicable");
             return false;
         }
         
@@ -90,6 +101,15 @@ class MinEventDelayHandler {
         if (!triggerAsyncDelaying(newView)) {
             logger.info("handleNewView: could not trigger async delaying, sending new view now.");
             viewStateManager.handleNewViewNonDelayed(newView);
+        } else {
+            // if triggering the async event was successful, then we should also
+            // ensure that we sent out a TOPOLOGY_CHANGING *before* that delayed event hits.
+            //
+            // and, we're still in lock.lock() - so we are safe to do a handleChanging() here
+            // even though there is the very unlikely possibility that the async-delay-thread
+            // would compete - but even if it would, thanks to the lock.lock() that would be safe.
+            // so: we're going to do a handleChanging here:
+            viewStateManager.handleChanging();
         }
         return true;
     }
@@ -165,4 +185,9 @@ class MinEventDelayHandler {
         }
     }
 
+    /** for testing only **/
+    public boolean isDelaying() {
+        return isDelaying;
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
index fe7e527..0b31949 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
@@ -52,7 +52,7 @@ import org.slf4j.LoggerFactory;
  * appropriately. Additionally, the ConsistencyService callback will
  * also be locked using the provided lock object.
  */
-class ViewStateManagerImpl implements ViewStateManager {
+public class ViewStateManagerImpl implements ViewStateManager {
 
     private static final Logger logger = LoggerFactory.getLogger(ViewStateManagerImpl.class);
     
@@ -412,10 +412,13 @@ class ViewStateManagerImpl implements ViewStateManager {
         if (minEventDelayHandler!=null) {
             if (minEventDelayHandler.handlesNewView(newView)) {
                 return;// true;
+            } else {
+                logger.debug("handleNewView: event delaying not applicable this time, invoking hanldeNewViewNonDelayed next.");
             }
+        } else {
+            logger.debug("handleNewView: minEventDelayHandler not set, invoking hanldeNewViewNonDelayed...");
         }
-        logger.debug("handleNewView: minEventDelayHandler not set or not applicable this time, invoking hanldeNewViewNonDelayed...");
-        /*return */handleNewViewNonDelayed(newView);
+        handleNewViewNonDelayed(newView);
     }
 
     boolean handleNewViewNonDelayed(final BaseTopologyView newView) {
@@ -521,10 +524,11 @@ class ViewStateManagerImpl implements ViewStateManager {
                         try{
                             logger.debug("consistencyService.callback.run: lock aquired. (modCnt should be {}, is {})", lastModCnt, modCnt);
                             if (modCnt!=lastModCnt) {
-                                logger.debug("consistencyService.callback.run: modCnt changed (from {} to {}) - ignoring",
+                                logger.info("consistencyService.callback.run: modCnt changed (from {} to {}) - ignoring",
                                         lastModCnt, modCnt);
                                 return;
                             }
+                            logger.info("consistencyService.callback.run: invoking doHandleConsistent.");
                             // else:
                             doHandleConsistent(newView);
                         } finally {
@@ -619,5 +623,24 @@ class ViewStateManagerImpl implements ViewStateManager {
     AsyncEventSender getAsyncEventSender() {
         return asyncEventSender;
     }
+
+    @Override
+    public boolean waitForAsyncEvents(long timeout) {
+        long end = System.currentTimeMillis() + timeout;
+        while(asyncEventSender.hasInFlightEvent() || 
+                (minEventDelayHandler!=null && minEventDelayHandler.isDelaying())) {
+            if (timeout==0) {
+                return false;
+            }
+            if (timeout<0 || System.currentTimeMillis()<end) {
+                try {
+                    Thread.sleep(50);
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
+        }
+        return true;
+    }
     
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/package-info.java
similarity index 87%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/impl/package-info.java
index c254e70..3456fd5 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/package-info.java
@@ -18,12 +18,12 @@
  */
 
 /**
- * Provides commons utility for the Discovery API.
+ * Provides commons implementations for providers of the Discovery API.
  *
  * @version 1.0.0
  */
 @Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+package org.apache.sling.discovery.commons.providers.impl;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
index c254e70..067d576 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
@@ -18,7 +18,7 @@
  */
 
 /**
- * Provides commons utility for the Discovery API.
+ * Provides commons utility for providers of the Discovery API.
  *
  * @version 1.0.0
  */
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
similarity index 61%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
index c254e70..12363af 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/LocalClusterView.java
@@ -16,14 +16,21 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.sling.discovery.commons.providers.spi;
 
-/**
- * Provides commons utility for the Discovery API.
- *
- * @version 1.0.0
- */
-@Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+import org.apache.sling.discovery.commons.providers.DefaultClusterView;
+
+public class LocalClusterView extends DefaultClusterView {
+
+    private final String localClusterSyncTokenId;
 
-import aQute.bnd.annotation.Version;
+    public LocalClusterView(String id, String localClusterSyncTokenId) {
+        super(id);
+        this.localClusterSyncTokenId = localClusterSyncTokenId;
+    }
+    
+    public String getLocalClusterSyncTokenId() {
+        return localClusterSyncTokenId;
+    }
 
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/AbstractServiceWithBackgroundCheck.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/AbstractServiceWithBackgroundCheck.java
new file mode 100644
index 0000000..62d163c
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/AbstractServiceWithBackgroundCheck.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Base class which implements the concept of a 'BackgroundCheck',
+ * a thread that periodically executes a check until that one succeeds.
+ * <p>
+ */
+public abstract class AbstractServiceWithBackgroundCheck {
+
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
+
+    /**
+     * The BackgroundCheckRunnable implements the details of
+     * calling BackgroundCheck.check and looping until it 
+     * returns true
+     */
+    private final class BackgroundCheckRunnable implements Runnable {
+        private final Runnable callback;
+        private final BackgroundCheck check;
+        private final long timeoutMillis;
+        private volatile boolean cancelled;
+        private final String threadName;
+        
+        // for testing only:
+        private final Object waitObj = new Object();
+        private int waitCnt;
+        private volatile boolean done;
+        private long waitInterval;
+
+        private BackgroundCheckRunnable(Runnable callback, 
+                BackgroundCheck check, long timeoutMillis, long waitInterval, String threadName) {
+            this.callback = callback;
+            this.check = check;
+            this.timeoutMillis = timeoutMillis;
+            if (waitInterval <= 0) {
+                throw new IllegalArgumentException("waitInterval must be greater than 0: "+waitInterval);
+            }
+            this.waitInterval = waitInterval;
+            this.threadName = threadName;
+        }
+
+        @Override
+        public void run() {
+            logger.debug("backgroundCheck.run: start");
+            long start = System.currentTimeMillis();
+            try{
+                while(!cancelled()) {
+                    if (check.check()) {
+                        if (callback != null) {
+                            callback.run();
+                        }
+                        return;
+                    }
+                    if (timeoutMillis != -1 && 
+                            (System.currentTimeMillis() > start + timeoutMillis)) {
+                        if (callback == null) {
+                            logger.info("backgroundCheck.run: timeout hit (no callback to invoke)");
+                        } else {
+                            logger.info("backgroundCheck.run: timeout hit, invoking callback.");
+                            callback.run();
+                        }
+                        return;
+                    }
+                    logger.trace("backgroundCheck.run: waiting another sec.");
+                    synchronized(waitObj) {
+                        waitCnt++;
+                        try {
+                            waitObj.notify();
+                            waitObj.wait(waitInterval);
+                        } catch (InterruptedException e) {
+                            logger.debug("backgroundCheck.run: got interrupted");
+                        }
+                    }
+                }
+                logger.debug("backgroundCheck.run: this run got cancelled. {}", check);
+            } catch(RuntimeException re) {
+                logger.error("backgroundCheck.run: RuntimeException: "+re, re);
+                // nevertheless calling runnable.run in this case
+                if (callback != null) {
+                    logger.info("backgroundCheck.run: RuntimeException -> invoking callback");
+                    callback.run();
+                }
+                throw re;
+            } catch(Error er) {
+                logger.error("backgroundCheck.run: Error: "+er, er);
+                // not calling runnable.run in this case!
+                // since Error is typically severe
+                logger.info("backgroundCheck.run: NOT invoking callback");
+                throw er;
+            } finally {
+                logger.debug("backgroundCheck.run: end");
+                synchronized(waitObj) {
+                    done = true;
+                    waitObj.notify();
+                }
+            }
+        }
+        
+        boolean cancelled() {
+            return cancelled;
+        }
+
+        void cancel() {
+            logger.info("cancel: "+threadName);
+            cancelled = true;
+        }
+
+        public void triggerCheck() {
+            synchronized(waitObj) {
+                int waitCntAtStart = waitCnt;
+                waitObj.notify();
+                while(!done && waitCnt<=waitCntAtStart) {
+                    try {
+                        waitObj.wait();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException("got interrupted");
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * A BackgroundCheck is anything that can be periodically
+     * checked until it eventually returns true.
+     */
+    interface BackgroundCheck {
+        
+        boolean check();
+        
+    }
+    
+    protected BackgroundCheckRunnable backgroundCheckRunnable;
+    
+    /**
+     * Cancel the currently ongoing background check if
+     * there is any ongoing.
+     */
+    protected void cancelPreviousBackgroundCheck() {
+        BackgroundCheckRunnable current = backgroundCheckRunnable;
+        if (current!=null) {
+            current.cancel();
+            // leave backgroundCheckRunnable field as is
+            // as that does not represent a memory leak
+            // nor is it a problem to invoke cancel multiple times
+            // but properly synchronizing on just setting backgroundCheckRunnable
+            // back to null is error-prone and overkill
+        }
+    }
+    
+    /**
+     * Start a new BackgroundCheck in a separate thread, that
+     * periodically calls BackgroundCheck.check and upon completion
+     * calls the provided callback.run()
+     * @param threadName the name of the thread (to allow identifying the thread)
+     * @param check the BackgroundCheck to periodically invoke with check()
+     * @param callback the Runnable to invoke upon a successful check()
+     * @param timeoutMillis a timeout at which point the BackgroundCheck is
+     * terminated and no callback is invoked. Note that this happens unnoticed
+     * at the moment, ie there is no feedback about whether a background
+     * check was successfully termianted (ie callback was invoked) or
+     * whether the timeout has hit (that's left as a TODO if needed).
+     */
+    protected void startBackgroundCheck(String threadName, final BackgroundCheck check, final Runnable callback, final long timeoutMillis, final long waitMillis) {
+        // cancel the current one if it's still running
+        cancelPreviousBackgroundCheck();
+        
+        if (check.check()) {
+            // then we're not even going to start the background-thread
+            // we're already done
+            if (callback!=null) {
+                logger.info("backgroundCheck: already done, backgroundCheck successful, invoking callback");
+                callback.run();
+            } else {
+                logger.info("backgroundCheck: already done, backgroundCheck successful. no callback to invoke.");
+            }
+            return;
+        }
+        logger.info("backgroundCheck: spawning background-thread for '"+threadName+"'");
+        backgroundCheckRunnable = new BackgroundCheckRunnable(callback, check, timeoutMillis, waitMillis, threadName);
+        Thread th = new Thread(backgroundCheckRunnable);
+        th.setName(threadName);
+        th.setDaemon(true);
+        th.start();
+    }
+    
+
+    /** for testing only! **/
+    protected void triggerBackgroundCheck() {
+        BackgroundCheckRunnable backgroundOp = backgroundCheckRunnable;
+        if (backgroundOp!=null) {
+            backgroundOp.triggerCheck();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteConfig.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteConfig.java
new file mode 100644
index 0000000..b43a6a4
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteConfig.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+/**
+ * Provides the configuration of a few paths needed by discovery-lite processing services
+ */
+public interface DiscoveryLiteConfig {
+
+    /**
+     * Returns the configured path to store the syncTokens to
+     * @return the configured path to store the syncTokens to
+     */
+    String getSyncTokenPath();
+
+    /**
+     * Returns the configured path to store the idMap to
+     * @return the configured path to store the idMap to
+     */
+    String getIdMapPath();
+
+    /**
+     * Returns the timeout (in milliseconds) to be used when waiting for the sync tokens or id mapping
+     * @return the timeout (in milliseconds) to be used when waiting for the sync tokens or id mapping
+     */
+    long getBgTimeoutMillis();
+    
+    /**
+     * Returns the interval (in milliseconds) to be used when waiting for the sync tokens or id mapping
+     * @return the interval (in milliseconds) to be used when waiting for the sync tokens or id mapping
+     */
+    long getBgIntervalMillis();
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptor.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptor.java
new file mode 100644
index 0000000..eeb1591
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptor.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONObject;
+
+/**
+ * Simplifies access to the underlying JSON-backed oak discovery-lite descriptor
+ */
+public class DiscoveryLiteDescriptor {
+
+    /** TODO: avoid hardcoding the constant here but use an Oak constant class instead if possible */
+    public static final String OAK_DISCOVERYLITE_CLUSTERVIEW = "oak.discoverylite.clusterview";
+
+    /**
+     * {"seq":8,"final":true,"id":"aae34e9a-b08d-409e-be10-9ff4106e5387","me":4,"active":[4],"deactivating":[],"inactive":[1,2,3]}
+     */
+    public static DiscoveryLiteDescriptor getDescriptorFrom(ResourceResolver resourceResolver) throws Exception {
+        Session session = resourceResolver.adaptTo(Session.class);
+        if (session == null) {
+            throw new Exception("Could not adapt resourceResolver to session: "+resourceResolver);
+        }
+        String descriptorStr = session.getRepository().getDescriptor(DiscoveryLiteDescriptor.OAK_DISCOVERYLITE_CLUSTERVIEW);
+        if (descriptorStr == null) {
+            throw new Exception("No Descriptor value available");
+        }
+        JSONObject descriptor = new JSONObject(descriptorStr);
+        return new DiscoveryLiteDescriptor(descriptor);
+    }
+    
+    /** the actual descriptor **/
+    private final JSONObject descriptor;
+
+    DiscoveryLiteDescriptor(JSONObject descriptor) {
+        this.descriptor = descriptor;
+    }
+    
+    /**
+     * Returns the 'me' field of the discovery-lite descriptor
+     * @return the 'me' field of the discovery-lite descriptor
+     * @throws Exception if anything in the descriptor is wrongly formatted
+     */
+    public int getMyId() throws Exception {
+        Object meObj = descriptor.get("me");
+        if (meObj == null || !(meObj instanceof Number)) {
+            throw new Exception("getMyId: 'me' value of descriptor not a Number: "+meObj+" (descriptor: "+descriptor+")");
+        }
+        Number me = (Number)meObj;
+        return me.intValue();
+    }
+    
+    private int[] getArray(String name) throws Exception {
+        Object deactivatingObj = descriptor.get(name);
+        if (deactivatingObj==null || !(deactivatingObj instanceof JSONArray)) {
+            throw new Exception("getArray: '" + name + "' value of descriptor not an array: "+deactivatingObj+" (descriptor: "+descriptor+")");
+        }
+        JSONArray deactivating = (JSONArray) deactivatingObj;
+        int[] result = new int[deactivating.length()];
+        for(int i=0; i<deactivating.length(); i++) {
+            Object obj = deactivating.get(i);
+            if (obj==null || !(obj instanceof Number)) {
+                throw new Exception("getArray: '" + name + "' at "+i+" null or not a number: "+obj+", (descriptor: "+descriptor+")");
+            }
+            result[i] = ((Number)obj).intValue();
+        }
+        return result;
+    }
+
+    /**
+     * Returns the 'deactivating' field of the discovery-lite descriptor
+     * @return the 'deactivating' field of the discovery-lite descriptor
+     * @throws Exception if anything in the descriptor is wrongly formatted
+     */
+    public int[] getDeactivatingIds() throws Exception {
+        return getArray("deactivating");
+    }
+
+    /**
+     * Returns the 'active' field of the discovery-lite descriptor
+     * @return the 'active' field of the discovery-lite descriptor
+     * @throws Exception if anything in the descriptor is wrongly formatted
+     */
+    public int[] getActiveIds() throws Exception {
+        return getArray("active");
+    }
+
+    /**
+     * Returns the 'id' field of the discovery-lite descriptor
+     * @return the 'id' field of the discovery-lite descriptor
+     * @throws Exception if anything in the descriptor is wrongly formatted
+     */
+    public String getViewId() throws Exception {
+        Object idObj = descriptor.get("id");
+        if (idObj == null || !(idObj instanceof String)) {
+            throw new Exception("getMe: 'me' value of descriptor not a String: "+idObj+" (descriptor: "+descriptor+")");
+        }
+        return String.valueOf(idObj);
+    }
+
+    @Override
+    public String toString() {
+        return getDescriptorStr();
+    }
+    
+    /**
+     * Returns the raw toString of the underlying descriptor
+     * @return the raw toString of the underlying descriptor
+     */
+    public String getDescriptorStr() {
+        return descriptor.toString();
+    }
+
+    public Long getSeqNum() throws Exception {
+        Object seqObj = descriptor.get("seq");
+        if (seqObj == null || !(seqObj instanceof Number)) {
+            throw new Exception("getSeqNum: 'seq' value of descriptor not a Number: "+seqObj+" (descriptor: "+descriptor+")");
+        }
+        Number seqNum = (Number)seqObj;
+        return seqNum.longValue();
+    }
+
+    public boolean isFinal() throws Exception {
+        Object finalObj = descriptor.get("final");
+        if (finalObj == null || !(finalObj instanceof Boolean)) {
+            throw new Exception("isFinal: 'final' value of descriptor not a Boolean: "+finalObj+" (descriptor: "+descriptor+")");
+        }
+        Boolean isFinal = (Boolean)finalObj;
+        return isFinal;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/IdMapService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/IdMapService.java
new file mode 100644
index 0000000..da5a191
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/IdMapService.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
+import org.apache.sling.settings.SlingSettingsService;
+
+/**
+ * The IdMapService is responsible for storing a slingId-clusterNodeId
+ * pair to the repository and given all other instances in the cluster
+ * do the same can map clusterNodeIds to slingIds (or vice-versa)
+ */
+@Component(immediate = false)
+@Service
+public class IdMapService extends AbstractServiceWithBackgroundCheck {
+
+    @Reference
+    private ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    private SlingSettingsService settingsService;
+
+    @Reference
+    private DiscoveryLiteConfig commonsConfig;
+    
+    private boolean initialized = false;
+    
+    private String slingId;
+
+    private long me;
+
+    private final Map<Integer, String> idMapCache = new HashMap<Integer, String>();
+
+    /** test-only constructor **/
+    public static IdMapService testConstructor(
+            DiscoveryLiteConfig commonsConfig,
+            SlingSettingsService settingsService, 
+            ResourceResolverFactory resourceResolverFactory) {
+        IdMapService service = new IdMapService();
+        service.commonsConfig = commonsConfig;
+        service.settingsService = settingsService;
+        service.resourceResolverFactory = resourceResolverFactory;
+        service.activate();
+        return service;
+    }
+
+    @Activate
+    protected void activate() {
+        startBackgroundCheck("IdMapService-initializer", new BackgroundCheck() {
+            
+            @Override
+            public boolean check() {
+                try {
+                    return init();
+                } catch (Exception e) {
+                    logger.error("initializer: could not init due to "+e, e);
+                    return false;
+                }
+            }
+        }, null, -1, 1000 /* = 1sec interval */);
+    }
+    
+    /** Get or create a ResourceResolver **/
+    private ResourceResolver getResourceResolver() throws LoginException {
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+    
+    public synchronized long getMyId() {
+        if (!initialized) {
+            return -1;
+        }
+        return me;
+    }
+    
+    /** for testing only **/
+    public synchronized boolean waitForInit(long timeout) {
+        long start = System.currentTimeMillis();
+        while(!initialized && timeout != 0) {
+            try {
+                if (timeout>0) {
+                    long diff = (start+timeout) - System.currentTimeMillis();
+                    if (diff<=0) {
+                        return false;
+                    }
+                    wait(diff);
+                } else {
+                    wait();
+                }
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+        return initialized;
+    }
+    
+    public synchronized boolean isInitialized() {
+        return initialized;
+    }
+
+    private synchronized boolean init() throws LoginException, JSONException, PersistenceException {
+        if (initialized) {
+            return true;
+        }
+        slingId = settingsService.getSlingId();
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            DiscoveryLiteDescriptor descriptor = 
+                    DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+            long me = descriptor.getMyId();
+            final Resource resource = ResourceHelper.getOrCreateResource(resourceResolver, getIdMapPath());
+            ModifiableValueMap idmap = resource.adaptTo(ModifiableValueMap.class);
+            idmap.put(slingId, me);
+            resourceResolver.commit();
+            logger.info("init: mapped slingId="+slingId+" to discovery-lite id="+me);
+            this.me = me;
+            initialized = true;
+            notifyAll();
+            return true;
+        } catch(Exception e) {
+            logger.info("init: init failed: "+e);
+            return false;
+        } finally {
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+        
+    }
+    
+    public synchronized void clearCache() {
+        idMapCache.clear();
+    }
+
+    public synchronized String toSlingId(int clusterNodeId, ResourceResolver resourceResolver) throws PersistenceException {
+        String slingId = idMapCache.get(clusterNodeId);
+        if (slingId!=null) {
+            // cache-hit
+            return slingId;
+        }
+        // cache-miss
+        Map<Integer, String> readMap = readIdMap(resourceResolver);
+        idMapCache.putAll(readMap);
+        return idMapCache.get(clusterNodeId);
+    }
+    
+    private Map<Integer, String> readIdMap(ResourceResolver resourceResolver) throws PersistenceException {
+        Resource resource = ResourceHelper.getOrCreateResource(resourceResolver, getIdMapPath());
+        ValueMap idmapValueMap = resource.adaptTo(ValueMap.class);
+        Map<Integer, String> idmap = new HashMap<Integer, String>();
+        for (String slingId : idmapValueMap.keySet()) {
+            Object value = idmapValueMap.get(slingId);
+            if (value instanceof Number) {
+                Number number = (Number)value;
+                idmap.put(number.intValue(), slingId);
+            }
+        }
+        return idmap;
+    }
+
+    private String getIdMapPath() {
+        return commonsConfig.getIdMapPath();
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
index 10cff5a..bfec3b3 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
@@ -18,125 +18,90 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.impl;
 
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Set;
 
-import javax.jcr.Session;
-
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.api.resource.LoginException;
-import org.apache.sling.api.resource.ModifiableValueMap;
-import org.apache.sling.api.resource.PersistenceException;
-import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.api.resource.ValueMap;
-import org.apache.sling.commons.json.JSONArray;
 import org.apache.sling.commons.json.JSONException;
-import org.apache.sling.commons.json.JSONObject;
 import org.apache.sling.discovery.ClusterView;
 import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.settings.SlingSettingsService;
 
 /**
  * Inherits the 'sync-token' part from the SyncTokenConsistencyService
  * and adds the 'wait while backlog' part to it, based on
  * the Oak discovery-lite descriptor.
  */
+@Component(immediate = false)
+@Service(value = { ConsistencyService.class })
 public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService {
 
-    private static final String IDMAP_PATH = "/var/discovery/commons/idmap";
-
     static enum BacklogStatus {
         UNDEFINED /* when there was an error retrieving the backlog status with oak */,
         HAS_BACKLOG /* when oak's discovery lite descriptor indicated that there is still some backlog */,
         NO_BACKLOG /* when oak's discovery lite descriptor declared we're backlog-free now */
     }
     
-    private final Logger logger = LoggerFactory.getLogger(getClass());
-
-    /** TODO: avoid hardcoding the constant here but use an Oak constant class instead if possible */
-    public static final String OAK_DISCOVERYLITE_CLUSTERVIEW = "oak.discoverylite.clusterview";
+    private long backlogWaitTimeoutMillis;
 
-    private boolean initialized = false;
+    private long backlogWaitIntervalMillis;
 
-    private final long waitWhileBacklogTimeoutMillis;
+    @Reference
+    private IdMapService idMapService;
+    
+    public static OakSyncTokenConsistencyService testConstructorAndActivate(
+            final DiscoveryLiteConfig commonsConfig,
+            final IdMapService idMapService,
+            final SlingSettingsService settingsService,
+            ResourceResolverFactory resourceResolverFactory) {
+        OakSyncTokenConsistencyService service = testConstructor(commonsConfig, idMapService, settingsService, resourceResolverFactory);
+        service.activate();
+        return service;
+    }
     
     /**
-     * 
+     * for testing only!
      * @param resourceResolverFactory
      * @param slingId the local slingId
      * @param syncTokenTimeoutMillis timeout value in millis after which the
      * sync-token process is cancelled - or -1 if no timeout should be used there
-     * @param waitWhileBacklogTimeoutMillis timeout value in millis after which
+     * @param backlogWaitTimeoutMillis timeout value in millis after which
      * the waiting-while-backlog should be cancelled - or -1 if no timeout should be 
      * used there
      * @throws LoginException when the login for initialization failed
      * @throws JSONException when the descriptor wasn't proper json at init time
      */
-    public OakSyncTokenConsistencyService(ResourceResolverFactory resourceResolverFactory,
-            String slingId, long syncTokenTimeoutMillis, long waitWhileBacklogTimeoutMillis) {
-        super(resourceResolverFactory, slingId, syncTokenTimeoutMillis);
-        this.waitWhileBacklogTimeoutMillis = waitWhileBacklogTimeoutMillis;
-        startBackgroundCheck("idmap-initializer", new BackgroundCheck() {
-            
-            @Override
-            public boolean check() {
-                return ensureInitialized();
-            }
-        }, null, -1);
-    }
-    
-    private boolean ensureInitialized() {
-        if (initialized) {
-            return true;
+    public static OakSyncTokenConsistencyService testConstructor(
+            final DiscoveryLiteConfig commonsConfig,
+            final IdMapService idMapService,
+            final SlingSettingsService settingsService,
+            ResourceResolverFactory resourceResolverFactory) {
+        OakSyncTokenConsistencyService service = new OakSyncTokenConsistencyService();
+        if (commonsConfig == null) {
+            throw new IllegalArgumentException("commonsConfig must not be null");
         }
-        logger.info("ensureInitialized: initializing.");
-        try {
-            initialized = init();
-            return initialized;
-        } catch (LoginException e) {
-            logger.error("ensureInitialized: could not login: "+e, e);
-            return false;
-        } catch (JSONException e) {
-            logger.error("ensureInitialized: got JSONException: "+e, e);
-            return false;
-        } catch (PersistenceException e) {
-            logger.error("ensureInitialized: got PersistenceException: "+e, e);
-            return false;
+        if (resourceResolverFactory == null) {
+            throw new IllegalArgumentException("resourceResolverFactory must not be null");
         }
-    }
-    
-    private boolean init() throws LoginException, JSONException, PersistenceException {
-        ResourceResolver resourceResolver = null;
-        try{
-            resourceResolver = getResourceResolver();
-            JSONObject descriptor = getDescriptor(resourceResolver);
-            if (descriptor == null) {
-                logger.info("init: could not yet get descriptor '"+OAK_DISCOVERYLITE_CLUSTERVIEW+"'!");
-                return false;
-            }
-            Object meObj = descriptor.get("me");
-            if (meObj == null || !(meObj instanceof Number)) {
-                logger.info("init: 'me' value of descriptor not a Number: "+meObj+" (descriptor: "+descriptor+")");
-                return false;
-            }
-            Number me = (Number)meObj;
-            final Resource resource = getOrCreateResource(resourceResolver, IDMAP_PATH);
-            ModifiableValueMap idmap = resource.adaptTo(ModifiableValueMap.class);
-            idmap.put(slingId, me.longValue());
-            resourceResolver.commit();
-            logger.info("init: mapped slingId="+slingId+" to discoveryLiteId="+me);
-            return true;
-        } finally {
-            if (resourceResolver!=null) {
-                resourceResolver.close();
-            }
+        if (settingsService == null) {
+            throw new IllegalArgumentException("settingsService must not be null");
         }
-        
+        service.commonsConfig = commonsConfig;
+        service.resourceResolverFactory = resourceResolverFactory;
+        service.syncTokenTimeoutMillis = commonsConfig.getBgTimeoutMillis();
+        service.syncTokenIntervalMillis = commonsConfig.getBgIntervalMillis();
+        service.idMapService = idMapService;
+        service.settingsService = settingsService;
+        service.backlogWaitIntervalMillis = commonsConfig.getBgIntervalMillis();
+        service.backlogWaitTimeoutMillis = commonsConfig.getBgTimeoutMillis();
+        return service;
     }
     
     @Override
@@ -161,12 +126,17 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
     private void waitWhileBacklog(final BaseTopologyView view, final Runnable runnable) {
         // start backgroundChecking until the backlogStatus 
         // is NO_BACKLOG
-        startBackgroundCheck("OakSyncTokenConsistencyService-waitWhileBacklog", new BackgroundCheck() {
+        startBackgroundCheck("OakSyncTokenConsistencyService-backlog-waiting", new BackgroundCheck() {
             
             @Override
             public boolean check() {
-                if (!ensureInitialized()) {
-                    logger.info("waitWhileBacklog: could not initialize...");
+                try {
+                    if (!idMapService.isInitialized()) {
+                        logger.info("waitWhileBacklog: could not initialize...");
+                        return false;
+                    }
+                } catch (Exception e) {
+                    logger.error("waitWhileBacklog: could not initialized due to "+e, e);
                     return false;
                 }
                 BacklogStatus backlogStatus = getBacklogStatus(view);
@@ -178,7 +148,7 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
                     return false;
                 }
             }
-        }, runnable, waitWhileBacklogTimeoutMillis);
+        }, runnable, backlogWaitTimeoutMillis, backlogWaitIntervalMillis);
     }
     
     private BacklogStatus getBacklogStatus(BaseTopologyView view) {
@@ -186,11 +156,9 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
         ResourceResolver resourceResolver = null;
         try{
             resourceResolver = getResourceResolver();
-            JSONObject descriptor = getDescriptor(resourceResolver);
-            if (descriptor == null) {
-                logger.warn("getBacklogStatus: could not get descriptor '"+OAK_DISCOVERYLITE_CLUSTERVIEW+"'!");
-                return BacklogStatus.UNDEFINED;
-            }
+            DiscoveryLiteDescriptor descriptor = 
+                    DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+
             // backlog-free means:
             // 1) 'deactivating' must be empty 
             //     (otherwise we indeed have a backlog)
@@ -205,40 +173,28 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
             // * instances in the view but not contained in the descriptor at all
             //     (this might be the case for just-started instances)
             
-            Object activeObj = descriptor.get("active");
-            JSONArray active = (JSONArray) activeObj;
-            Object deactivatingObj = descriptor.get("deactivating");
-            JSONArray deactivating = (JSONArray) deactivatingObj;
+            int[] activeIds = descriptor.getActiveIds();
+            int[] deactivatingIds = descriptor.getDeactivatingIds();
             // we're not worried about 'inactive' ones - as that could
             // be a larger list filled with legacy entries too
             // plus once the instance is inactive there's no need to 
             // check anything further - that one is then backlog-free
             
             // 1) 'deactivating' must be empty 
-            if (deactivating.length()!=0) {
-                logger.info("getBacklogStatus: there are deactivating instances: "+deactivating);
+            if (deactivatingIds.length!=0) {
+                logger.info("getBacklogStatus: there are deactivating instances: "+deactivatingIds);
                 return BacklogStatus.HAS_BACKLOG;
             }
 
-            Resource resource = getOrCreateResource(resourceResolver, IDMAP_PATH);
-            ValueMap idmapValueMap = resource.adaptTo(ValueMap.class);
             ClusterView cluster = view.getLocalInstance().getClusterView();
             Set<String> slingIds = new HashSet<String>();
             for (InstanceDescription instance : cluster.getInstances()) {
                 slingIds.add(instance.getSlingId());
             }
-            Map<Long, String> idmap = new HashMap<Long, String>();
-            for (String slingId : idmapValueMap.keySet()) {
-                Object value = idmapValueMap.get(slingId);
-                if (value instanceof Number) {
-                    Number number = (Number)value;
-                    idmap.put(number.longValue(), slingId);
-                }
-            }
             
-            for(int i=0; i<active.length(); i++) {
-                Number activeId = (Number) active.get(i);
-                String slingId = idmap.get(activeId.longValue());
+            for(int i=0; i<activeIds.length; i++) {
+                int activeId = activeIds[i];
+                String slingId = idMapService.toSlingId(activeId, resourceResolver);
                 // 2) all ids of the descriptor must have a mapping to slingIds
                 if (slingId == null) {
                     logger.info("getBacklogStatus: no slingId found for active id: "+activeId);
@@ -253,14 +209,8 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
 
             logger.info("getBacklogStatus: no backlog (anymore)");
             return BacklogStatus.NO_BACKLOG;
-        } catch (LoginException e) {
-            logger.error("getBacklogStatus: could not login: "+e, e);
-            return BacklogStatus.UNDEFINED;
-        } catch (JSONException e) {
-            logger.error("getBacklogStatus: got JSONException: "+e, e);
-            return BacklogStatus.UNDEFINED;
-        } catch (PersistenceException e) {
-            logger.error("getBacklogStatus: got PersistenceException: "+e, e);
+        } catch(Exception e) {
+            logger.info("getBacklogStatus: failed to determine backlog status: "+e);
             return BacklogStatus.UNDEFINED;
         } finally {
             logger.trace("getBacklogStatus: end");
@@ -270,20 +220,5 @@ public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService
         }
     }
     
-    /**
-     * {"seq":8,"final":true,"id":"aae34e9a-b08d-409e-be10-9ff4106e5387","me":4,"active":[4],"deactivating":[],"inactive":[1,2,3]}
-     */
-    private JSONObject getDescriptor(ResourceResolver resourceResolver) throws JSONException {
-        Session session = resourceResolver.adaptTo(Session.class);
-        if (session == null) {
-            return null;
-        }
-        String descriptorStr = session.getRepository().getDescriptor(OAK_DISCOVERYLITE_CLUSTERVIEW);
-        if (descriptorStr == null) {
-            return null;
-        }
-        JSONObject descriptor = new JSONObject(descriptorStr);
-        return descriptor;
-    }
 
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
index 27d80a7..42ec63a 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
@@ -18,17 +18,22 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.impl;
 
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
 import org.apache.sling.api.resource.Resource;
 import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.api.resource.ResourceUtil;
 import org.apache.sling.api.resource.ValueMap;
 import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
+import org.apache.sling.settings.SlingSettingsService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,178 +42,62 @@ import org.slf4j.LoggerFactory;
  * but not the 'wait while backlog' part (which is left to subclasses
  * if needed).
  */
-public class SyncTokenConsistencyService implements ConsistencyService {
+@Component(immediate = false)
+@Service(value = { ConsistencyService.class })
+public class SyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
-    private final Logger logger = LoggerFactory.getLogger(getClass());
+    protected final Logger logger = LoggerFactory.getLogger(getClass());
 
-    private static final String SYNCTOKEN_PATH = "/var/discovery/commons/synctokens";
+    /** the config to be used by this class **/
+    @Reference
+    protected DiscoveryLiteConfig commonsConfig;
 
-    private static final String DEFAULT_RESOURCE_TYPE = "sling:Folder";
+    @Reference
+    protected ResourceResolverFactory resourceResolverFactory;
 
-    protected static Resource getOrCreateResource(
-            final ResourceResolver resourceResolver, final String path)
-            throws PersistenceException {
-        return ResourceUtil.getOrCreateResource(resourceResolver, path,
-                DEFAULT_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, true);
-    }
-
-    private final class BackgroundCheckRunnable implements Runnable {
-        private final Runnable callback;
-        private final BackgroundCheck check;
-        private final long timeoutMillis;
-        private volatile boolean cancelled;
-        private final String threadName;
-        
-        // for testing only:
-        private final Object waitObj = new Object();
-        private int waitCnt;
-        private volatile boolean done;
-
-        private BackgroundCheckRunnable(Runnable callback, 
-                BackgroundCheck check, long timeoutMillis, String threadName) {
-            this.callback = callback;
-            this.check = check;
-            this.timeoutMillis = timeoutMillis;
-            this.threadName = threadName;
-        }
+    @Reference
+    protected SlingSettingsService settingsService;
 
-        @Override
-        public void run() {
-            logger.debug("backgroundCheck.run: start");
-            long start = System.currentTimeMillis();
-            try{
-                while(!cancelled()) {
-                    if (check.check()) {
-                        if (callback != null) {
-                            callback.run();
-                        }
-                        return;
-                    }
-                    if (timeoutMillis != -1 && 
-                            (System.currentTimeMillis() > start + timeoutMillis)) {
-                        if (callback == null) {
-                            logger.info("backgroundCheck.run: timeout hit (no callback to invoke)");
-                        } else {
-                            logger.info("backgroundCheck.run: timeout hit, invoking callback.");
-                            callback.run();
-                        }
-                        return;
-                    }
-                    logger.debug("backgroundCheck.run: waiting another sec.");
-                    synchronized(waitObj) {
-                        waitCnt++;
-                        try {
-                            waitObj.notify();
-                            waitObj.wait(1000);
-                        } catch (InterruptedException e) {
-                            logger.info("backgroundCheck.run: got interrupted");
-                        }
-                    }
-                }
-                logger.debug("backgroundCheck.run: this run got cancelled. {}", check);
-            } catch(RuntimeException re) {
-                logger.error("backgroundCheck.run: RuntimeException: "+re, re);
-                // nevertheless calling runnable.run in this case
-                if (callback != null) {
-                    logger.info("backgroundCheck.run: RuntimeException -> invoking callback");
-                    callback.run();
-                }
-                throw re;
-            } catch(Error er) {
-                logger.error("backgroundCheck.run: Error: "+er, er);
-                // not calling runnable.run in this case!
-                // since Error is typically severe
-                logger.info("backgroundCheck.run: NOT invoking callback");
-                throw er;
-            } finally {
-                logger.debug("backgroundCheck.run: end");
-                synchronized(waitObj) {
-                    done = true;
-                    waitObj.notify();
-                }
-            }
-        }
-        
-        boolean cancelled() {
-            return cancelled;
-        }
-
-        void cancel() {
-            logger.info("cancel: "+threadName);
-            cancelled = true;
-        }
-
-        public void triggerCheck() {
-            synchronized(waitObj) {
-                int waitCntAtStart = waitCnt;
-                waitObj.notify();
-                while(!done && waitCnt<=waitCntAtStart) {
-                    try {
-                        waitObj.wait();
-                    } catch (InterruptedException e) {
-                        throw new RuntimeException("got interrupted");
-                    }
-                }
-            }
-        }
-    }
+    protected String slingId;
 
-    interface BackgroundCheck {
-        
-        boolean check();
-        
-    }
+    protected long syncTokenTimeoutMillis;
     
-    protected final ResourceResolverFactory resourceResolverFactory;
-
-    protected final String slingId;
-
-    private final long syncTokenTimeoutMillis;
-
-    protected BackgroundCheckRunnable backgroundCheckRunnable;
+    protected long syncTokenIntervalMillis;
+
+    public static SyncTokenConsistencyService testConstructorAndActivate(
+            DiscoveryLiteConfig commonsConfig,
+            ResourceResolverFactory resourceResolverFactory,
+            SlingSettingsService settingsService) {
+        SyncTokenConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
+        service.activate();
+        return service;
+    }
     
-    public SyncTokenConsistencyService(ResourceResolverFactory resourceResolverFactory,
-            String slingId, long syncTokenTimeoutMillis) {
+    public static SyncTokenConsistencyService testConstructor(
+            DiscoveryLiteConfig commonsConfig,
+            ResourceResolverFactory resourceResolverFactory,
+            SlingSettingsService settingsService) {
+        SyncTokenConsistencyService service = new SyncTokenConsistencyService();
+        if (commonsConfig == null) {
+            throw new IllegalArgumentException("commonsConfig must not be null");
+        }
         if (resourceResolverFactory == null) {
             throw new IllegalArgumentException("resourceResolverFactory must not be null");
         }
-        if (slingId == null || slingId.length() == 0) {
-            throw new IllegalArgumentException("slingId must not be null or empty: "+slingId);
+        if (settingsService == null) {
+            throw new IllegalArgumentException("settingsService must not be null");
         }
-        this.slingId = slingId;
-        this.resourceResolverFactory = resourceResolverFactory;
-        this.syncTokenTimeoutMillis = syncTokenTimeoutMillis;
+        service.commonsConfig = commonsConfig;
+        service.resourceResolverFactory = resourceResolverFactory;
+        service.syncTokenTimeoutMillis = commonsConfig.getBgTimeoutMillis();
+        service.syncTokenIntervalMillis = commonsConfig.getBgIntervalMillis();
+        service.settingsService = settingsService;
+        return service;
     }
     
-    protected void cancelPreviousBackgroundCheck() {
-        BackgroundCheckRunnable current = backgroundCheckRunnable;
-        if (current!=null) {
-            current.cancel();
-            // leave backgroundCheckRunnable field as is
-            // as that does not represent a memory leak
-            // nor is it a problem to invoke cancel multiple times
-            // but properly synchronizing on just setting backgroundCheckRunnable
-            // back to null is error-prone and overkill
-        }
-    }
-    
-    protected void startBackgroundCheck(String threadName, final BackgroundCheck check, final Runnable callback, final long timeoutMillis) {
-        // cancel the current one if it's still running
-        cancelPreviousBackgroundCheck();
-        
-        if (check.check()) {
-            // then we're not even going to start the background-thread
-            // we're already done
-            logger.info("backgroundCheck: already done, backgroundCheck successful, invoking callback");
-            callback.run();
-            return;
-        }
-        logger.info("backgroundCheck: spawning background-thread for '"+threadName+"'");
-        backgroundCheckRunnable = new BackgroundCheckRunnable(callback, check, timeoutMillis, threadName);
-        Thread th = new Thread(backgroundCheckRunnable);
-        th.setName(threadName);
-        th.setDaemon(true);
-        th.start();
+    @Activate
+    protected void activate() {
+        this.slingId = settingsService.getSlingId();
     }
     
     /** Get or create a ResourceResolver **/
@@ -247,7 +136,7 @@ public class SyncTokenConsistencyService implements ConsistencyService {
             public boolean check() {
                 return seenAllSyncTokens(view);
             }
-        }, callback, syncTokenTimeoutMillis);
+        }, callback, syncTokenTimeoutMillis, syncTokenIntervalMillis);
     }
 
     private void storeMySyncToken(String syncTokenId) throws LoginException, PersistenceException {
@@ -255,7 +144,7 @@ public class SyncTokenConsistencyService implements ConsistencyService {
         ResourceResolver resourceResolver = null;
         try{
             resourceResolver = getResourceResolver();
-            final Resource resource = getOrCreateResource(resourceResolver, SYNCTOKEN_PATH);
+            final Resource resource = ResourceHelper.getOrCreateResource(resourceResolver, getSyncTokenPath());
             ModifiableValueMap syncTokens = resource.adaptTo(ModifiableValueMap.class);
             Object currentValue = syncTokens.get(slingId);
             if (currentValue == null || !syncTokenId.equals(currentValue)) {
@@ -271,12 +160,16 @@ public class SyncTokenConsistencyService implements ConsistencyService {
         }
     }
 
+    private String getSyncTokenPath() {
+        return commonsConfig.getSyncTokenPath();
+    }
+
     private boolean seenAllSyncTokens(BaseTopologyView view) {
         logger.trace("seenAllSyncTokens: start");
         ResourceResolver resourceResolver = null;
         try{
             resourceResolver = getResourceResolver();
-            Resource resource = getOrCreateResource(resourceResolver, SYNCTOKEN_PATH);
+            Resource resource = ResourceHelper.getOrCreateResource(resourceResolver, getSyncTokenPath());
             ValueMap syncTokens = resource.adaptTo(ValueMap.class);
             String syncToken = view.getLocalClusterSyncTokenId();
             
@@ -309,12 +202,4 @@ public class SyncTokenConsistencyService implements ConsistencyService {
             }
         }
     }
-
-    /** for testing only! **/
-    protected void triggerBackgroundCheck() {
-        BackgroundCheckRunnable backgroundOp = backgroundCheckRunnable;
-        if (backgroundOp!=null) {
-            backgroundOp.triggerCheck();
-        }
-    }
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/package-info.java
similarity index 86%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/package-info.java
index c254e70..6eeff70 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/package-info.java
@@ -18,12 +18,12 @@
  */
 
 /**
- * Provides commons utility for the Discovery API.
+ * Provides default SPI-implementations used by discovery.commons.providers.impl
  *
  * @version 1.0.0
  */
 @Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+package org.apache.sling.discovery.commons.providers.spi.impl;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
similarity index 87%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
index c254e70..e09f0f5 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/package-info.java
@@ -18,12 +18,12 @@
  */
 
 /**
- * Provides commons utility for the Discovery API.
+ * Provides an SPI for providers, used by discovery.commons.providers.impl
  *
  * @version 1.0.0
  */
 @Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+package org.apache.sling.discovery.commons.providers.spi;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/util/PropertyNameHelper.java
similarity index 69%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/util/PropertyNameHelper.java
index c254e70..835b985 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/util/PropertyNameHelper.java
@@ -16,14 +16,16 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+package org.apache.sling.discovery.commons.providers.util;
 
-/**
- * Provides commons utility for the Discovery API.
- *
- * @version 1.0.0
- */
-@Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+public class PropertyNameHelper {
 
-import aQute.bnd.annotation.Version;
+    /** SLING-2883 : properly test for valid property names **/
+    public static boolean isValidPropertyName(String name) {
+        if (name==null || name.length()==0) {
+            return false;
+        }
+        return name.matches("[a-zA-Z0-9._-]+");
+    }
 
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/util/ResourceHelper.java b/src/main/java/org/apache/sling/discovery/commons/providers/util/ResourceHelper.java
new file mode 100644
index 0000000..7002d3d
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/util/ResourceHelper.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.util;
+
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+
+/**
+ * Some helper methods surrounding resources
+ */
+public class ResourceHelper {
+
+    private static final String DEFAULT_RESOURCE_TYPE = "sling:Folder";
+
+    public static Resource getOrCreateResource(
+            final ResourceResolver resourceResolver, final String path)
+            throws PersistenceException {
+    	return ResourceUtil.getOrCreateResource(resourceResolver, path,
+    	        DEFAULT_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, true);
+    }
+
+    public static boolean deleteResource(
+            final ResourceResolver resourceResolver, final String path) throws PersistenceException {
+        final Resource resource = resourceResolver.getResource(path);
+        if (resource==null) {
+            return false;
+        }
+        resourceResolver.delete(resource);
+        return true;
+    }
+
+    /** Compile a string builder containing the properties of a resource - used for logging **/
+    public static StringBuilder getPropertiesForLogging(final Resource resource) {
+        ValueMap valueMap;
+        try{
+            valueMap = resource.adaptTo(ValueMap.class);
+        } catch(RuntimeException re) {
+            return new StringBuilder("non-existing resource: "+resource+" ("+re.getMessage()+")");
+        }
+        if (valueMap==null) {
+            return new StringBuilder("non-existing resource: "+resource+" (no ValueMap)");
+        }
+        final Set<Entry<String, Object>> entrySet = valueMap.entrySet();
+        final StringBuilder sb = new StringBuilder();
+        for (Iterator<Entry<String, Object>> it = entrySet.iterator(); it
+                .hasNext();) {
+            Entry<String, Object> entry = it.next();
+            sb.append(" ");
+            sb.append(entry.getKey());
+            sb.append("=");
+            sb.append(entry.getValue());
+        }
+        return sb;
+    }
+
+    /**
+     * Move resource to given path. Try to do it optimized via JCR API.
+     * If JCR is not available, fallback to Sling Resource API. 
+     * @param res Source resource
+     * @param path Target path
+     * @throws PersistenceException
+     */
+    public static void moveResource(Resource res, String path) throws PersistenceException {
+        Node node = res.adaptTo(Node.class);
+        if (node != null) {
+            try {
+                Session session = node.getSession();
+                session.move(res.getPath(), path);
+            }
+            catch (RepositoryException re) {
+                throw new PersistenceException("Move from " + res.getPath() + " to " + path + " failed.", re);
+            }
+        }
+        else {
+            moveResourceWithResourceAPI(res, path);
+        }
+    }
+    
+    /**
+     * Move resource to given path with Sling Resource API.
+     * @param res Source resource
+     * @param path target path
+     * @throws PersistenceException
+     */
+    private static void moveResourceWithResourceAPI(Resource res, String path) throws PersistenceException {
+        String parentPath = ResourceUtil.getParent(path);
+        Resource parent = res.getResourceResolver().getResource(parentPath);
+        if (parent == null) {
+            throw new PersistenceException("Parent resource does not exist: " + parentPath);
+        }
+
+        // make move with copy + delete
+        copyResourceWithResourceAPI(res, parent, ResourceUtil.getName(path));
+        res.getResourceResolver().delete(res);
+    }
+
+    /**
+     * Copy resource to given target with Sling Resource API.
+     * @param source Source resource
+     * @param destParent Destination parent
+     * @param name Destination resource name
+     * @throws PersistenceException
+     */
+    private static void copyResourceWithResourceAPI(Resource source, Resource destParent, String name) throws PersistenceException {
+        Resource copy = source.getResourceResolver().create(destParent, name, ResourceUtil.getValueMap(source));
+        Iterator<Resource> children = source.listChildren();
+        while (children.hasNext()) {
+            Resource child = children.next();
+            copyResourceWithResourceAPI(child, copy, child.getName());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
similarity index 87%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
index c254e70..242bce5 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/util/package-info.java
@@ -18,12 +18,12 @@
  */
 
 /**
- * Provides commons utility for the Discovery API.
+ * Provides some static helpers for providers of the Discovery API.
  *
  * @version 1.0.0
  */
 @Version("1.0.0")
-package org.apache.sling.discovery.commons.providers;
+package org.apache.sling.discovery.commons.providers.util;
 
 import aQute.bnd.annotation.Version;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/BaseTopologyViewTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/BaseTopologyViewTest.java
new file mode 100644
index 0000000..c4d39d7
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/BaseTopologyViewTest.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Set;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.junit.Test;
+
+public class BaseTopologyViewTest {
+
+    BaseTopologyView newView() {
+        return new BaseTopologyView() {
+            
+            @Override
+            public InstanceDescription getLocalInstance() {
+                throw new IllegalStateException("not yet impl");
+            }
+            
+            @Override
+            public Set<InstanceDescription> getInstances() {
+                throw new IllegalStateException("not yet impl");
+            }
+            
+            @Override
+            public Set<ClusterView> getClusterViews() {
+                throw new IllegalStateException("not yet impl");
+            }
+            
+            @Override
+            public Set<InstanceDescription> findInstances(InstanceFilter filter) {
+                throw new IllegalStateException("not yet impl");
+            }
+            
+            @Override
+            public String getLocalClusterSyncTokenId() {
+                throw new IllegalStateException("not yet impl");
+            }
+        };
+    }
+    
+    @Test
+    public void testCurrent() throws Exception {
+        BaseTopologyView view = newView();
+        assertTrue(view.isCurrent());
+        view.setNotCurrent();
+        assertTrue(!view.isCurrent());
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/DefaultClusterViewTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/DefaultClusterViewTest.java
new file mode 100644
index 0000000..8e09748
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/DefaultClusterViewTest.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.util.UUID;
+
+import org.junit.Test;
+
+public class DefaultClusterViewTest {
+
+    @Test
+    public void testConstructor() throws Exception {
+
+        try {
+            new DefaultClusterView(null);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        try {
+            new DefaultClusterView("");
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        final String slingId = UUID.randomUUID().toString();
+        DefaultClusterView cv = new DefaultClusterView(slingId);
+        assertEquals(slingId, cv.getId());
+
+        try {
+            cv.getInstances();
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            cv.getLeader();
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        try {
+            cv.getInstances();
+            fail("should complain that there were never any instances added...");
+        } catch (Exception e) {
+            // ok
+        }
+        DefaultInstanceDescription id0 = new DefaultInstanceDescription(
+                null, false, false, UUID.randomUUID().toString(), null);
+        try {
+            cv.getInstances();
+            fail("should complain that there were never any instances added...");
+        } catch (Exception e) {
+            // ok
+        }
+        cv.addInstanceDescription(id0);
+        assertEquals(1, cv.getInstances().size());
+        assertSame(id0, cv.getInstances().get(0));
+        try {
+            cv.addInstanceDescription(id0);
+            fail("can only set clusterview once");
+        } catch (Exception e) {
+            // ok
+        }
+
+        assertEquals(1, cv.getInstances().size());
+        DefaultInstanceDescription id = new DefaultInstanceDescription(
+                cv, true, false, UUID.randomUUID().toString(), null);
+        assertEquals(2, cv.getInstances().size());
+        try {
+            cv.addInstanceDescription(id);
+            fail("can only set clusterview once - already set in constructor above");
+        } catch (Exception e) {
+            // ok
+        }
+        assertEquals(2, cv.getInstances().size());
+        assertSame(id, cv.getInstances().get(1));
+        assertSame(id, cv.getLeader());
+
+        DefaultInstanceDescription id2 = new DefaultInstanceDescription(
+                cv, false, false, UUID.randomUUID().toString(), null);
+        try {
+            cv.addInstanceDescription(id2);
+            fail("can only set clusterview once - already set in constructor above");
+        } catch (Exception e) {
+            // ok
+        }
+        assertEquals(3, cv.getInstances().size());
+        assertSame(id, cv.getLeader());
+
+        try {
+            cv.addInstanceDescription(id);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            cv.addInstanceDescription(id2);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            DefaultInstanceDescription id3 = new DefaultInstanceDescription(
+                    cv, true, false, UUID.randomUUID().toString(), null);
+            cv.addInstanceDescription(id3);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/DefaultInstanceDescriptionTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/DefaultInstanceDescriptionTest.java
new file mode 100644
index 0000000..e026331
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/DefaultInstanceDescriptionTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.Test;
+
+public class DefaultInstanceDescriptionTest {
+
+    @Test
+    public void testConstructor() throws Exception {
+        final String slingId = UUID.randomUUID().toString();
+
+        final DefaultClusterView clusterView = null;
+        final boolean isLeader = false;
+        final boolean isOwn = false;
+        final String theSlingId = null;
+        final Map<String, String> properties = null;
+        try {
+            constructInstanceDescription(clusterView, isLeader, isOwn,
+                    theSlingId, properties);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            constructInstanceDescription(null, false, false, "", null);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            constructInstanceDescription(null, false, false, slingId, null)
+                    .setClusterView(null);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            constructInstanceDescription(null, false, false, slingId, null)
+                    .setProperties(null);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        DefaultInstanceDescription id = constructInstanceDescription(null,
+                false, false, slingId, null);
+        id.setClusterView(new DefaultClusterView(UUID.randomUUID()
+                .toString()));
+        try {
+            id.setClusterView(new DefaultClusterView(UUID.randomUUID()
+                    .toString()));
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        assertEquals(slingId,
+                constructInstanceDescription(null, false, false, slingId, null)
+                        .getSlingId());
+        assertEquals(true,
+                constructInstanceDescription(null, true, false, slingId, null)
+                        .isLeader());
+        assertEquals(false,
+                constructInstanceDescription(null, false, false, slingId, null)
+                        .isLeader());
+        assertEquals(false,
+                constructInstanceDescription(null, false, false, slingId, null)
+                        .isLocal());
+
+    }
+
+    @Test
+    public void testNotOwnInstance() throws Exception {
+        final String slingId = UUID.randomUUID().toString();
+        assertEquals(true,
+                constructInstanceDescription(null, false, true, slingId, null)
+                        .isLocal());
+    }
+
+    @Test
+    public void testPropertiesSetting() throws Exception {
+        String slingId = UUID.randomUUID().toString();
+        DefaultInstanceDescription id = constructInstanceDescription(null,
+                false, false, slingId, null);
+        id.setProperties(new HashMap<String, String>());
+        // it is actually ok to set the properties multiple times...
+        id.setProperties(new HashMap<String, String>());
+        id.setProperties(new HashMap<String, String>());
+        id.setProperties(new HashMap<String, String>());
+    }
+
+    public DefaultInstanceDescription constructInstanceDescription(
+            final DefaultClusterView clusterView, final boolean isLeader,
+            final boolean isOwn, final String theSlingId,
+            final Map<String, String> properties) throws Exception {
+        return new DefaultInstanceDescription(clusterView, isLeader, isOwn,
+                theSlingId, properties);
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java b/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
similarity index 68%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
index e0add44..d1962f8 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/DummyTopologyView.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.sling.discovery.commons.providers.impl;
+package org.apache.sling.discovery.commons.providers;
 
 import java.util.HashMap;
 import java.util.HashSet;
@@ -30,28 +30,27 @@ import java.util.UUID;
 import org.apache.sling.discovery.ClusterView;
 import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.InstanceFilter;
-import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 
-public class SimpleTopologyView extends BaseTopologyView {
+public class DummyTopologyView extends BaseTopologyView {
 
     private List<InstanceDescription> instances = new LinkedList<InstanceDescription>();
 
     private final String id;
 
-    public SimpleTopologyView() {
+    public DummyTopologyView() {
         id = UUID.randomUUID().toString();
     }
     
-    public SimpleTopologyView(String id) {
+    public DummyTopologyView(String id) {
         this.id = id;
     }
     
     @Override
     public boolean equals(Object obj) {
-        if (!(obj instanceof SimpleTopologyView)) {
+        if (!(obj instanceof DummyTopologyView)) {
             return false;
         }
-        final SimpleTopologyView other = (SimpleTopologyView) obj;
+        final DummyTopologyView other = (DummyTopologyView) obj;
         if (this==other) {
             return true;
         }
@@ -139,43 +138,37 @@ public class SimpleTopologyView extends BaseTopologyView {
         return id;
     }
 
-    public SimpleTopologyView addInstance() {
+    public DummyTopologyView addInstance() {
         final String slingId = UUID.randomUUID().toString();
         final String clusterId = UUID.randomUUID().toString();
-        final SimpleClusterView cluster = new SimpleClusterView(clusterId);
-        final SimpleInstanceDescription instance = new SimpleInstanceDescription(true, true, slingId, null);
-        instance.setClusterView(cluster);
-        cluster.addInstanceDescription(instance);
+        final DefaultClusterView cluster = new DefaultClusterView(clusterId);
+        final DefaultInstanceDescription instance = new DefaultInstanceDescription(cluster, true, true, slingId, new HashMap<String, String>());
         instances.add(instance);
         return this;
     }
 
-    public SimpleTopologyView addInstance(String slingId, SimpleClusterView cluster, boolean isLeader, boolean isLocal) {
-        final SimpleInstanceDescription instance = new SimpleInstanceDescription(isLeader, isLocal, slingId, null);
-        instance.setClusterView(cluster);
-        cluster.addInstanceDescription(instance);
+    public DummyTopologyView addInstance(String slingId, DefaultClusterView cluster, boolean isLeader, boolean isLocal) {
+        final DefaultInstanceDescription instance = new DefaultInstanceDescription(cluster, isLeader, isLocal, slingId, new HashMap<String, String>());
         instances.add(instance);
         return this;
     }
 
-    public SimpleTopologyView addInstance(InstanceDescription artefact) {
-        final String slingId = artefact.getSlingId();
-        final boolean isLeader = artefact.isLeader();
-        final boolean isLocal = artefact.isLocal();
-        SimpleClusterView cluster = (SimpleClusterView) artefact.getClusterView();
-        final SimpleInstanceDescription instance = new SimpleInstanceDescription(isLeader, isLocal, slingId, artefact.getProperties());
-        instance.setClusterView(cluster);
-        cluster.addInstanceDescription(instance);
-        instances.add(instance);
-        return this;
-    }
+//    public SimpleTopologyView addInstance(InstanceDescription artefact) {
+//        final String slingId = artefact.getSlingId();
+//        final boolean isLeader = artefact.isLeader();
+//        final boolean isLocal = artefact.isLocal();
+//        DefaultClusterViewImpl cluster = (DefaultClusterViewImpl) artefact.getClusterView();
+//        final DefaultInstanceDescriptionImpl instance = new DefaultInstanceDescriptionImpl(cluster, isLeader, isLocal, slingId, artefact.getProperties());
+//        instances.add(instance);
+//        return this;
+//    }
 
-    public SimpleTopologyView removeInstance(String slingId) {
+    public DummyTopologyView removeInstance(String slingId) {
         for (Iterator<InstanceDescription> it = instances.iterator(); it.hasNext();) {
             InstanceDescription id = (InstanceDescription) it.next();
             if (id.getSlingId().equals(slingId)) {
                 it.remove();
-                SimpleClusterView cluster = (SimpleClusterView) id.getClusterView();
+                DefaultClusterView cluster = (DefaultClusterView) id.getClusterView();
                 if (!cluster.removeInstanceDescription(id)) {
                     throw new IllegalStateException("could not remove id: "+id);
                 }
@@ -185,21 +178,20 @@ public class SimpleTopologyView extends BaseTopologyView {
         throw new IllegalStateException("instance not found: "+slingId);
     }
 
-    public static SimpleTopologyView clone(final SimpleTopologyView view) {
-        final SimpleTopologyView result = new SimpleTopologyView(view.id);
+    public static DummyTopologyView clone(final DummyTopologyView view) {
+        final DummyTopologyView result = new DummyTopologyView(view.id);
         final Iterator<InstanceDescription> it = view.getInstances().iterator();
-        Map<String,SimpleClusterView> clusters = new HashMap<String, SimpleClusterView>();
+        Map<String,DefaultClusterView> clusters = new HashMap<String, DefaultClusterView>();
         while(it.hasNext()) {
             InstanceDescription id = it.next();
-            SimpleInstanceDescription clone = clone(id);
             String clusterId = id.getClusterView().getId();
-            SimpleClusterView cluster = clusters.get(clusterId);
+            DefaultClusterView cluster = clusters.get(clusterId);
             if (cluster==null) {
-                cluster = new SimpleClusterView(clusterId);
+                cluster = new DefaultClusterView(clusterId);
                 clusters.put(clusterId, cluster);
             }
-            clone.setClusterView(cluster);
-            result.addInstance(clone);
+            DefaultInstanceDescription clone = clone(cluster, id);
+            result.addInstanceDescription(clone);
         }
         if (!view.isCurrent()) {
             result.setNotCurrent();
@@ -207,11 +199,11 @@ public class SimpleTopologyView extends BaseTopologyView {
         return result;
     }
     
-    private static SimpleInstanceDescription clone(InstanceDescription id) {
-        return new SimpleInstanceDescription(id.isLeader(), id.isLocal(), id.getSlingId(), id.getProperties());
+    private static DefaultInstanceDescription clone(DefaultClusterView cluster, InstanceDescription id) {
+        return new DefaultInstanceDescription(cluster, id.isLeader(), id.isLocal(), id.getSlingId(), id.getProperties());
     }
 
-    public SimpleTopologyView clone() {
-        return SimpleTopologyView.clone(this);
+    public DummyTopologyView clone() {
+        return DummyTopologyView.clone(this);
     }
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java
new file mode 100644
index 0000000..eea02e1
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/EventFactoryTest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class EventFactoryTest {
+
+    BaseTopologyView newView() {
+        return new DummyTopologyView();
+    }
+    
+    @Test
+    public void testInitEvent() throws Exception {
+        try{
+            EventFactory.newInitEvent(null);
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        EventFactory.newInitEvent(newView());
+    }
+    
+    @Test
+    public void testChangingEvent() throws Exception {
+        try{
+            EventFactory.newChangingEvent(null);
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newChangingEvent(newView());
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        BaseTopologyView view = newView();
+        view.setNotCurrent();
+        EventFactory.newChangingEvent(view);
+    }
+
+    @Test
+    public void testChangedEvent() throws Exception {
+        try{
+            EventFactory.newChangedEvent(null, null);
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newChangedEvent(newView(), null);
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newChangedEvent(null, newView());
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newChangedEvent(newView(), newView());
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        BaseTopologyView oldView = newView();
+        oldView.setNotCurrent();
+        BaseTopologyView newView = newView();
+        EventFactory.newChangedEvent(oldView, newView);
+    }
+    
+    @Test
+    public void testPropertiesEvent() throws Exception {
+        try{
+            EventFactory.newPropertiesChangedEvent(null, null);
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newPropertiesChangedEvent(newView(), null);
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newPropertiesChangedEvent(null, newView());
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        try{
+            EventFactory.newPropertiesChangedEvent(newView(), newView());
+            fail("should complain");
+        } catch(Exception e) {
+            // ok
+        }
+        BaseTopologyView oldView = newView();
+        oldView.setNotCurrent();
+        BaseTopologyView newView = newView();
+        EventFactory.newPropertiesChangedEvent(oldView, newView);
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java b/src/test/java/org/apache/sling/discovery/commons/providers/NonLocalInstanceDescriptionTest.java
similarity index 56%
copy from src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
copy to src/test/java/org/apache/sling/discovery/commons/providers/NonLocalInstanceDescriptionTest.java
index c254e70..9947651 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/package-info.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/NonLocalInstanceDescriptionTest.java
@@ -16,14 +16,28 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-
-/**
- * Provides commons utility for the Discovery API.
- *
- * @version 1.0.0
- */
-@Version("1.0.0")
 package org.apache.sling.discovery.commons.providers;
 
-import aQute.bnd.annotation.Version;
+import java.util.Map;
+
+import org.junit.Test;
+
+public class NonLocalInstanceDescriptionTest extends DefaultInstanceDescriptionTest {
+
+    @Override
+    public DefaultInstanceDescription constructInstanceDescription(
+            DefaultClusterView clusterView, boolean isLeader,
+            boolean isOwn, String theSlingId, Map<String, String> properties)
+            throws Exception {
+
+        return new NonLocalInstanceDescription(clusterView, isLeader,
+                theSlingId, properties);
+    }
+
+    @Override
+    @Test
+    public void testNotOwnInstance() throws Exception {
+        // exclude that one - since incoming instancedescription is always !own!
+    }
 
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
index d43a94c..6ad305b 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
@@ -82,7 +82,7 @@ public class ClusterTest {
         }
     }
     
-    private void assertCountEvents(ViewStateManagerImpl mgr, Listener l, TopologyEvent.Type... types) throws InterruptedException {
+    private void assertCountEvents(ViewStateManagerImpl mgr, DummyListener l, TopologyEvent.Type... types) throws InterruptedException {
         waitForInflightEvents(mgr);
         assertEquals(types.length, l.countEvents());
         Iterator<TopologyEvent> it = l.getEvents().iterator();
@@ -123,12 +123,12 @@ public class ClusterTest {
         final String slingId2 = UUID.randomUUID().toString();
         
         // bind l1
-        Listener l1 = new Listener();
+        DummyListener l1 = new DummyListener();
         mgr1.bind(l1);
         assertCountEvents(mgr1, l1);
         
         // bind l2
-        Listener l2 = new Listener();
+        DummyListener l2 = new DummyListener();
         mgr2.bind(l2);
         assertCountEvents(mgr2, l2);
         
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleDiscoveryService.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyDiscoveryService.java
similarity index 95%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleDiscoveryService.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyDiscoveryService.java
index fabe0f6..2ad8a61 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleDiscoveryService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyDiscoveryService.java
@@ -22,7 +22,7 @@ import org.apache.sling.discovery.DiscoveryService;
 import org.apache.sling.discovery.TopologyView;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 
-public class SimpleDiscoveryService implements DiscoveryService {
+public class DummyDiscoveryService implements DiscoveryService {
 
     private BaseTopologyView topologyView;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/Listener.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyListener.java
similarity index 93%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/Listener.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyListener.java
index fa07cd5..d2fb4aa 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/Listener.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyListener.java
@@ -28,13 +28,15 @@ import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEventListener;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 
-public class Listener implements TopologyEventListener {
+public class DummyListener implements TopologyEventListener {
 
+    private List<TopologyEvent> allEvents = new LinkedList<TopologyEvent>();
     private List<TopologyEvent> events = new LinkedList<TopologyEvent>();
     private TopologyEvent lastEvent;
     
     public synchronized void handleTopologyEvent(TopologyEvent event) {
         events.add(event);
+        allEvents.add(event);
         lastEvent = event;
     }
     
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleScheduler.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyScheduler.java
similarity index 98%
rename from src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleScheduler.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyScheduler.java
index be28878..b9a149b 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleScheduler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/DummyScheduler.java
@@ -25,7 +25,7 @@ import java.util.NoSuchElementException;
 
 import org.apache.sling.commons.scheduler.Scheduler;
 
-public class SimpleScheduler implements Scheduler {
+public class DummyScheduler implements Scheduler {
 
     private boolean failMode;
 
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleClusterView.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleClusterView.java
deleted file mode 100644
index 74b7812..0000000
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleClusterView.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.discovery.commons.providers.impl;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.apache.sling.discovery.ClusterView;
-import org.apache.sling.discovery.InstanceDescription;
-
-public class SimpleClusterView implements ClusterView {
-
-    private final String id;
-    private List<InstanceDescription> instances = new LinkedList<InstanceDescription>();
-
-    public SimpleClusterView(String id) {
-        this.id = id;
-    }
-    
-    @Override
-    public String getId() {
-        return id;
-    }
-    
-    public void addInstanceDescription(InstanceDescription id) {
-        instances.add(id);
-    }
-
-    public boolean removeInstanceDescription(InstanceDescription id) {
-        return instances.remove(id);
-    }
-
-    @Override
-    public List<InstanceDescription> getInstances() {
-        return Collections.unmodifiableList(instances);
-    }
-
-    @Override
-    public InstanceDescription getLeader() {
-        for (InstanceDescription instanceDescription : instances) {
-            if (instanceDescription.isLeader()) {
-                return instanceDescription;
-            }
-        }
-        throw new IllegalStateException("no leader");
-    }
-
-}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
deleted file mode 100644
index f6c8893..0000000
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.discovery.commons.providers.impl;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.sling.discovery.ClusterView;
-import org.apache.sling.discovery.InstanceDescription;
-
-public class SimpleInstanceDescription implements InstanceDescription {
-
-    private ClusterView clusterView;
-    private final boolean isLeader;
-    private final boolean isLocal;
-    private final String slingId;
-    private Map<String, String> properties;
-
-    public SimpleInstanceDescription(boolean isLeader, boolean isLocal, String slingId,
-            Map<String, String> properties) {
-        this.isLeader = isLeader;
-        this.isLocal = isLocal;
-        this.slingId = slingId;
-        this.properties = properties;
-    }
-    
-    @Override
-    public String toString() {
-        return "Instance["+slingId+"]";
-    }
-    
-    @Override
-    public int hashCode() {
-        return slingId.hashCode() + (isLeader?0:1) + (isLocal?0:1);
-    }
-    
-    @Override
-    public boolean equals(Object obj) {
-        if (!(obj instanceof SimpleInstanceDescription)) {
-            return false;
-        }
-        SimpleInstanceDescription other = (SimpleInstanceDescription) obj;
-        if (!slingId.equals(other.slingId)) {
-            return false;
-        }
-        if (isLeader!=other.isLeader) {
-            return false;
-        }
-        if (isLocal!=other.isLocal) {
-            return false;
-        }
-        Map<String, String> myProperties = getProperties();
-        Map<String, String> otherProperties = other.getProperties();
-        return (myProperties.equals(otherProperties));
-    }
-
-    public void setClusterView(ClusterView clusterView) {
-        this.clusterView = clusterView;
-    }
-    
-    @Override
-    public ClusterView getClusterView() {
-        return clusterView;
-    }
-
-    @Override
-    public boolean isLeader() {
-        return isLeader;
-    }
-
-    @Override
-    public boolean isLocal() {
-        return isLocal;
-    }
-
-    @Override
-    public String getSlingId() {
-        return slingId;
-    }
-
-    @Override
-    public String getProperty(String name) {
-        return properties.get(name);
-    }
-
-    @Override
-    public Map<String, String> getProperties() {
-        if (properties==null) {
-            return new HashMap<String, String>();
-        }
-        return new HashMap<String,String>(properties);
-    }
-
-    public void setProperty(String key, String value) {
-        if (properties==null) {
-            properties = new HashMap<String, String>();
-        }
-        properties.put(key, value);
-    }
-
-}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
index 057aeae..4ea03b1 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
@@ -22,12 +22,16 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
+import java.util.HashMap;
 import java.util.Random;
 import java.util.UUID;
 
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.DefaultClusterView;
+import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -35,7 +39,7 @@ public class TestHelper {
 
     private static final Logger logger = LoggerFactory.getLogger(TestHelper.class);
 
-    public static void assertEvents(ViewStateManagerImpl mgr, Listener listener, TopologyEvent... events) {
+    public static void assertEvents(ViewStateManagerImpl mgr, DummyListener listener, TopologyEvent... events) {
         waitForAsyncEvents(mgr);
         assertEquals(events.length, listener.countEvents());
         for (int i = 0; i < events.length; i++) {
@@ -85,14 +89,14 @@ public class TestHelper {
         }
     }
 
-    public static void assertNoEvents(Listener listener) {
+    public static void assertNoEvents(DummyListener listener) {
         assertEquals(0, listener.countEvents());
     }
 
     /** does couple loops randomly calling handleChanging() (or not) and then handleNewView().
      * Note: random is passed to allow customizing and not hardcoding this method to a particular random 
      * @throws InterruptedException **/
-    public static void randomEventLoop(ViewStateManagerImpl mgr, SimpleDiscoveryService sds, int loopSize, int delayInMillis, final Random random, Listener... listeners) throws InterruptedException {
+    public static void randomEventLoop(ViewStateManagerImpl mgr, DummyDiscoveryService sds, int loopSize, int delayInMillis, final Random random, DummyListener... listeners) throws InterruptedException {
         for(int i=0; i<loopSize; i++) {
             final boolean shouldCallChanging = random.nextBoolean();
             if (shouldCallChanging) {
@@ -113,7 +117,7 @@ public class TestHelper {
                     assertNoEvents(listeners[j]);
                 }
             }
-            final BaseTopologyView view = new SimpleTopologyView().addInstance();
+            final DummyTopologyView view = new DummyTopologyView().addInstance();
             BaseTopologyView[] lastViews = new BaseTopologyView[listeners.length];
             for(int j=0; j<listeners.length; j++) {
                 lastViews[j] = listeners[j].getLastView();
@@ -122,6 +126,7 @@ public class TestHelper {
             if (sds!=null) {
                 sds.setTopoology(view);
             }
+            DummyTopologyView clonedView = view.clone();
             mgr.handleNewView(view);
             if (delayInMillis>0) {
                 logger.debug("randomEventLoop: waiting "+delayInMillis+"ms ...");
@@ -137,24 +142,22 @@ public class TestHelper {
             } else {
                 logger.debug("randomEventLoop: asserting CHANGED event was sent");
                 for(int j=0; j<listeners.length; j++) {
-                    assertEvents(mgr, listeners[j], EventFactory.newChangedEvent(lastViews[j], view));
+                    assertEvents(mgr, listeners[j], EventFactory.newChangedEvent(lastViews[j], clonedView));
                 }
             }
         }
     }
 
-    public static SimpleTopologyView newView(boolean isCurrent, String leaderId, String localId, String... slingIds) {
+    public static DummyTopologyView newView(boolean isCurrent, String leaderId, String localId, String... slingIds) {
         return newView(UUID.randomUUID().toString(), UUID.randomUUID().toString(), isCurrent, leaderId, localId, slingIds);
     }
 
-    public static SimpleTopologyView newView(String syncId, String clusterId, boolean isCurrent, String leaderId, String localId, String... slingIds) {
-        SimpleTopologyView topology = new SimpleTopologyView(syncId);
-        SimpleClusterView cluster = new SimpleClusterView(clusterId);
+    public static DummyTopologyView newView(String syncId, String clusterId, boolean isCurrent, String leaderId, String localId, String... slingIds) {
+        DummyTopologyView topology = new DummyTopologyView(syncId);
+        DefaultClusterView cluster = new DefaultClusterView(clusterId);
         for (String slingId : slingIds) {
-            SimpleInstanceDescription id = new SimpleInstanceDescription(
-                    slingId.equals(leaderId), slingId.equals(localId), slingId, null);
-            id.setClusterView(cluster);
-            cluster.addInstanceDescription(id);
+            DefaultInstanceDescription id = new DefaultInstanceDescription(cluster,
+                    slingId.equals(leaderId), slingId.equals(localId), slingId, new HashMap<String, String>());
             topology.addInstanceDescription(id);
         }
         if (!isCurrent) {
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
index f514676..82e03fb 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
@@ -19,17 +19,22 @@
 package org.apache.sling.discovery.commons.providers.impl;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 
 import java.util.Random;
+import java.util.UUID;
 import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -42,11 +47,11 @@ public class TestMinEventDelayHandler {
     
     private Random defaultRandom;
     
-    private SimpleDiscoveryService sds;
+    private DummyDiscoveryService sds;
 
     private Level logLevel;
 
-    private SimpleScheduler scheduler;
+    private DummyScheduler scheduler;
 
     @Before
     public void setup() throws Exception {
@@ -58,8 +63,8 @@ public class TestMinEventDelayHandler {
         });
         defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
         
-        scheduler = new SimpleScheduler();
-        sds = new SimpleDiscoveryService();
+        scheduler = new DummyScheduler();
+        sds = new DummyDiscoveryService();
         mgr.installMinEventDelayHandler(sds, scheduler, 1);
 
         final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
@@ -75,13 +80,13 @@ public class TestMinEventDelayHandler {
         discoveryLogger.setLevel(logLevel);
     }
     
-    private void assertNoEvents(Listener listener) {
+    private void assertNoEvents(DummyListener listener) {
         assertEquals(0, listener.countEvents());
     }
 
-    @Test
+    @Test @Ignore
     public void testNormalDelaying() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         // first activate
         logger.info("testNormalDelaying: calling handleActivated...");
         mgr.handleActivated();
@@ -93,7 +98,7 @@ public class TestMinEventDelayHandler {
         logger.info("testNormalDelaying: calling handleChanging...");
         mgr.handleChanging();
         assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         logger.info("testNormalDelaying: calling handleNewView...");
         mgr.handleNewView(view);
         TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
@@ -104,10 +109,10 @@ public class TestMinEventDelayHandler {
         }
     }
 
-    @Test
+    @Test @Ignore
     public void testFailedDelaying() throws Exception {
         scheduler.failMode();
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         // first activate
         mgr.handleActivated();
         assertNoEvents(listener); // paranoia
@@ -116,7 +121,7 @@ public class TestMinEventDelayHandler {
         assertNoEvents(listener); // there was no changing or changed yet
         mgr.handleChanging();
         assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
         for(int i=0; i<7; i++) {
@@ -124,4 +129,33 @@ public class TestMinEventDelayHandler {
             Thread.sleep(1000);
         }
     }
+    
+    @Test
+    public void testLongMinDelay() throws Exception {
+        mgr.installMinEventDelayHandler(sds, scheduler, 5);
+        final DummyListener listener = new DummyListener();
+        // first activate
+        logger.info("testLongMinDelay: calling handleActivated...");
+        mgr.handleActivated();
+        assertNoEvents(listener); // paranoia
+        // then bind
+        logger.info("testLongMinDelay: calling bind...");
+        mgr.bind(listener);
+        assertNoEvents(listener); // there was no changing or changed yet
+        logger.info("testLongMinDelay: calling handleChanging...");
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final DummyTopologyView view = new DummyTopologyView().addInstance();
+        DummyTopologyView clonedView = view.clone();
+        logger.info("testLongMinDelay: calling handleNewView...");
+        mgr.handleNewView(view);
+        TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
+        final DummyTopologyView view2 = new DummyTopologyView().addInstance();
+        view2.addInstance(UUID.randomUUID().toString(), (DefaultClusterView) view2.getLocalInstance().getClusterView(), false, false);
+        logger.info("testLongMinDelay: calling handleNewView...");
+        clonedView.setNotCurrent();
+        mgr.handleNewView(view2);
+        TestHelper.assertEvents(mgr, listener, EventFactory.newChangingEvent(clonedView));
+        assertFalse(view.isCurrent());
+    }
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
index 1ef9050..9eda40d 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
@@ -30,10 +30,12 @@ import java.util.concurrent.locks.ReentrantLock;
 
 import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
-import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.DefaultClusterView;
+import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.junit.After;
 import org.junit.Before;
@@ -92,20 +94,20 @@ public class TestViewStateManager {
         defaultRandom= null;
     }
     
-    void assertEvents(Listener listener, TopologyEvent... events) {
+    void assertEvents(DummyListener listener, TopologyEvent... events) {
         TestHelper.assertEvents(mgr, listener, events);
     }
     
     /** does couple loops randomly calling handleChanging() (or not) and then handleNewView().
      * Note: random is passed to allow customizing and not hardcoding this method to a particular random 
      * @throws InterruptedException **/
-    private void randomEventLoop(final Random random, Listener... listeners) throws InterruptedException {
+    private void randomEventLoop(final Random random, DummyListener... listeners) throws InterruptedException {
         TestHelper.randomEventLoop(mgr, null, 100, -1, random, listeners);
     }
     
     @Test
     public void testDuplicateListeners() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         mgr.bind(listener); // we should be generous and allow duplicate registration
         assertTrue(mgr.unbind(listener));
@@ -121,14 +123,14 @@ public class TestViewStateManager {
     
     @Test
     public void testBindActivateChangingChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener, EventFactory.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
@@ -136,14 +138,14 @@ public class TestViewStateManager {
     
     @Test
     public void testBindChangingActivateChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener, EventFactory.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
@@ -151,12 +153,12 @@ public class TestViewStateManager {
     
     @Test
     public void testBindChangingChangedActivate() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
@@ -166,19 +168,19 @@ public class TestViewStateManager {
     
     @Test
     public void testBindChangingChangedChangingActivate() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
         assertEvents(listener, EventFactory.newInitEvent(view2));
         randomEventLoop(defaultRandom, listener);
@@ -186,17 +188,17 @@ public class TestViewStateManager {
     
     @Test
     public void testBindChangedChangingActivate() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
         assertEvents(listener, EventFactory.newInitEvent(view2));
         randomEventLoop(defaultRandom, listener);
@@ -204,7 +206,7 @@ public class TestViewStateManager {
     
     @Test
     public void testActivateBindChangingChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         // first activate
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener); // paranoia
@@ -213,7 +215,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener); // there was no changing or changed yet
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener, EventFactory.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
@@ -221,20 +223,20 @@ public class TestViewStateManager {
 
     @Test
     public void testPropertiesChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.handleActivated();
         mgr.bind(listener);
         mgr.handleChanging();
-        SimpleTopologyView oldView = new SimpleTopologyView().addInstance();
-        SimpleInstanceDescription localInstance = 
-                (SimpleInstanceDescription) oldView.getLocalInstance();
+        DummyTopologyView oldView = new DummyTopologyView().addInstance();
+        DefaultInstanceDescription localInstance = 
+                (DefaultInstanceDescription) oldView.getLocalInstance();
         localInstance.setProperty("foo", "bar1");
         mgr.handleNewView(oldView);
         TopologyEvent initEvent = EventFactory.newInitEvent(oldView.clone());
         assertEvents(listener, initEvent);
-        SimpleTopologyView newView = oldView.clone();
+        DummyTopologyView newView = oldView.clone();
         oldView.setNotCurrent();
-        localInstance = (SimpleInstanceDescription) newView.getLocalInstance();
+        localInstance = (DefaultInstanceDescription) newView.getLocalInstance();
         localInstance.setProperty("foo", "bar2");
         mgr.handleNewView(newView);
         Thread.sleep(2000);
@@ -244,7 +246,7 @@ public class TestViewStateManager {
 
     @Test
     public void testActivateChangingBindChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         // first activate
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener); // paranoia
@@ -253,7 +255,7 @@ public class TestViewStateManager {
         // then bind
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener); // no changed event yet
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener, EventFactory.newInitEvent(view));
         randomEventLoop(defaultRandom, listener);
@@ -261,13 +263,13 @@ public class TestViewStateManager {
 
     @Test
     public void testActivateChangingChangedBind() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         // first activate
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener); // paranoia
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener); // no listener yet
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener); // no listener yet
         // then bind
@@ -278,8 +280,8 @@ public class TestViewStateManager {
     
     @Test
     public void testBindActivateBindChangingChanged() throws Exception {
-        final Listener listener1 = new Listener();
-        final Listener listener2 = new Listener();
+        final DummyListener listener1 = new DummyListener();
+        final DummyListener listener2 = new DummyListener();
         
         mgr.bind(listener1);
         TestHelper.assertNoEvents(listener1);
@@ -291,7 +293,7 @@ public class TestViewStateManager {
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener1);
         TestHelper.assertNoEvents(listener2);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener1, EventFactory.newInitEvent(view));
         assertEvents(listener2, EventFactory.newInitEvent(view));
@@ -301,8 +303,8 @@ public class TestViewStateManager {
 
     @Test
     public void testBindActivateChangingBindChanged() throws Exception {
-        final Listener listener1 = new Listener();
-        final Listener listener2 = new Listener();
+        final DummyListener listener1 = new DummyListener();
+        final DummyListener listener2 = new DummyListener();
         
         mgr.bind(listener1);
         TestHelper.assertNoEvents(listener1);
@@ -313,7 +315,7 @@ public class TestViewStateManager {
         mgr.bind(listener2);
         TestHelper.assertNoEvents(listener1);
         TestHelper.assertNoEvents(listener2);
-        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener1, EventFactory.newInitEvent(view));
         assertEvents(listener2, EventFactory.newInitEvent(view));
@@ -323,31 +325,31 @@ public class TestViewStateManager {
     
     @Test
     public void testActivateBindChangingDuplicateHandleNewView() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.handleActivated();
         mgr.bind(listener);
         mgr.handleChanging();
-        final SimpleTopologyView view = new SimpleTopologyView().addInstance();
+        final DummyTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener, EventFactory.newInitEvent(view));
-        mgr.handleNewView(SimpleTopologyView.clone(view));
+        mgr.handleNewView(DummyTopologyView.clone(view));
         TestHelper.assertNoEvents(listener);
         randomEventLoop(defaultRandom, listener);
     }
     
     @Test
     public void testActivateBindChangingChangedBindDuplicateHandleNewView() throws Exception {
-        final Listener listener1 = new Listener();
+        final DummyListener listener1 = new DummyListener();
         mgr.handleActivated();
         mgr.bind(listener1);
         mgr.handleChanging();
-        final SimpleTopologyView view = new SimpleTopologyView().addInstance();
+        final DummyTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         assertEvents(listener1, EventFactory.newInitEvent(view));
         
-        final Listener listener2 = new Listener();
+        final DummyListener listener2 = new DummyListener();
         mgr.bind(listener2);
-        mgr.handleNewView(SimpleTopologyView.clone(view));
+        mgr.handleNewView(DummyTopologyView.clone(view));
         TestHelper.assertNoEvents(listener1);
         assertEvents(listener2, EventFactory.newInitEvent(view));
         randomEventLoop(defaultRandom, listener1, listener2);
@@ -355,32 +357,32 @@ public class TestViewStateManager {
     
     @Test
     public void testActivateChangedBindDuplicateHandleNewView() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final SimpleTopologyView view = new SimpleTopologyView().addInstance();
+        final DummyTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
         TestHelper.assertNoEvents(listener);
         mgr.bind(listener);
         assertEvents(listener, EventFactory.newInitEvent(view));
-        mgr.handleNewView(SimpleTopologyView.clone(view));
+        mgr.handleNewView(DummyTopologyView.clone(view));
         TestHelper.assertNoEvents(listener);
         randomEventLoop(defaultRandom, listener);
     }
     
     @Test
     public void testBindActivateChangedChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
         assertEvents(listener, EventFactory.newInitEvent(view1));
-        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
         assertEvents(listener, EventFactory.newChangingEvent(view1), EventFactory.newChangedEvent(view1, view2));
         randomEventLoop(defaultRandom, listener);
@@ -388,12 +390,12 @@ public class TestViewStateManager {
     
     @Test
     public void testBindActivateChangedDeactivateChangingActivateChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
         assertEvents(listener, EventFactory.newInitEvent(view1));
         mgr.handleDeactivated();
@@ -403,42 +405,42 @@ public class TestViewStateManager {
         mgr.bind(listener); // need to bind again after deactivate
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
         assertEvents(listener, EventFactory.newInitEvent(view2));
     }
 
     @Test
     public void testBindActivateChangedDeactivateChangedActivateChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
         assertEvents(listener, EventFactory.newInitEvent(view1));
         mgr.handleDeactivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
         TestHelper.assertNoEvents(listener);
         mgr.bind(listener); // need to bind again after deactivate
         mgr.handleActivated();
         assertEvents(listener, EventFactory.newInitEvent(view2));
-        final BaseTopologyView view3 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view3 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view3);
         assertEvents(listener, EventFactory.newChangingEvent(view2), EventFactory.newChangedEvent(view2, view3));
     }
 
     @Test
     public void testBindActivateChangedChangingDeactivateActivateChangingChanged() throws Exception {
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view1 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view1);
         assertEvents(listener, EventFactory.newInitEvent(view1));
         mgr.handleChanging();
@@ -450,7 +452,7 @@ public class TestViewStateManager {
         TestHelper.assertNoEvents(listener);
         mgr.handleChanging();
         TestHelper.assertNoEvents(listener);
-        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        final BaseTopologyView view2 = new DummyTopologyView().addInstance();
         mgr.handleNewView(view2);
         assertEvents(listener, EventFactory.newInitEvent(view2));
     }
@@ -464,7 +466,7 @@ public class TestViewStateManager {
         final ReentrantLock lock = new ReentrantLock();
         final ConsistencyServiceWithSemaphore cs = new ConsistencyServiceWithSemaphore(lock, serviceSemaphore );
         mgr = new ViewStateManagerImpl(lock, cs);
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
@@ -472,8 +474,8 @@ public class TestViewStateManager {
         final String slingId1 = UUID.randomUUID().toString();
         final String slingId2 = UUID.randomUUID().toString();
         final String clusterId = UUID.randomUUID().toString();
-        final SimpleClusterView cluster = new SimpleClusterView(clusterId);
-        final SimpleTopologyView view1 = new SimpleTopologyView()
+        final DefaultClusterView cluster = new DefaultClusterView(clusterId);
+        final DummyTopologyView view1 = new DummyTopologyView()
                 .addInstance(slingId1, cluster, true, true)
                 .addInstance(slingId2, cluster, false, false);
         async(new Runnable() {
@@ -488,7 +490,7 @@ public class TestViewStateManager {
         serviceSemaphore.release(1);
         Thread.sleep(1000);
         assertEvents(listener, EventFactory.newInitEvent(view1));
-        final SimpleTopologyView view2 = view1.clone();
+        final DummyTopologyView view2 = view1.clone();
         mgr.handleChanging();
         assertEvents(listener, EventFactory.newChangingEvent(view1));
         view2.removeInstance(slingId2);
@@ -526,7 +528,7 @@ public class TestViewStateManager {
         final ReentrantLock lock = new ReentrantLock();
         final ConsistencyServiceWithSemaphore cs = new ConsistencyServiceWithSemaphore(lock, serviceSemaphore );
         mgr = new ViewStateManagerImpl(lock, cs);
-        final Listener listener = new Listener();
+        final DummyListener listener = new DummyListener();
         mgr.bind(listener);
         TestHelper.assertNoEvents(listener);
         mgr.handleActivated();
@@ -535,13 +537,13 @@ public class TestViewStateManager {
         final String slingId2 = UUID.randomUUID().toString();
         final String slingId3 = UUID.randomUUID().toString();
         final String clusterId = UUID.randomUUID().toString();
-        final SimpleClusterView cluster = new SimpleClusterView(clusterId);
-        final SimpleTopologyView view1 = new SimpleTopologyView()
+        final DefaultClusterView cluster = new DefaultClusterView(clusterId);
+        final DummyTopologyView view1 = new DummyTopologyView()
                 .addInstance(slingId1, cluster, true, true)
                 .addInstance(slingId2, cluster, false, false)
                 .addInstance(slingId3, cluster, false, false);
-        final SimpleTopologyView view2 = SimpleTopologyView.clone(view1).removeInstance(slingId2);
-        final SimpleTopologyView view3 = SimpleTopologyView.clone(view1).removeInstance(slingId2).removeInstance(slingId3);
+        final DummyTopologyView view2 = DummyTopologyView.clone(view1).removeInstance(slingId2);
+        final DummyTopologyView view3 = DummyTopologyView.clone(view1).removeInstance(slingId2).removeInstance(slingId3);
         async(new Runnable() {
 
             public void run() {
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DescriptorHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DescriptorHelper.java
new file mode 100644
index 0000000..b1b2f8e
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DescriptorHelper.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Method;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.commons.SimpleValueFactory;
+import org.apache.jackrabbit.oak.util.GenericDescriptors;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+
+public class DescriptorHelper {
+
+    public static void setDiscoveryLiteDescriptor(ResourceResolverFactory factory, DiscoveryLiteDescriptorBuilder builder) throws Exception {
+        setDescriptor(factory, DiscoveryLiteDescriptor.OAK_DISCOVERYLITE_CLUSTERVIEW, builder.asJson());
+    }
+    
+    public static void setDescriptor(ResourceResolverFactory factory, String key,
+            String value) throws Exception {
+        ResourceResolver resourceResolver = factory.getAdministrativeResourceResolver(null);
+        try{
+            Session session = resourceResolver.adaptTo(Session.class);
+            if (session == null) {
+                return;
+            }
+            Repository repo = session.getRepository();
+            
+            //<hack>
+//            Method setDescriptorMethod = repo.getClass().
+//                    getDeclaredMethod("setDescriptor", String.class, String.class);
+//            if (setDescriptorMethod!=null) {
+//                setDescriptorMethod.setAccessible(true);
+//                setDescriptorMethod.invoke(repo, key, value);
+//            } else {
+//                fail("could not get 'setDescriptor' method");
+//            }
+            Method getDescriptorsMethod = repo.getClass().getDeclaredMethod("getDescriptors");
+            if (getDescriptorsMethod==null) {
+                fail("could not get 'getDescriptors' method");
+            } else {
+                getDescriptorsMethod.setAccessible(true);
+                GenericDescriptors descriptors = (GenericDescriptors) getDescriptorsMethod.invoke(repo);
+                SimpleValueFactory valueFactory = new SimpleValueFactory();
+                descriptors.put(key, valueFactory.createValue(value), true, true);
+            }
+            //</hack>
+            
+            //<verify-hack>
+            assertEquals(value, repo.getDescriptor(key));
+            //</verify-hack>
+        } finally {
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoLite.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptorBuilder.java
similarity index 68%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoLite.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptorBuilder.java
index fcf361f..5ef9eb1 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoLite.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoveryLiteDescriptorBuilder.java
@@ -25,45 +25,68 @@ import org.apache.sling.commons.json.JSONException;
 import org.apache.sling.commons.json.JSONObject;
 
 // {"seq":8,"final":true,"id":"aae34e9a-b08d-409e-be10-9ff4106e5387","me":4,"active":[4],"deactivating":[],"inactive":[1,2,3]}
-public class DiscoLite {
+public class DiscoveryLiteDescriptorBuilder {
     
     private int seqNum;
     private int me;
     private Integer[] activeIds = new Integer[0];
     private Integer[] inactiveIds = new Integer[0];
     private Integer[] deactivating = new Integer[0];
+    private String id;
+    private boolean isFinal = false;
 
-    public DiscoLite() {
+    public DiscoveryLiteDescriptorBuilder() {
         // nothing here
     }
     
-    public DiscoLite seq(int seqNum) {
+    @Override
+    public String toString() {
+        try {
+            return asJson();
+        } catch (JSONException e) {
+            return "A DiscoLite["+e+"]";
+        }
+    }
+    
+    public DiscoveryLiteDescriptorBuilder setFinal(boolean isFinal) {
+        this.isFinal = isFinal;
+        return this;
+    }
+
+    public DiscoveryLiteDescriptorBuilder seq(int seqNum) {
         this.seqNum = seqNum;
         return this;
     }
 
-    public DiscoLite me(int me) {
+    public DiscoveryLiteDescriptorBuilder me(int me) {
         this.me = me;
         return this;
     }
 
-    public DiscoLite activeIds(Integer... activeIds) {
+    public DiscoveryLiteDescriptorBuilder id(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public DiscoveryLiteDescriptorBuilder activeIds(Integer... activeIds) {
         this.activeIds = activeIds;
         return this;
     }
 
-    public DiscoLite inactiveIds(Integer... inactiveIds) {
+    public DiscoveryLiteDescriptorBuilder inactiveIds(Integer... inactiveIds) {
         this.inactiveIds = inactiveIds;
         return this;
     }
 
-    public DiscoLite deactivatingIds(Integer... deactivating) {
+    public DiscoveryLiteDescriptorBuilder deactivatingIds(Integer... deactivating) {
         this.deactivating = deactivating;
         return this;
     }
     
     public String asJson() throws JSONException {
         JSONObject json = new JSONObject();
+        json.put("id", id);
+        json.put("final", isFinal);
         json.put("me", me);
         json.put("seq", seqNum);
         json.put("active", new JSONArray(Arrays.asList(activeIds)));
@@ -71,4 +94,5 @@ public class DiscoLite {
         json.put("deactivating", new JSONArray(Arrays.asList(deactivating)));
         return json.toString();
     }
+
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DummySlingSettingsService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DummySlingSettingsService.java
new file mode 100644
index 0000000..46fad54
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DummySlingSettingsService.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.net.URL;
+import java.util.Set;
+
+import org.apache.sling.settings.SlingSettingsService;
+
+public class DummySlingSettingsService implements SlingSettingsService {
+
+    private String slingId;
+    private String slingHome;
+
+    public DummySlingSettingsService(String slingId) {
+        this(slingId, "/slingHome/"+slingId);
+    }
+    
+    public DummySlingSettingsService(String slingId, String slingHome) {
+        this.slingId = slingId;
+        this.slingHome = slingHome;
+    }
+    
+    @Override
+    public String getAbsolutePathWithinSlingHome(String relativePath) {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public String getSlingId() {
+        return slingId;
+    }
+
+    @Override
+    public String getSlingHomePath() {
+        return slingHome;
+    }
+
+    @Override
+    public URL getSlingHome() {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public Set<String> getRunModes() {
+        throw new IllegalStateException("not yet impl");
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockFactory.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockFactory.java
deleted file mode 100644
index 895ebbe..0000000
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockFactory.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.discovery.commons.providers.spi.impl;
-
-import javax.jcr.Session;
-
-import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.commons.testing.jcr.RepositoryProvider;
-import org.apache.sling.jcr.api.SlingRepository;
-import org.hamcrest.Description;
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.api.Action;
-import org.jmock.api.Invocation;
-import org.jmock.integration.junit4.JUnit4Mockery;
-
-public class MockFactory {
-
-    public final Mockery context = new JUnit4Mockery();
-
-    public static ResourceResolverFactory mockResourceResolverFactory()
-            throws Exception {
-    	return mockResourceResolverFactory(null);
-    }
-
-    public static ResourceResolverFactory mockResourceResolverFactory(final SlingRepository repositoryOrNull)
-            throws Exception {
-        Mockery context = new JUnit4Mockery();
-
-        final ResourceResolverFactory resourceResolverFactory = context
-                .mock(ResourceResolverFactory.class);
-        // final ResourceResolver resourceResolver = new MockResourceResolver();
-        // final ResourceResolver resourceResolver = new
-        // MockedResourceResolver();
-
-        context.checking(new Expectations() {
-            {
-                allowing(resourceResolverFactory)
-                        .getAdministrativeResourceResolver(null);
-                will(new Action() {
-
-                    public Object invoke(Invocation invocation)
-                            throws Throwable {
-                    	return new MockedResourceResolver(repositoryOrNull);
-                    }
-
-                    public void describeTo(Description arg0) {
-                        arg0.appendText("whateva - im going to create a new mockedresourceresolver");
-                    }
-                });
-            }
-        });
-        return resourceResolverFactory;
-    }
-
-    public static void resetRepo() throws Exception {
-        Session l = RepositoryProvider.instance().getRepository()
-                .loginAdministrative(null);
-        try {
-            l.removeItem("/var");
-            l.save();
-            l.logout();
-        } catch (Exception e) {
-            l.refresh(false);
-            l.logout();
-        }
-    }
-    
-}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryTestHelper.java
similarity index 71%
rename from src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryHelper.java
rename to src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryTestHelper.java
index 374f0f6..ede5412 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryTestHelper.java
@@ -18,10 +18,7 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.impl;
 
-import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -35,7 +32,6 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.naming.NamingException;
 
-import org.apache.jackrabbit.commons.JcrUtils;
 import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.api.ContentRepository;
 import org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl;
@@ -51,33 +47,22 @@ import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeStore;
 import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
 import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.testing.jcr.RepositoryProvider;
 import org.apache.sling.commons.testing.jcr.RepositoryUtil;
 import org.apache.sling.commons.testing.jcr.RepositoryUtil.RepositoryWrapper;
 import org.apache.sling.jcr.api.SlingRepository;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JUnit4Mockery;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public class RepositoryHelper {
+public class RepositoryTestHelper {
 
-    private static class ShutdownThread extends Thread {
-        
-        private SlingRepository repository;
-        
-        public ShutdownThread(SlingRepository repository) {
-            this.repository = repository;
-        }
-        @Override
-        public void run() {
-            try {
-                stopRepository(repository);
-            } catch(Exception e) {
-                System.out.println("Exception in ShutdownThread:" + e);
-            }
-        }
-        
-    }
-
-    private final static Logger logger = LoggerFactory.getLogger(RepositoryHelper.class);
+    private final static Logger logger = LoggerFactory.getLogger(RepositoryTestHelper.class);
     
     public static void dumpRepo(ResourceResolverFactory resourceResolverFactory) throws Exception {
         Session session = resourceResolverFactory
@@ -135,15 +120,8 @@ public class RepositoryHelper {
     /** from commons.testing.jcr **/
     public static final String CONFIG_FILE = "jackrabbit-test-config.xml";
 
-    public static SlingRepository newRepository(String homeDir) throws RepositoryException {
-        SlingRepository repository = startRepository(homeDir);
-        Runtime.getRuntime().addShutdownHook(new ShutdownThread(repository));
-        return repository;
-    }
-
     public static SlingRepository newOakRepository(NodeStore nodeStore) throws RepositoryException {
             SlingRepository repository = new RepositoryWrapper(createOakRepository(nodeStore));
-    //        Runtime.getRuntime().addShutdownHook(new ShutdownThread(repository));
             return repository;
         }
 
@@ -162,62 +140,6 @@ public class RepositoryHelper {
     }
 
     /**
-     * Start a new repository
-     * @return 
-     *
-     * @throws RepositoryException when it is not possible to start the
-     *             repository.
-     */
-    private static RepositoryWrapper startRepository(String homeDir) throws RepositoryException {
-        // copy the repository configuration file to the repository HOME_DIR
-        InputStream ins = RepositoryUtil.class.getClassLoader().getResourceAsStream(
-            CONFIG_FILE);
-        if (ins == null) {
-            throw new RepositoryException("Cannot get " + CONFIG_FILE);
-        }
-    
-        File configFile = new File(homeDir, "repository.xml");
-        configFile.getParentFile().mkdirs();
-    
-        FileOutputStream out = null;
-        try {
-            out = new FileOutputStream(configFile);
-            byte[] buf = new byte[1024];
-            int rd;
-            while ((rd = ins.read(buf)) >= 0) {
-                out.write(buf, 0, rd);
-            }
-        } catch (IOException ioe) {
-            throw new RepositoryException("Cannot copy configuration file to "
-                + configFile);
-        } finally {
-            try {
-                ins.close();
-            } catch (IOException ignore) {
-            }
-            if (out != null) {
-                try {
-                    out.close();
-                } catch (IOException ignore) {
-                }
-            }
-        }
-    
-        // somewhat dirty hack to have the derby.log file in a sensible
-        // location, but don't overwrite anything already set
-        if (System.getProperty("derby.stream.error.file") == null) {
-            String derbyLog = homeDir + "/derby.log";
-            System.setProperty("derby.stream.error.file", derbyLog);
-        }
-    
-        final File f = new File(homeDir);
-        RepositoryWrapper repository = new RepositoryWrapper(JcrUtils.getRepository(f.toURI().toString()));
-        Session adminSession = repository.loginAdministrative(null);
-        adminSessions.put(repository, adminSession);
-        return repository;
-    }
-
-    /**
      * Stop a repository.
      */
     public static void stopRepository(SlingRepository repository) throws NamingException {
@@ -277,5 +199,48 @@ public class RepositoryHelper {
         return new RepositoryImpl(contentRepository, whiteboard, new OpenSecurityProvider(), 1000, null);
     }
 
+    public static void resetRepo() throws Exception {
+        Session l = RepositoryProvider.instance().getRepository()
+                .loginAdministrative(null);
+        try {
+            l.removeItem("/var");
+            l.save();
+            l.logout();
+        } catch (Exception e) {
+            l.refresh(false);
+            l.logout();
+        }
+    }
+
+    public static ResourceResolverFactory mockResourceResolverFactory(final SlingRepository repositoryOrNull)
+            throws Exception {
+        Mockery context = new JUnit4Mockery();
+    
+        final ResourceResolverFactory resourceResolverFactory = context
+                .mock(ResourceResolverFactory.class);
+        // final ResourceResolver resourceResolver = new MockResourceResolver();
+        // final ResourceResolver resourceResolver = new
+        // MockedResourceResolver();
+    
+        context.checking(new Expectations() {
+            {
+                allowing(resourceResolverFactory)
+                        .getAdministrativeResourceResolver(null);
+                will(new Action() {
+    
+                    public Object invoke(Invocation invocation)
+                            throws Throwable {
+                    	return new MockedResourceResolver(repositoryOrNull);
+                    }
+    
+                    public void describeTo(Description arg0) {
+                        arg0.appendText("whateva - im going to create a new mockedresourceresolver");
+                    }
+                });
+            }
+        });
+        return resourceResolverFactory;
+    }
+
 
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
index d011d91..4260cdb 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
@@ -19,25 +19,17 @@
 package org.apache.sling.discovery.commons.providers.spi.impl;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
 
-import java.lang.reflect.Method;
 import java.util.UUID;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
-import javax.jcr.Repository;
-import javax.jcr.Session;
-
-import org.apache.jackrabbit.commons.SimpleValueFactory;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
-import org.apache.jackrabbit.oak.util.GenericDescriptors;
-import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.api.resource.ResourceResolverFactory;
-import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.commons.providers.DummyTopologyView;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
-import org.apache.sling.discovery.commons.providers.impl.Listener;
-import org.apache.sling.discovery.commons.providers.impl.SimpleTopologyView;
+import org.apache.sling.discovery.commons.providers.impl.DummyListener;
 import org.apache.sling.discovery.commons.providers.impl.TestHelper;
 import org.apache.sling.discovery.commons.providers.impl.ViewStateManagerFactory;
 import org.apache.sling.jcr.api.SlingRepository;
@@ -47,46 +39,86 @@ import org.junit.Test;
 
 public class TestOakSyncTokenConsistencyService {
 
+    private static final String SYNCTOKEN_PATH = "/var/discovery/commons/synctokens";
+
+    private static final String IDMAP_PATH = "/var/discovery/commons/idmap";
+
+    public final class SimpleCommonsConfig implements DiscoveryLiteConfig {
+        
+        private long bgIntervalMillis;
+        private long bgTimeoutMillis;
+
+        SimpleCommonsConfig() {
+            this(1000, -1); // defaults
+        }
+
+        SimpleCommonsConfig(long bgIntervalMillis, long bgTimeoutMillis) {
+            this.bgIntervalMillis = bgIntervalMillis;
+            this.bgTimeoutMillis = bgTimeoutMillis;
+        }
+        
+        @Override
+        public String getSyncTokenPath() {
+            return SYNCTOKEN_PATH;
+        }
+
+        @Override
+        public String getIdMapPath() {
+            return IDMAP_PATH;
+        }
+
+        @Override
+        public long getBgTimeoutMillis() {
+            return bgTimeoutMillis;
+        }
+
+        @Override
+        public long getBgIntervalMillis() {
+            return bgIntervalMillis;
+        }
+
+    }
+
     ResourceResolverFactory factory1;
     ResourceResolverFactory factory2;
     private SlingRepository repository1;
     private SlingRepository repository2;
     private MemoryNodeStore memoryNS;
+    private IdMapService idMapService1;
+    private String slingId1;
     
     @Before
     public void setup() throws Exception {
-        MockFactory.resetRepo();
+        RepositoryTestHelper.resetRepo();
         memoryNS = new MemoryNodeStore();
-        repository1 = RepositoryHelper.newOakRepository(memoryNS);
-//        repository1 = MultipleRepositoriesSupport.newRepository("target/repo1");
-        RepositoryHelper.initSlingNodeTypes(repository1);
-        repository2 = RepositoryHelper.newOakRepository(memoryNS);
-//        repository2 = MultipleRepositoriesSupport.newRepository("target/repo2");
-//        MultipleRepositoriesSupport.initSlingNodeTypes(repository2);
-        factory1 = MockFactory.mockResourceResolverFactory(repository1);
-        factory2 = MockFactory.mockResourceResolverFactory(repository2);
+        repository1 = RepositoryTestHelper.newOakRepository(memoryNS);
+        RepositoryTestHelper.initSlingNodeTypes(repository1);
+        repository2 = RepositoryTestHelper.newOakRepository(memoryNS);
+        factory1 = RepositoryTestHelper.mockResourceResolverFactory(repository1);
+        factory2 = RepositoryTestHelper.mockResourceResolverFactory(repository2);
+        slingId1 = UUID.randomUUID().toString();
+        idMapService1 = IdMapService.testConstructor(new SimpleCommonsConfig(), new DummySlingSettingsService(slingId1), factory1);
     }
     
     @After
     public void tearDown() throws Exception {
         if (repository1!=null) {
-            RepositoryHelper.stopRepository(repository1);
+            RepositoryTestHelper.stopRepository(repository1);
             repository1 = null;
         }
         if (repository2!=null) {
-            RepositoryHelper.stopRepository(repository2);
+            RepositoryTestHelper.stopRepository(repository2);
             repository2 = null;
         }
     }
     
     @Test
     public void testOneNode() throws Exception {
-        String slingId1 = UUID.randomUUID().toString();
-        SimpleTopologyView one = TestHelper.newView(true, slingId1, slingId1, slingId1);
+        DummyTopologyView one = TestHelper.newView(true, slingId1, slingId1, slingId1);
         Lock lock = new ReentrantLock();
-        OakSyncTokenConsistencyService cs = new OakSyncTokenConsistencyService(factory1, slingId1, -1, -1);
+        OakSyncTokenConsistencyService cs = OakSyncTokenConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
         ViewStateManager vsm = ViewStateManagerFactory.newViewStateManager(lock, cs);
-        Listener l = new Listener();
+        DummyListener l = new DummyListener();
         assertEquals(0, l.countEvents());
         vsm.bind(l);
         cs.triggerBackgroundCheck();
@@ -98,106 +130,67 @@ public class TestOakSyncTokenConsistencyService {
         cs.triggerBackgroundCheck();
         assertEquals(0, l.countEvents());
         cs.triggerBackgroundCheck();
-        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(1).activeIds(1));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().me(1).seq(1).activeIds(1).setFinal(true));
+        assertTrue(idMapService1.waitForInit(2000));
         cs.triggerBackgroundCheck();
+        assertTrue(vsm.waitForAsyncEvents(1000));
         assertEquals(1, l.countEvents());
     }
     
     @Test
     public void testTwoNodesOneLeaving() throws Exception {
-        String slingId1 = UUID.randomUUID().toString();
         String slingId2 = UUID.randomUUID().toString();
-        SimpleTopologyView two1 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
+        DummyTopologyView two1 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
         Lock lock1 = new ReentrantLock();
-        OakSyncTokenConsistencyService cs1 = new OakSyncTokenConsistencyService(factory1, slingId1, -1, -1);
+        OakSyncTokenConsistencyService cs1 = OakSyncTokenConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
         ViewStateManager vsm1 = ViewStateManagerFactory.newViewStateManager(lock1, cs1);
-        Listener l = new Listener();
+        DummyListener l = new DummyListener();
         vsm1.bind(l);
         vsm1.handleActivated();
         vsm1.handleNewView(two1);
         cs1.triggerBackgroundCheck();
         assertEquals(0, l.countEvents());
-        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(1).activeIds(1).deactivatingIds(2));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(1).activeIds(1).deactivatingIds(2));
         cs1.triggerBackgroundCheck();
         assertEquals(0, l.countEvents());
-        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(2).activeIds(1));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(2).activeIds(1));
         cs1.triggerBackgroundCheck();
         Lock lock2 = new ReentrantLock();
-        OakSyncTokenConsistencyService cs2 = new OakSyncTokenConsistencyService(factory2, slingId2, -1, -1);
+        IdMapService idMapService2 = IdMapService.testConstructor(
+                new SimpleCommonsConfig(), new DummySlingSettingsService(slingId2), factory2);
+        OakSyncTokenConsistencyService cs2 = OakSyncTokenConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService2, new DummySlingSettingsService(slingId2), factory2);
         ViewStateManager vsm2 = ViewStateManagerFactory.newViewStateManager(lock2, cs2);
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
         assertEquals(0, l.countEvents());
-        setDiscoveryLiteDescriptor(factory2, new DiscoLite().me(2).seq(3).activeIds(1, 2));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory2, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(2).seq(3).activeIds(1, 2));
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
         assertEquals(0, l.countEvents());
-        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(3).activeIds(1, 2));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(3).activeIds(1, 2));
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
         assertEquals(0, l.countEvents());
         vsm2.handleActivated();
-        SimpleTopologyView two2 = TestHelper.newView(two1.getLocalClusterSyncTokenId(), two1.getLocalInstance().getClusterView().getId(), true, slingId1, slingId1, slingId1, slingId2);
+        assertTrue(idMapService1.waitForInit(2000));
+        assertTrue(idMapService2.waitForInit(2000));
+        DummyTopologyView two2 = TestHelper.newView(two1.getLocalClusterSyncTokenId(), two1.getLocalInstance().getClusterView().getId(), true, slingId1, slingId1, slingId1, slingId2);
         vsm2.handleNewView(two2);
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
         assertEquals(1, l.countEvents());
-        SimpleTopologyView oneLeaving = two1.clone();
+        DummyTopologyView oneLeaving = two1.clone();
         oneLeaving.removeInstance(slingId2);
-        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(1).activeIds(1).deactivatingIds(2));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(1).activeIds(1).deactivatingIds(2));
         vsm1.handleNewView(oneLeaving);
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
         assertEquals(2, l.countEvents());
-        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(2).activeIds(1).inactiveIds(2));
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(2).activeIds(1).inactiveIds(2));
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
-        RepositoryHelper.dumpRepo(factory1);
+        RepositoryTestHelper.dumpRepo(factory1);
         assertEquals(3, l.countEvents());
     }
     
-    private void setDiscoveryLiteDescriptor(ResourceResolverFactory factory, DiscoLite builder) throws JSONException, Exception {
-        setDescriptor(factory, OakSyncTokenConsistencyService.OAK_DISCOVERYLITE_CLUSTERVIEW, builder.asJson());
-    }
-    
-    private void setDescriptor(ResourceResolverFactory factory, String key,
-            String value) throws Exception {
-        ResourceResolver resourceResolver = factory.getAdministrativeResourceResolver(null);
-        try{
-            Session session = resourceResolver.adaptTo(Session.class);
-            if (session == null) {
-                return;
-            }
-            Repository repo = session.getRepository();
-            
-            //<hack>
-//            Method setDescriptorMethod = repo.getClass().
-//                    getDeclaredMethod("setDescriptor", String.class, String.class);
-//            if (setDescriptorMethod!=null) {
-//                setDescriptorMethod.setAccessible(true);
-//                setDescriptorMethod.invoke(repo, key, value);
-//            } else {
-//                fail("could not get 'setDescriptor' method");
-//            }
-            Method getDescriptorsMethod = repo.getClass().getDeclaredMethod("getDescriptors");
-            if (getDescriptorsMethod==null) {
-                fail("could not get 'getDescriptors' method");
-            } else {
-                getDescriptorsMethod.setAccessible(true);
-                GenericDescriptors descriptors = (GenericDescriptors) getDescriptorsMethod.invoke(repo);
-                SimpleValueFactory valueFactory = new SimpleValueFactory();
-                descriptors.put(key, valueFactory.createValue(value), true, true);
-            }
-            //</hack>
-            
-            //<verify-hack>
-            assertEquals(value, repo.getDescriptor(key));
-            //</verify-hack>
-        } finally {
-            if (resourceResolver!=null) {
-                resourceResolver.close();
-            }
-        }
-    }
-
 }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 35/38: SLING-5094 related : more test stability by adding a wait time of 2sec

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 032f5007a19eaa39549821174d8ee8c8bc6dc956
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 09:34:49 2015 +0000

    SLING-5094 related : more test stability by adding a wait time of 2sec
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710537 13f79535-47bb-0310-9956-ffa450edef68
---
 .../discovery/commons/providers/spi/base/TestOakSyncTokenService.java   | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
index 591fea8..b90c622 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenService.java
@@ -191,10 +191,12 @@ public class TestOakSyncTokenService {
         vsm1.handleNewView(oneLeaving);
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
+        assertEquals(0, vsm1.waitForAsyncEvents(2000));
         assertEquals(2, l.countEvents());
         DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().setFinal(true).me(1).seq(2).activeIds(1).inactiveIds(2));
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
+        assertEquals(0, vsm1.waitForAsyncEvents(2000));
         RepositoryTestHelper.dumpRepo(factory1);
         assertEquals(3, l.countEvents());
     }

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 15/38: SLING-4603 related : some fixes in class structure / syncToken handling

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit dd9150cdff8555ff1cb20ac63e9d18a4005c3dd7
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 11:02:22 2015 +0000

    SLING-4603 related : some fixes in class structure / syncToken handling
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709793 13f79535-47bb-0310-9956-ffa450edef68
---
 .../spi/base/BaseSyncTokenConsistencyService.java  | 87 ++++++++++++----------
 .../spi/base/OakSyncTokenConsistencyService.java   | 23 +++---
 .../spi/base/SyncTokenOnlyConsistencyService.java  | 19 +++--
 3 files changed, 68 insertions(+), 61 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
index 5746d8b..f37b7a2 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/BaseSyncTokenConsistencyService.java
@@ -18,7 +18,6 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
-import org.apache.felix.scr.annotations.Activate;
 import org.apache.sling.api.resource.LoginException;
 import org.apache.sling.api.resource.ModifiableValueMap;
 import org.apache.sling.api.resource.PersistenceException;
@@ -31,8 +30,6 @@ import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.settings.SlingSettingsService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
  * Implements the 'sync-token' part of the ConsistencyService,
@@ -41,25 +38,14 @@ import org.slf4j.LoggerFactory;
  */
 public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWithBackgroundCheck implements ConsistencyService {
 
-    protected final Logger logger = LoggerFactory.getLogger(getClass());
-
     protected String slingId;
 
-    protected long syncTokenTimeoutMillis;
-    
-    protected long syncTokenIntervalMillis;
-
     protected abstract DiscoveryLiteConfig getCommonsConfig();
 
     protected abstract ResourceResolverFactory getResourceResolverFactory();
 
     protected abstract SlingSettingsService getSettingsService();
     
-    @Activate
-    protected void activate() {
-        this.slingId = getSettingsService().getSlingId();
-    }
-    
     /** Get or create a ResourceResolver **/
     protected ResourceResolver getResourceResolver() throws LoginException {
         return getResourceResolverFactory().getAdministrativeResourceResolver(null);
@@ -76,42 +62,61 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
     }
 
     protected void syncToken(final BaseTopologyView view, final Runnable callback) {
-        // 1) first storing my syncToken
-        try {
-            storeMySyncToken(view.getLocalClusterSyncTokenId());
-        } catch (LoginException e) {
-            logger.error("syncToken: will run into timeout: could not login for storing my syncToken: "+e, e);
-        } catch (PersistenceException e) {
-            logger.error("syncToken: will run into timeout: got PersistenceException while storing my syncToken: "+e, e);
-        }
-        // if anything goes wrong above, then this will mean for the others
-        // that they will have to wait until the timeout hits
-        // which means we should do the same..
-        // hence no further action possible on error above
         
-        // 2) then check if all others have done the same already
         startBackgroundCheck("SyncTokenConsistencyService", new BackgroundCheck() {
             
             @Override
             public boolean check() {
+                // 1) first storing my syncToken
+                if (!storeMySyncToken(view.getLocalClusterSyncTokenId())) {
+                    // if anything goes wrong above, then this will mean for the others
+                    // that they will have to wait until the timeout hits
+                    
+                    // so to try to avoid this, retry storing my sync token later:
+                    return false;
+                }
+                
+                
+                // 2) then check if all others have done the same already
                 return seenAllSyncTokens(view);
             }
-        }, callback, syncTokenTimeoutMillis, syncTokenIntervalMillis);
+        }, callback, getCommonsConfig().getBgTimeoutMillis(), getCommonsConfig().getBgIntervalMillis());
     }
 
-    private void storeMySyncToken(String syncTokenId) throws LoginException, PersistenceException {
+    private boolean storeMySyncToken(String syncTokenId) {
         logger.trace("storeMySyncToken: start");
+        if (slingId == null) {
+            logger.info("storeMySyncToken: not yet activated (slingId is null)");
+            return false;
+        }
         ResourceResolver resourceResolver = null;
         try{
             resourceResolver = getResourceResolver();
             final Resource resource = ResourceHelper.getOrCreateResource(resourceResolver, getSyncTokenPath());
             ModifiableValueMap syncTokens = resource.adaptTo(ModifiableValueMap.class);
-            Object currentValue = syncTokens.get(slingId);
-            if (currentValue == null || !syncTokenId.equals(currentValue)) {
+            boolean updateToken = false;
+            if (!syncTokens.containsKey(slingId)) {
+                updateToken = true;
+            } else {
+                Object existingToken = syncTokens.get(slingId);
+                if (existingToken==null || !existingToken.equals(syncTokenId)) {
+                    updateToken = true;
+                }
+            }
+            if (updateToken) {
                 syncTokens.put(slingId, syncTokenId);
+                resourceResolver.commit();
+                logger.info("storeMySyncToken: stored syncToken of slingId="+slingId+" as="+syncTokenId);
+            } else {
+                logger.info("storeMySyncToken: syncToken was left unchanged for slingId="+slingId+" at="+syncTokenId);
             }
-            resourceResolver.commit();
-            logger.info("syncToken: stored syncToken of slingId="+slingId+" as="+syncTokenId);
+            return true;
+        } catch (LoginException e) {
+            logger.error("storeMySyncToken: could not login for storing my syncToken: "+e, e);
+            return false;
+        } catch (PersistenceException e) {
+            logger.error("storeMySyncToken: got PersistenceException while storing my syncToken: "+e, e);
+            return false;
         } finally {
             logger.trace("storeMySyncToken: end");
             if (resourceResolver!=null) {
@@ -133,18 +138,22 @@ public abstract class BaseSyncTokenConsistencyService extends AbstractServiceWit
             ValueMap syncTokens = resource.adaptTo(ValueMap.class);
             String syncToken = view.getLocalClusterSyncTokenId();
             
+            boolean success = true;
             for (InstanceDescription instance : view.getLocalInstance().getClusterView().getInstances()) {
                 Object currentValue = syncTokens.get(instance.getSlingId());
                 if (currentValue == null) {
-                    logger.info("seenAllSyncTokens: no syncToken of "+instance);
-                    return false;
-                }
-                if (!syncToken.equals(currentValue)) {
-                    logger.info("seenAllSyncTokens: old syncToken of " + instance
+                    logger.info("seenAllSyncTokens: no syncToken of "+instance.getSlingId());
+                    success = false;
+                } else if (!syncToken.equals(currentValue)) {
+                    logger.info("seenAllSyncTokens: old syncToken of " + instance.getSlingId()
                             + " : expected=" + syncToken + " got="+currentValue);
-                    return false;
+                    success = false;
                 }
             }
+            if (!success) {
+                logger.info("seenAllSyncTokens: not yet seen all expected syncTokens (see above for details)");
+                return false;
+            }
             
             resourceResolver.commit();
             logger.info("seenAllSyncTokens: seen all syncTokens!");
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
index 3a3a40b..24c12dc 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/OakSyncTokenConsistencyService.java
@@ -21,6 +21,7 @@ package org.apache.sling.discovery.commons.providers.spi.base;
 import java.util.HashSet;
 import java.util.Set;
 
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
@@ -40,7 +41,7 @@ import org.apache.sling.settings.SlingSettingsService;
  * the Oak discovery-lite descriptor.
  */
 @Component(immediate = false)
-@Service(value = { ConsistencyService.class })
+@Service(value = { ConsistencyService.class, OakSyncTokenConsistencyService.class })
 public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyService {
 
     static enum BacklogStatus {
@@ -49,10 +50,6 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
         NO_BACKLOG /* when oak's discovery lite descriptor declared we're backlog-free now */
     }
     
-    private long backlogWaitTimeoutMillis;
-
-    private long backlogWaitIntervalMillis;
-
     @Reference
     private IdMapService idMapService;
     
@@ -104,28 +101,30 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
         }
         service.commonsConfig = commonsConfig;
         service.resourceResolverFactory = resourceResolverFactory;
-        service.syncTokenTimeoutMillis = commonsConfig.getBgTimeoutMillis();
-        service.syncTokenIntervalMillis = commonsConfig.getBgIntervalMillis();
         service.idMapService = idMapService;
         service.settingsService = settingsService;
-        service.backlogWaitIntervalMillis = commonsConfig.getBgIntervalMillis();
-        service.backlogWaitTimeoutMillis = commonsConfig.getBgTimeoutMillis();
         return service;
     }
     
+    @Activate
+    protected void activate() {
+        this.slingId = getSettingsService().getSlingId();
+        logger.info("activate: activated with slingId="+slingId);
+    }
+    
     @Override
     public void sync(final BaseTopologyView view, final Runnable callback) {
         // cancel the previous backgroundCheck if it's still running
         cancelPreviousBackgroundCheck();
 
         // first do the wait-for-backlog part
-        logger.info("sync: doing wait-for-backlog part for view="+view);
+        logger.info("sync: doing wait-for-backlog part for view="+view.toShortString());
         waitWhileBacklog(view, new Runnable() {
 
             @Override
             public void run() {
                 // when done, then do the sync-token part
-                logger.info("sync: doing sync-token part for view="+view);
+                logger.info("sync: doing sync-token part for view="+view.toShortString());
                 syncToken(view, callback);
             }
             
@@ -157,7 +156,7 @@ public class OakSyncTokenConsistencyService extends BaseSyncTokenConsistencyServ
                     return false;
                 }
             }
-        }, runnable, backlogWaitTimeoutMillis, backlogWaitIntervalMillis);
+        }, runnable, getCommonsConfig().getBgTimeoutMillis(), getCommonsConfig().getBgIntervalMillis());
     }
     
     private BacklogStatus getBacklogStatus(BaseTopologyView view) {
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
index 8811458..32438ea 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/SyncTokenOnlyConsistencyService.java
@@ -18,6 +18,7 @@
  */
 package org.apache.sling.discovery.commons.providers.spi.base;
 
+import org.apache.felix.scr.annotations.Activate;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
@@ -47,22 +48,16 @@ public class SyncTokenOnlyConsistencyService extends BaseSyncTokenConsistencySer
     @Reference
     protected SlingSettingsService settingsService;
 
-    protected String slingId;
-
-    protected long syncTokenTimeoutMillis;
-    
-    protected long syncTokenIntervalMillis;
-
     public static BaseSyncTokenConsistencyService testConstructorAndActivate(
             DiscoveryLiteConfig commonsConfig,
             ResourceResolverFactory resourceResolverFactory,
             SlingSettingsService settingsService) {
-        BaseSyncTokenConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
+        SyncTokenOnlyConsistencyService service = testConstructor(commonsConfig, resourceResolverFactory, settingsService);
         service.activate();
         return service;
     }
     
-    public static BaseSyncTokenConsistencyService testConstructor(
+    public static SyncTokenOnlyConsistencyService testConstructor(
             DiscoveryLiteConfig commonsConfig,
             ResourceResolverFactory resourceResolverFactory,
             SlingSettingsService settingsService) {
@@ -78,12 +73,16 @@ public class SyncTokenOnlyConsistencyService extends BaseSyncTokenConsistencySer
         }
         service.commonsConfig = commonsConfig;
         service.resourceResolverFactory = resourceResolverFactory;
-        service.syncTokenTimeoutMillis = commonsConfig.getBgTimeoutMillis();
-        service.syncTokenIntervalMillis = commonsConfig.getBgIntervalMillis();
         service.settingsService = settingsService;
         return service;
     }
 
+    @Activate
+    protected void activate() {
+        this.slingId = getSettingsService().getSlingId();
+        logger.info("activate: activated with slingId="+slingId);
+    }
+    
     @Override
     protected DiscoveryLiteConfig getCommonsConfig() {
         return commonsConfig;

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 10/38: SLING-5131 : re-adding ViewStateManager which for some reason got removed by svn in 1707548

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit b33eee08d18f82d96db55b45af940f7744b5af38
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 8 14:38:11 2015 +0000

    SLING-5131 : re-adding ViewStateManager which for some reason got removed by svn in 1707548
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1707551 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/ViewStateManager.java        | 91 ++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
new file mode 100644
index 0000000..5ab8fc4
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.TopologyEventListener;
+
+/**
+ * The ViewStateManager is at the core of managing TopologyEventListeners,
+ * the 'view state' (changing vs changed) and sending out the appropriate
+ * and according TopologyEvents to the registered listeners - depending
+ * on the implementation it also supports the ConsistencyService, which is
+ * invoked on handleNewView.
+ */
+public interface ViewStateManager {
+
+    /**
+     * Installs an optional 'min event delay handler' which, using the given scheduler,
+     * delays sending TOPOLOGY_CHANGED event after receiving a handleNewView - with the
+     * idea as to limit the number of toggling between view states.
+     */
+    void installMinEventDelayHandler(DiscoveryService discoveryService, Scheduler scheduler, 
+            long minEventDelaySecs);
+    
+    /** 
+     * Binds the given eventListener, sending it an INIT event if applicable.
+     * @param eventListener the eventListener that is to bind
+     */
+    void bind(TopologyEventListener eventListener);
+
+    /** 
+     * Unbinds the given eventListener, returning whether or not it was bound at all.
+     * @param eventListener the eventListner that is to unbind
+     * @return whether or not the listener was added in the first place 
+     */
+    boolean unbind(TopologyEventListener eventListener);
+
+    /**
+     * Handles activation - ie marks this manager as activated thus the TOPOLOGY_INIT
+     * event can be sent to already bound listeners and subsequent calls to
+     * handleChanging/handleNewView will result in according/appropriate TOPOLOGY_CHANGING/
+     * TOPOLOGY_CHANGED events.
+     */
+    void handleActivated();
+
+    /** 
+     * Must be called when the corresponding service (typically a DiscoveryService implementation)
+     * is deactivated.
+     * <p>
+     * Will mark this manager as deactivated and flags the last available view as not current.
+     */
+    void handleDeactivated();
+
+    /**
+     * Handles the fact that some (possibly early) indicator of a change in a topology
+     * has been detected and that a new view is being agreed upon (whatever that means,
+     * be it voting or similar).
+     * <p>
+     * Will send out TOPOLOGY_CHANGING to all initialized listeners.
+     */
+    void handleChanging();
+
+    /**
+     * Handles the fact that a new view became true/established and sends out
+     * TOPOLOGY_INIT to uninitialized listeners and TOPOLOGY_CHANGED to already initialized
+     * listeners (in the latter case, also sends a TOPOLOGY_CHANGING if that has not yet been 
+     * done)
+     * @param newView the new, established view
+     * @return false if the newView was the same as previous and we were not in 'changing' mode,
+     * true if we were either in changing mode or the newView was different from the previous one.
+     */
+    void handleNewView(BaseTopologyView newView);
+
+}
\ No newline at end of file

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 31/38: SLING-4603 : minor fix to previous commit : turns out the path must be /var/xy/idMap not /var/xy/idMap/* as that would apply to children only

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 11a225dc8e883351d831b91a9602d0cccb63e379
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 09:01:18 2015 +0000

    SLING-4603 : minor fix to previous commit : turns out the path must be /var/xy/idMap not /var/xy/idMap/* as that would apply to children only
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710148 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/discovery/commons/providers/spi/base/IdMapService.java     | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
index 1ec4583..947e587 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/base/IdMapService.java
@@ -134,7 +134,10 @@ public class IdMapService extends AbstractServiceWithBackgroundCheck implements
                 SlingConstants.TOPIC_RESOURCE_CHANGED,
                 SlingConstants.TOPIC_RESOURCE_REMOVED };
         properties.put(EventConstants.EVENT_TOPIC, topics);
-        String path = getIdMapPath().endsWith("/") ? getIdMapPath() + "*" : getIdMapPath() + "/*";
+        String path = getIdMapPath();
+        if (path.endsWith("/")) {
+            path = path.substring(0, path.length()-1);
+        }
         properties.put(EventConstants.EVENT_FILTER, "(&(path="+path+"))");
         eventHandlerRegistration = bundleContext.registerService(
                 EventHandler.class.getName(), this, properties);

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 20/38: SLING-5173 : reducing visibility of isDelaying

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit f95752847d48c4399c76518daab2586f4a348e40
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 17:02:10 2015 +0000

    SLING-5173 : reducing visibility of isDelaying
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709885 13f79535-47bb-0310-9956-ffa450edef68
---
 .../sling/discovery/commons/providers/base/MinEventDelayHandler.java    | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
index cb37adf..068cf7e 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
@@ -188,7 +188,7 @@ class MinEventDelayHandler {
     }
 
     /** for testing only **/
-    public boolean isDelaying() {
+    boolean isDelaying() {
         return isDelaying;
     }
 

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 09/38: SLING-5131 : introducing ConsistencyService and an oak-discovery-lite based implementation of it - plus SLING-4697 : support for PROPERTIES_CHANGED added to ViewStateManagerImpl

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 9a31ba9ceca3f9182e5f9c9e3a4573607bed7bec
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 8 14:31:45 2015 +0000

    SLING-5131 : introducing ConsistencyService and an oak-discovery-lite based implementation of it - plus SLING-4697 : support for PROPERTIES_CHANGED added to ViewStateManagerImpl
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1707548 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  92 +++
 .../commons/providers/BaseTopologyView.java        |  33 ++
 .../discovery/commons/providers/EventFactory.java  |  82 +++
 .../commons/providers/ViewStateManager.java        | 352 ------------
 .../commons/providers/impl/AsyncEvent.java         |  42 ++
 .../commons/providers/impl/AsyncEventSender.java   | 165 ++++++
 .../providers/impl/MinEventDelayHandler.java       | 168 ++++++
 .../providers/impl/ViewStateManagerFactory.java    |  38 ++
 .../providers/impl/ViewStateManagerImpl.java       | 621 +++++++++++++++++++++
 .../commons/providers/spi/ConsistencyService.java  |  84 +++
 .../spi/impl/OakSyncTokenConsistencyService.java   | 289 ++++++++++
 .../spi/impl/SyncTokenConsistencyService.java      | 320 +++++++++++
 .../commons/providers/TestViewStateManager.java    | 569 -------------------
 .../commons/providers/impl/ClusterTest.java        | 193 +++++++
 .../discovery/commons/providers/impl/Listener.java |  78 +++
 .../commons/providers/impl/SimpleClusterView.java  |  65 +++
 .../providers/impl/SimpleDiscoveryService.java     |  38 ++
 .../providers/impl/SimpleInstanceDescription.java  | 108 ++++
 .../commons/providers/impl/SimpleScheduler.java    | 109 ++++
 .../commons/providers/impl/SimpleTopologyView.java | 214 +++++++
 .../commons/providers/impl/TestHelper.java         | 165 ++++++
 .../providers/impl/TestMinEventDelayHandler.java   | 127 +++++
 .../providers/impl/TestViewStateManager.java       | 590 ++++++++++++++++++++
 .../commons/providers/spi/impl/DiscoLite.java      |  74 +++
 .../commons/providers/spi/impl/MockFactory.java    |  85 +++
 .../commons/providers/spi/impl/MockedResource.java | 296 ++++++++++
 .../providers/spi/impl/MockedResourceResolver.java | 329 +++++++++++
 .../providers/spi/impl/RepositoryHelper.java       | 281 ++++++++++
 .../impl/TestOakSyncTokenConsistencyService.java   | 203 +++++++
 src/test/resources/log4j.properties                |  26 +
 30 files changed, 4915 insertions(+), 921 deletions(-)

diff --git a/pom.xml b/pom.xml
index c8b314d..d1a7e8e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -57,11 +57,77 @@
             <artifactId>bndlib</artifactId>
         </dependency>
         <dependency>
+        	<groupId>org.apache.sling</groupId>
+        	<artifactId>org.apache.sling.commons.scheduler</artifactId>
+        	<version>2.3.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.discovery.api</artifactId>
             <version>1.0.0</version>
             <scope>provided</scope>
         </dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.4.0</version>
+            <scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.commons.json</artifactId>
+			<version>2.0.6</version>
+            <scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>javax.jcr</groupId>
+			<artifactId>jcr</artifactId>
+			<version>2.0</version>
+			<scope>provided</scope>
+		</dependency>
+        <dependency>
+        	<groupId>org.apache.sling</groupId>
+        	<artifactId>org.apache.sling.commons.testing</artifactId>
+        	<version>2.0.16</version>
+        	<scope>test</scope>
+            <exclusions>
+                <!-- slf4j simple implementation logs INFO + higher to stdout (we don't want that behaviour) -->
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-simple</artifactId>
+                </exclusion>
+                <!--  also excluding jcl-over-slf4j as we need a newer vesion of this which is compatible with slf4j 1.6 -->
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>jcl-over-slf4j</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+        	<!--  needed to get the nodetypes for testing properly with sling: prefix -->
+        	<groupId>org.apache.sling</groupId>
+        	<artifactId>org.apache.sling.jcr.resource</artifactId>
+        	<version>2.3.8</version>
+        	<scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-core</artifactId>
+            <version>1.3.7</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-jcr</artifactId>
+            <version>1.3.7</version>
+            <scope>provided</scope>
+        </dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+        	<scope>test</scope>
+		</dependency>
         <dependency>
             <groupId>com.google.code.findbugs</groupId>
             <artifactId>jsr305</artifactId>
@@ -83,5 +149,31 @@
             <version>1.9.5</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+        	<groupId>org.slf4j</groupId>
+        	<artifactId>slf4j-log4j12</artifactId>
+        	<version>1.7.5</version>
+        	<scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+            <version>1.2.13</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.jackrabbit</groupId>
+        	<artifactId>jackrabbit-jcr-commons</artifactId>
+        	<version>2.11.0</version>
+        	<type>bundle</type>
+        	<scope>test</scope>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.jackrabbit</groupId>
+        	<artifactId>jackrabbit-api</artifactId>
+        	<version>2.11.0</version>
+        	<type>bundle</type>
+        	<scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
index d8daefe..e8796b4 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/BaseTopologyView.java
@@ -49,4 +49,37 @@ public abstract class BaseTopologyView implements TopologyView {
         current = false;
     }
 
+    /**
+     * Returns the id that shall be used in the syncToken
+     * by the ConsistencyService.
+     * <p>
+     * The clusterSyncId uniquely identifies each change
+     * of the local cluster for all participating instances. 
+     * That means, all participating instances know of the 
+     * clusterSyncId and it is the same for all instances.
+     * Whenever an instance joins/leaves the cluster, this
+     * clusterSyncId must change. 
+     * <p>
+     * Since this method returns the *local* clusterSyncId,
+     * it doesn't care if a remote cluster experienced
+     * changes - it must only change when the local cluster changes.
+     * However, it *can* change when a remote cluster changes too.
+     * So the requirement is just that it changes *at least* when
+     * the local cluster changes - but implementations
+     * can opt to regard this rather as a TopologyView-ID too
+     * (ie an ID that identifies a particular incarnation
+     * of the TopologyView for all participating instances
+     * in the whole topology).
+     * <p>
+     * This id can further safely be used by the ConsistencyService
+     * to identify a syncToken that it writes and that all
+     * other instances in the lcoal cluster wait for, before
+     * sending a TOPOLOGY_CHANGED event.
+     * <p>
+     * Note that this is obviously not to be confused
+     * with the ClusterView.getId() which is stable throughout
+     * the lifetime of a cluster.
+     */
+    public abstract String getLocalClusterSyncTokenId();
+
 }
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/EventFactory.java b/src/main/java/org/apache/sling/discovery/commons/providers/EventFactory.java
new file mode 100644
index 0000000..9d95689
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/EventFactory.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+
+/** Factory for creating TopologyEvents with BaseTopologyView **/
+public class EventFactory {
+
+    /** Simple factory method for creating a TOPOLOGY_INIT event with the given newView **/
+    public static TopologyEvent newInitEvent(final BaseTopologyView newView) {
+        if (newView==null) {
+            throw new IllegalStateException("newView must not be null");
+        }
+        if (!newView.isCurrent()) {
+            throw new IllegalStateException("newView must be current");
+        }
+        return new TopologyEvent(Type.TOPOLOGY_INIT, null, newView);
+    }
+
+    /** Simple factory method for creating a TOPOLOGY_CHANGING event with the given oldView **/
+    public static TopologyEvent newChangingEvent(final BaseTopologyView oldView) {
+        if (oldView==null) {
+            throw new IllegalStateException("oldView must not be null");
+        }
+        if (oldView.isCurrent()) {
+            throw new IllegalStateException("oldView must not be current");
+        }
+        return new TopologyEvent(Type.TOPOLOGY_CHANGING, oldView, null);
+    }
+
+    /** Simple factory method for creating a TOPOLOGY_CHANGED event with the given old and new views **/
+    public static TopologyEvent newChangedEvent(final BaseTopologyView oldView, final BaseTopologyView newView) {
+        if (oldView==null) {
+            throw new IllegalStateException("oldView must not be null");
+        }
+        if (oldView.isCurrent()) {
+            throw new IllegalStateException("oldView must not be current");
+        }
+        if (newView==null) {
+            throw new IllegalStateException("newView must not be null");
+        }
+        if (!newView.isCurrent()) {
+            throw new IllegalStateException("newView must be current");
+        }
+        return new TopologyEvent(Type.TOPOLOGY_CHANGED, oldView, newView);
+    }
+
+    public static TopologyEvent newPropertiesChangedEvent(final BaseTopologyView oldView, final BaseTopologyView newView) {
+        if (oldView==null) {
+            throw new IllegalStateException("oldView must not be null");
+        }
+        if (oldView.isCurrent()) {
+            throw new IllegalStateException("oldView must not be current");
+        }
+        if (newView==null) {
+            throw new IllegalStateException("newView must not be null");
+        }
+        if (!newView.isCurrent()) {
+            throw new IllegalStateException("newView must be current");
+        }
+        return new TopologyEvent(Type.PROPERTIES_CHANGED, oldView, newView);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
deleted file mode 100644
index 5820f5d..0000000
--- a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.discovery.commons.providers;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-import org.apache.sling.discovery.TopologyEvent;
-import org.apache.sling.discovery.TopologyEvent.Type;
-import org.apache.sling.discovery.TopologyEventListener;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * The ViewStateManager is at the core of managing TopologyEventListeners,
- * the 'view state' (changing vs changed) and sending out the appropriate
- * and according TopologyEvents to the registered listeners.
- * <p>
- * Note that this class is completely unsynchronized and the idea is that
- * (since this class is only of interest to other implementors/providers of 
- * the discovery.api, not to users of the discovery.api however) that those
- * other implementors take care of proper synchronization. Without synchronization
- * this class is prone to threading issues! The most simple form of 
- * synchronization is to synchronize all of the public (non-static..) methods
- * with the same monitor object.
- */
-public class ViewStateManager {
-
-    private final Logger logger = LoggerFactory.getLogger(this.getClass());
-    
-    /** 
-     * List of bound listeners that have already received their INIT event - others are in unInitializedEventListeners.
-     * @see ViewStateManager#unInitializedEventListeners
-     */
-    private List<TopologyEventListener> eventListeners = new ArrayList<TopologyEventListener>();
-
-    /**
-     * List of bound listeners that have not yet received their TOPOLOGY_INIT event - 
-     * once they are sent the TOPOLOGY_INIT event they are moved to eventListeners (and stay there).
-     * <p>
-     * This list becomes necessary for cases where the bind() happens before activate, or after activate but at a time
-     * when the topology is TOPOLOGY_CHANGING - at which point an TOPOLOGY_INIT event can not yet be sent.
-     * @see ViewStateManager#eventListeners
-     */
-    private List<TopologyEventListener> unInitializedEventListeners = new ArrayList<TopologyEventListener>();
-    
-    /** 
-     * Set true when the bundle.activate() was called, false if not yet or when deactivate() is called.
-     * <p>
-     * This controls whether handleChanging() and handleNewView() should cause any events
-     * to be sent - which they do not if called before handleActivated() (or after handleDeactivated())
-     * @see ViewStateManager#handleActivated()
-     * @see ViewStateManager#handleChanging()
-     * @see ViewStateManager#handleNewView(BaseTopologyView)
-     * @see ViewStateManager#handleDeactivated()
-     */
-    private boolean activated;
-    
-    /**
-     * Represents the 'newView' passed to handleNewTopologyView at the most recent invocation.
-     * <p>
-     * This is used for:
-     * <ul>
-     *  <li>sending with the TOPOLOGY_INIT event to newly bound listeners or at activate time</li>
-     *  <li>sending as oldView (marked not current) with the TOPOLOGY_CHANGING event</li>
-     *  <li>sending as oldView (marked not current in case handleChanging() was not invoked) with the TOPOLOGY_CHANGED event</li>
-     * </ul>
-     */
-    private BaseTopologyView previousView;
-    
-    /**
-     * Set to true when handleChanging is called - set to false in handleNewView.
-     * When this goes true, a TOPOLOGY_CHANGING is sent.
-     * When this goes false, a TOPOLOGY_CHANGED is sent.
-     */
-    private boolean isChanging;
-    
-    /** 
-     * Binds the given eventListener, sending it an INIT event if applicable.
-     * <p>
-     * Note: no synchronization done in ViewStateManager, <b>must</b> be done externally
-     * @param eventListener the eventListener that is to bind
-     */
-    public void bind(final TopologyEventListener eventListener) {
-
-        logger.debug("bind: Binding TopologyEventListener {}",
-                eventListener);
-        
-        if (eventListeners.contains(eventListener) || unInitializedEventListeners.contains(eventListener)) {
-            logger.info("bind: TopologyEventListener already registered: "+eventListener);
-            return;
-        }
-
-        if (activated) {
-            // check to see in which state we are
-            if (isChanging || (previousView==null)) {
-                // then we cannot send the TOPOLOGY_INIT at this point - need to delay this
-                unInitializedEventListeners.add(eventListener);
-            } else {
-                // otherwise we can send the TOPOLOGY_INIT now
-                sendEvent(eventListener, newInitEvent(previousView));
-                eventListeners.add(eventListener);
-            }
-        } else {
-            unInitializedEventListeners.add(eventListener);
-        }
-    }
-    
-    /** 
-     * Unbinds the given eventListener, returning whether or not it was bound at all.
-     * <p>
-     * Note: no synchronization done in ViewStateManager, <b>must</b> be done externally
-     * @param eventListener the eventListner that is to unbind
-     * @return whether or not the listener was added in the first place 
-     */
-    public boolean unbind(final TopologyEventListener eventListener) {
-
-        logger.debug("unbind: Releasing TopologyEventListener {}",
-                eventListener);
-
-        // even though a listener must always only ever exist in one of the two,
-        // the unbind we do - for safety-paranoia-reasons - remove them from both
-        final boolean a = eventListeners.remove(eventListener);
-        final boolean b = unInitializedEventListeners.remove(eventListener);
-        return a || b;
-    }
-    
-    /** Internal helper method that sends a given event to a list of listeners **/
-    private void sendEvent(final List<TopologyEventListener> audience, final TopologyEvent event) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("sendEvent: sending topologyEvent {}, to all ({}) listeners", event, audience.size());
-        }
-        for (Iterator<TopologyEventListener> it = audience.iterator(); it.hasNext();) {
-            TopologyEventListener topologyEventListener = it.next();
-            sendEvent(topologyEventListener, event);
-        }
-        if (logger.isDebugEnabled()) {
-            logger.debug("sendEvent: sent topologyEvent {}, to all ({}) listeners", event, audience.size());
-        }
-    }
-    
-    /** Internal helper method that sends a given event to a particular listener **/
-    private void sendEvent(final TopologyEventListener da, final TopologyEvent event) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("sendEvent: sending topologyEvent {}, to {}", event, da);
-        }
-        try{
-            da.handleTopologyEvent(event);
-        } catch(final Exception e) {
-            logger.warn("sendEvent: handler threw exception. handler: "+da+", exception: "+e, e);
-        }
-    }
-
-    /** Note: no synchronization done in ViewStateManager, <b>must</b> be done externally **/
-    public void handleActivated() {
-        logger.debug("handleActivated: activating the ViewStateManager");
-        activated = true;
-        
-        if (previousView!=null && !isChanging) {
-            sendEvent(unInitializedEventListeners, newInitEvent(previousView));
-            eventListeners.addAll(unInitializedEventListeners);
-            unInitializedEventListeners.clear();
-        }
-        logger.debug("handleActivated: activated the ViewStateManager");
-    }
-
-    /** Simple factory method for creating a TOPOLOGY_INIT event with the given newView **/
-    public static TopologyEvent newInitEvent(final BaseTopologyView newView) {
-        if (newView==null) {
-            throw new IllegalStateException("newView must not be null");
-        }
-        if (!newView.isCurrent()) {
-            throw new IllegalStateException("newView must be current");
-        }
-        return new TopologyEvent(Type.TOPOLOGY_INIT, null, newView);
-    }
-    
-    /** Simple factory method for creating a TOPOLOGY_CHANGING event with the given oldView **/
-    public static TopologyEvent newChangingEvent(final BaseTopologyView oldView) {
-        if (oldView==null) {
-            throw new IllegalStateException("oldView must not be null");
-        }
-        if (oldView.isCurrent()) {
-            throw new IllegalStateException("oldView must not be current");
-        }
-        return new TopologyEvent(Type.TOPOLOGY_CHANGING, oldView, null);
-    }
-    
-    /** Simple factory method for creating a TOPOLOGY_CHANGED event with the given old and new views **/
-    public static TopologyEvent newChangedEvent(final BaseTopologyView oldView, final BaseTopologyView newView) {
-        if (oldView==null) {
-            throw new IllegalStateException("oldView must not be null");
-        }
-        if (oldView.isCurrent()) {
-            throw new IllegalStateException("oldView must not be current");
-        }
-        if (newView==null) {
-            throw new IllegalStateException("newView must not be null");
-        }
-        if (!newView.isCurrent()) {
-            throw new IllegalStateException("newView must be current");
-        }
-        return new TopologyEvent(Type.TOPOLOGY_CHANGED, oldView, newView);
-    }
-
-    /** 
-     * Must be called when the corresponding service (typically a DiscoveryService implementation)
-     * is deactivated.
-     * <p>
-     * Will mark this manager as deactivated and flags the last available view as not current.
-     * <p>
-     * Note: no synchronization done in ViewStateManager, <b>must</b> be done externally 
-     */
-    public void handleDeactivated() {
-        logger.debug("handleDeactivated: deactivating the ViewStateManager");
-        activated = false;
-
-        if (previousView!=null) {
-            previousView.setNotCurrent();
-            previousView = null;
-        }
-        isChanging = false;
-        
-        eventListeners.clear();
-        unInitializedEventListeners.clear();
-        logger.debug("handleDeactivated: deactivated the ViewStateManager");
-    }
-    
-    /** Note: no synchronization done in ViewStateManager, <b>must</b> be done externally **/
-    public void handleChanging() {
-        logger.debug("handleChanging: start");
-
-        if (isChanging) {
-            // if isChanging: then this is no news
-            // hence: return asap
-            logger.debug("handleChanging: was already changing - ignoring.");
-            return;
-        }
-        
-        // whether activated or not: set isChanging to true now
-        isChanging = true;
-        
-        if (!activated) {
-            // if not activated: we can only start sending events once activated
-            // hence returning here - after isChanging was set to true accordingly
-            
-            // note however, that if !activated, there should be no eventListeners yet
-            // all of them should be in unInitializedEventListeners at the moment
-            // waiting for activate() and handleNewTopologyView
-            logger.debug("handleChanging: not yet activated - ignoring.");
-            return;
-        }
-        
-        if (previousView==null) {
-            // then nothing further to do - this is a very early changing event
-            // before even the first view was available
-            logger.debug("handleChanging: no previousView set - ignoring.");
-            return;
-        }
-        
-        logger.debug("handleChanging: sending TOPOLOGY_CHANGING to initialized listeners");
-        previousView.setNotCurrent();
-        sendEvent(eventListeners, newChangingEvent(previousView));
-        logger.debug("handleChanging: end");
-    }
-    
-    /** Note: no synchronization done in ViewStateManager, <b>must</b> be done externally **/
-    public void handleNewView(BaseTopologyView newView) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("handleNewView: start, newView={}", newView);
-        }
-        if (!newView.isCurrent()) {
-            logger.error("handleNewView: newView must be current");
-            throw new IllegalArgumentException("newView must be current");
-        }
-        
-        if (!isChanging) {
-            // verify if there is actually a change between previousView and newView
-            // if there isn't, then there is not much point in sending a CHANGING/CHANGED tuple
-            // at all
-            if (previousView!=null && previousView.equals(newView)) {
-                // then nothing to send - the view has not changed, and we haven't
-                // sent the CHANGING event - so we should not do anything here
-                logger.debug("handleNewView: we were not in changing state and new view matches old, so - ignoring");
-                return;
-            }
-            logger.debug("handleNewView: simulating a handleChanging as we were not in changing state");
-            handleChanging();
-            logger.debug("handleNewView: simulation of a handleChanging done");
-        }
-
-        // whether activated or not: set isChanging to false, first thing
-        isChanging = false;
-        
-        if (!activated) {
-            // then all we can do is to pass this on to previoueView
-            previousView = newView;
-            // other than that, we can't currently send any event, before activate
-            logger.debug("handleNewView: not yet activated - ignoring");
-            return;
-        }
-        
-        if (previousView==null) {
-            // this is the first time handleNewTopologyView is called
-            
-            if (eventListeners.size()>0) {
-                logger.info("handleNewTopologyView: no previous view available even though listeners already got CHANGED event");
-            }
-            
-            // otherwise this is the normal case where there are uninitialized event listeners waiting below
-                
-        } else {
-            logger.debug("handleNewView: sending TOPOLOGY_CHANGED to initialized listeners");
-            previousView.setNotCurrent();
-            sendEvent(eventListeners, newChangedEvent(previousView, newView));
-        }
-        
-        if (unInitializedEventListeners.size()>0) {
-            // then there were bindTopologyEventListener calls coming in while
-            // we were in CHANGING state - so we must send those the INIT they were 
-            // waiting for oh so long
-            if (logger.isDebugEnabled()) {
-                logger.debug("handleNewView: sending TOPOLOGY_INIT to uninitialized listeners ({})", 
-                        unInitializedEventListeners.size());
-            }
-            sendEvent(unInitializedEventListeners, newInitEvent(newView));
-            eventListeners.addAll(unInitializedEventListeners);
-            unInitializedEventListeners.clear();
-        }
-        
-        previousView = newView;
-        logger.debug("handleNewView: end");
-    }
-    
-}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEvent.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEvent.java
new file mode 100644
index 0000000..f814dfd
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEvent.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEventListener;
+
+/** SLING-4755 : encapsulates an event that yet has to be sent (asynchronously) for a particular listener **/
+final class AsyncEvent {
+    final TopologyEventListener listener;
+    final TopologyEvent event;
+    AsyncEvent(TopologyEventListener listener, TopologyEvent event) {
+        if (listener==null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        if (event==null) {
+            throw new IllegalArgumentException("event must not be null");
+        }
+        this.listener = listener;
+        this.event = event;
+    }
+    @Override
+    public String toString() {
+        return "an AsyncEvent[event="+event+", listener="+listener+"]";
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEventSender.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEventSender.java
new file mode 100644
index 0000000..c463350
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/AsyncEventSender.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** 
+ * SLING-4755 : background runnable that takes care of asynchronously sending events.
+ * <p>
+ * API is: enqueue() puts a listener-event tuple onto the internal Q, which
+ * is processed in a loop in run that does so (uninterruptably, even catching
+ * Throwables to be 'very safe', but sleeps 5sec if an Error happens) until
+ * flushThenStop() is called - which puts the sender in a state where any pending
+ * events are still sent (flush) but then stops automatically. The argument of
+ * using flush before stop is that the event was originally meant to be sent
+ * before the bundle was stopped - thus just because the bundle is stopped
+ * doesn't undo the event and it still has to be sent. That obviously can
+ * mean that listeners can receive a topology event after deactivate. But I
+ * guess that was already the case before the change to become asynchronous.
+ */
+final class AsyncEventSender implements Runnable {
+    
+    static final Logger logger = LoggerFactory.getLogger(AsyncEventSender.class);
+
+    /** stopped is always false until flushThenStop is called **/
+    private boolean stopped = false;
+
+    /** eventQ contains all AsyncEvent objects that have yet to be sent - in order to be sent **/
+    private final List<AsyncEvent> eventQ = new LinkedList<AsyncEvent>();
+    
+    /** flag to track whether or not an event is currently being sent (but already taken off the Q **/
+    private boolean isSending = false;
+    
+    /** Enqueues a particular event for asynchronous sending to a particular listener **/
+    void enqueue(TopologyEventListener listener, TopologyEvent event) {
+        final AsyncEvent asyncEvent = new AsyncEvent(listener, event);
+        synchronized(eventQ) {
+            eventQ.add(asyncEvent);
+            if (logger.isDebugEnabled()) {
+                logger.debug("enqueue: enqueued event {} for async sending (Q size: {})", asyncEvent, eventQ.size());
+            }
+            eventQ.notifyAll();
+        }
+    }
+    
+    /**
+     * Stops the AsyncEventSender as soon as the queue is empty
+     */
+    void flushThenStop() {
+        synchronized(eventQ) {
+            logger.info("AsyncEventSender.flushThenStop: flushing (size: {}) & stopping...", eventQ.size());
+            stopped = true;
+            eventQ.notifyAll();
+        }
+    }
+    
+    /** Main worker loop that dequeues from the eventQ and calls sendTopologyEvent with each **/
+    public void run() {
+        logger.info("AsyncEventSender.run: started.");
+        try{
+            while(true) {
+                try{
+                    final AsyncEvent asyncEvent;
+                    synchronized(eventQ) {
+                        isSending = false;
+                        while(!stopped && eventQ.isEmpty()) {
+                            try {
+                                eventQ.wait();
+                            } catch (InterruptedException e) {
+                                // issue a log debug but otherwise continue
+                                logger.debug("AsyncEventSender.run: interrupted while waiting for async events");
+                            }
+                        }
+                        if (stopped) {
+                            if (eventQ.isEmpty()) {
+                                // then we have flushed, so we can now finally stop
+                                logger.info("AsyncEventSender.run: flush finished. stopped.");
+                                return;
+                            } else {
+                                // otherwise the eventQ is not yet empty, so we are still in flush mode
+                                logger.info("AsyncEventSender.run: flushing another event. (pending {})", eventQ.size());
+                            }
+                        }
+                        asyncEvent = eventQ.remove(0);
+                        if (logger.isDebugEnabled()) {
+                            logger.debug("AsyncEventSender.run: dequeued event {}, remaining: {}", asyncEvent, eventQ.size());
+                        }
+                        isSending = asyncEvent!=null;
+                    }
+                    if (asyncEvent!=null) {
+                        sendTopologyEvent(asyncEvent);
+                    }
+                } catch(Throwable th) {
+                    // Even though we should never catch Error or RuntimeException
+                    // here's the thinking about doing it anyway:
+                    //  * in case of a RuntimeException that would be less dramatic
+                    //    and catching it is less of an issue - we rather want
+                    //    the background thread to be able to continue than
+                    //    having it finished just because of a RuntimeException
+                    //  * catching an Error is of course not so nice.
+                    //    however, should we really give up this thread even in
+                    //    case of an Error? It could be an OOM or some other 
+                    //    nasty one, for sure. But even if. Chances are that
+                    //    other parts of the system would also get that Error
+                    //    if it is very dramatic. If not, then catching it
+                    //    sounds feasible. 
+                    // My two cents..
+                    // the goal is to avoid quitting the AsyncEventSender thread
+                    logger.error("AsyncEventSender.run: Throwable occurred. Sleeping 5sec. Throwable: "+th, th);
+                    try {
+                        Thread.sleep(5000);
+                    } catch (InterruptedException e) {
+                        logger.warn("AsyncEventSender.run: interrupted while sleeping");
+                    }
+                }
+            }
+        } finally {
+            logger.info("AsyncEventSender.run: quits (finally).");
+        }
+    }
+
+    /** Actual sending of the asynchronous event - catches RuntimeExceptions a listener can send. (Error is caught outside) **/
+    private void sendTopologyEvent(AsyncEvent asyncEvent) {
+        logger.trace("sendTopologyEvent: start");
+        final TopologyEventListener listener = asyncEvent.listener;
+        final TopologyEvent event = asyncEvent.event;
+        try{
+            logger.debug("sendTopologyEvent: sending to listener: {}, event: {}", listener, event);
+            listener.handleTopologyEvent(event);
+        } catch(final Exception e) {
+            logger.warn("sendTopologyEvent: handler threw exception. handler: "+listener+", exception: "+e, e);
+        }
+        logger.trace("sendTopologyEvent: start: listener: {}, event: {}", listener, event);
+    }
+
+    /** for testing only: checks whether there are any events being queued or sent **/
+    boolean hasInFlightEvent() {
+        synchronized(eventQ) {
+            return isSending || !eventQ.isEmpty();
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
new file mode 100644
index 0000000..d032a8b
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/MinEventDelayHandler.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.Date;
+import java.util.concurrent.locks.Lock;
+
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** hooks into the ViewStateManagerImpl and adds a delay between
+ * TOPOLOGY_CHANGING and TOPOLOGY_CHANGED - with the idea to avoid
+ * bundle multiple TOPOLOGY_CHANGED events should they happen within
+ * a very short amount of time.
+ */
+class MinEventDelayHandler {
+
+    private final static Logger logger = LoggerFactory.getLogger(MinEventDelayHandler.class);
+
+    private boolean isDelaying = false;
+
+    private final Scheduler scheduler;
+
+    private final long minEventDelaySecs;
+
+    private DiscoveryService discoveryService;
+
+    private ViewStateManagerImpl viewStateManager;
+
+    private Lock lock;
+    
+    MinEventDelayHandler(ViewStateManagerImpl viewStateManager, Lock lock,
+            DiscoveryService discoveryService, Scheduler scheduler,
+            long minEventDelaySecs) {
+        this.viewStateManager = viewStateManager;
+        this.lock = lock;
+        if (discoveryService==null) {
+            throw new IllegalArgumentException("discoveryService must not be null");
+        }
+        this.discoveryService = discoveryService;
+        this.scheduler = scheduler;
+        if (minEventDelaySecs<=0) {
+            throw new IllegalArgumentException("minEventDelaySecs must be greater than 0 (is "+minEventDelaySecs+")");
+        }
+        this.minEventDelaySecs = minEventDelaySecs;
+    }
+
+    /**
+     * Asks the MinEventDelayHandler to handle the new view
+     * and return true if the caller shouldn't worry about any follow-up action -
+     * only if the method returns false should the caller do the usual 
+     * handleNewView action
+     */
+    boolean handlesNewView(BaseTopologyView newView) {
+        if (isDelaying) {
+            // already delaying, so we'll soon ask the DiscoveryServiceImpl for the
+            // latest view and go ahead then
+            logger.info("handleNewView: already delaying, ignoring new view meanwhile");
+            return true;
+        }
+        
+        if (!viewStateManager.hadPreviousView() 
+                || viewStateManager.isPropertiesDiff(newView) 
+                || viewStateManager.unchanged(newView)) {
+            logger.info("handleNewView: we never had a previous view, so we mustn't delay");
+            return false;
+        }
+        
+        // thanks to force==true this will always return true
+        if (!triggerAsyncDelaying(newView)) {
+            logger.info("handleNewView: could not trigger async delaying, sending new view now.");
+            viewStateManager.handleNewViewNonDelayed(newView);
+        }
+        return true;
+    }
+    
+    private boolean triggerAsyncDelaying(BaseTopologyView newView) {
+        final boolean triggered = runAfter(minEventDelaySecs /*seconds*/ , new Runnable() {
+    
+            public void run() {
+                lock.lock();
+                try{
+                    // unlock the CHANGED event for any subsequent call to handleTopologyChanged()
+                    isDelaying = false;
+
+                    // check if the new topology is already ready
+                    TopologyView t = discoveryService.getTopology();
+                    if (!(t instanceof BaseTopologyView)) {
+                        logger.error("asyncDelay.run: topology not of type BaseTopologyView: "+t);
+                        // cannot continue in this case
+                        return;
+                    }
+                    BaseTopologyView topology = (BaseTopologyView) t;
+                    
+                    if (topology.isCurrent()) {
+                        logger.info("asyncDelay.run: got new view: "+topology);
+                        viewStateManager.handleNewViewNonDelayed(topology);
+                    } else {
+                        logger.info("asyncDelay.run: new view (still) not current, delaying again");
+                        triggerAsyncDelaying(topology);
+                        // we're actually not interested in the result here
+                        // if the async part failed, then we have to rely
+                        // on a later handleNewView to come in - we can't
+                        // really send a view now cos it is not current.
+                        // so we're really stuck to waiting for handleNewView
+                        // in this case.
+                    }
+                } catch(RuntimeException re) {
+                    logger.error("RuntimeException: "+re, re);
+                    throw re;
+                } catch(Error er) {
+                    logger.error("Error: "+er, er);
+                    throw er;
+                } finally {
+                    lock.unlock();
+                }
+            }
+        });
+            
+        logger.info("triggerAsyncDelaying: asynch delaying of "+minEventDelaySecs+" triggered: "+triggered);
+        if (triggered) {
+            isDelaying = true;
+        }
+        return triggered;
+    }
+    
+    /**
+     * run the runnable after the indicated number of seconds, once.
+     * @return true if the scheduling of the runnable worked, false otherwise
+     */
+    private boolean runAfter(long seconds, final Runnable runnable) {
+        final Scheduler theScheduler = scheduler;
+        if (theScheduler == null) {
+            logger.info("runAfter: no scheduler set");
+            return false;
+        }
+        logger.trace("runAfter: trying with scheduler.fireJob");
+        final Date date = new Date(System.currentTimeMillis() + seconds * 1000);
+        try {
+            theScheduler.fireJobAt(null, runnable, null, date);
+            return true;
+        } catch (Exception e) {
+            logger.info("runAfter: could not schedule a job: "+e);
+            return false;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerFactory.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerFactory.java
new file mode 100644
index 0000000..41d3ec7
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.concurrent.locks.Lock;
+
+import org.apache.sling.discovery.commons.providers.ViewStateManager;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+
+/**
+ * Used to create an implementation classes of type ViewStateManager
+ * (with the idea to be able to leave the implementation classes
+ * as package-protected)
+ */
+public class ViewStateManagerFactory {
+
+    public static ViewStateManager newViewStateManager(Lock lock, 
+            ConsistencyService consistencyService) {
+        return new ViewStateManagerImpl(lock, consistencyService);
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
new file mode 100644
index 0000000..5462174
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/impl/ViewStateManagerImpl.java
@@ -0,0 +1,621 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.commons.InstancesDiff;
+import org.apache.sling.discovery.commons.InstancesDiff.InstanceCollection;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.ViewStateManager;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The ViewStateManager is at the core of managing TopologyEventListeners,
+ * the 'view state' (changing vs changed) and sending out the appropriate
+ * and according TopologyEvents to the registered listeners.
+ * <p>
+ * Note re synchronization: this class rquires a lock object to be passed
+ * in the constructor - this will be applied to all public methods
+ * appropriately. Additionally, the ConsistencyService callback will
+ * also be locked using the provided lock object.
+ */
+class ViewStateManagerImpl implements ViewStateManager {
+
+    private static final Logger logger = LoggerFactory.getLogger(ViewStateManagerImpl.class);
+    
+    /** 
+     * List of bound listeners that have already received their INIT event - others are in unInitializedEventListeners.
+     * @see ViewStateManagerImpl#unInitializedEventListeners
+     */
+    private List<TopologyEventListener> eventListeners = new ArrayList<TopologyEventListener>();
+
+    /**
+     * List of bound listeners that have not yet received their TOPOLOGY_INIT event - 
+     * once they are sent the TOPOLOGY_INIT event they are moved to eventListeners (and stay there).
+     * <p>
+     * This list becomes necessary for cases where the bind() happens before activate, or after activate but at a time
+     * when the topology is TOPOLOGY_CHANGING - at which point an TOPOLOGY_INIT event can not yet be sent.
+     * @see ViewStateManagerImpl#eventListeners
+     */
+    private List<TopologyEventListener> unInitializedEventListeners = new ArrayList<TopologyEventListener>();
+    
+    /** 
+     * Set true when the bundle.activate() was called, false if not yet or when deactivate() is called.
+     * <p>
+     * This controls whether handleChanging() and handleNewView() should cause any events
+     * to be sent - which they do not if called before handleActivated() (or after handleDeactivated())
+     * @see ViewStateManagerImpl#handleActivated()
+     * @see ViewStateManagerImpl#handleChanging()
+     * @see ViewStateManagerImpl#handleNewView(BaseTopologyView)
+     * @see ViewStateManagerImpl#handleDeactivated()
+     */
+    private boolean activated;
+    
+    /**
+     * Represents the 'newView' passed to handleNewTopologyView at the most recent invocation.
+     * <p>
+     * This is used for:
+     * <ul>
+     *  <li>sending with the TOPOLOGY_INIT event to newly bound listeners or at activate time</li>
+     *  <li>sending as oldView (marked not current) with the TOPOLOGY_CHANGING event</li>
+     *  <li>sending as oldView (marked not current in case handleChanging() was not invoked) with the TOPOLOGY_CHANGED event</li>
+     * </ul>
+     */
+    private BaseTopologyView previousView;
+    
+    /**
+     * Set to true when handleChanging is called - set to false in handleNewView.
+     * When this goes true, a TOPOLOGY_CHANGING is sent.
+     * When this goes false, a TOPOLOGY_CHANGED is sent.
+     */
+    private boolean isChanging;
+
+    /**
+     * The lock object with which all public methods are guarded - to be provided in the constructor.
+     */
+    protected final Lock lock;
+
+    /**
+     * An optional ConsistencyService can be provided in the constructor which, when set, will
+     * be invoked upon a new view becoming available (in handleNewView) and the actual
+     * TOPOLOGY_CHANGED event will only be sent once the ConsistencyService.sync method
+     * does the according callback (which can be synchronous or asynchronous again).
+     */
+    private final ConsistencyService consistencyService;
+    
+    /** 
+     * A modification counter that increments on each of the following methods:
+     * <ul>
+     *  <li>handleActivated()</li>
+     *  <li>handleDeactivated()</li>
+     *  <li>handleChanging()</li>
+     *  <li>handleNewView()</li>
+     * </ul>
+     * with the intent that - when a consistencyService is set - the callback from the
+     * ConsistencyService can check if any of the above methods was invoked - and if so,
+     * it does not send the TOPOLOGY_CHANGED event due to those new facts that happened
+     * while it was synching with the repository.
+     */
+    private int modCnt = 0;
+
+    /** SLING-4755 : reference to the background AsyncEventSender. Started/stopped in activate/deactivate **/
+    private AsyncEventSender asyncEventSender;
+    
+    /** SLING-5030 : this map contains the event last sent to each listener to prevent duplicate CHANGING events when scheduler is broken**/
+    private Map<TopologyEventListener,TopologyEvent.Type> lastEventMap = new HashMap<TopologyEventListener, TopologyEvent.Type>();
+
+    private MinEventDelayHandler minEventDelayHandler;
+
+    /**
+     * Creates a new ViewStateManager which synchronizes each method with the given
+     * lock and which optionally uses the given ConsistencyService to sync the repository
+     * upon handling a new view where an instances leaves the local cluster.
+     * @param lock the lock to be used - must not be null
+     * @param consistencyService optional (ie can be null) - the ConsistencyService to 
+     * sync the repository upon handling a new view where an instances leaves the local cluster.
+     */
+    ViewStateManagerImpl(Lock lock, ConsistencyService consistencyService) {
+        if (lock==null) {
+            throw new IllegalArgumentException("lock must not be null");
+        }
+        this.lock = lock;
+        this.consistencyService = consistencyService;
+    }
+
+    @Override
+    public void installMinEventDelayHandler(DiscoveryService discoveryService, Scheduler scheduler, long minEventDelaySecs) {
+        this.minEventDelayHandler = new MinEventDelayHandler(this, lock, discoveryService, scheduler, minEventDelaySecs);
+    }
+    
+    protected boolean hadPreviousView() {
+        return previousView!=null;
+    }
+    
+    protected boolean unchanged(BaseTopologyView newView) {
+        if (isChanging) {
+            return false;
+        }
+        if (previousView==null) {
+            return false;
+        }
+        return previousView.equals(newView);
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.sling.discovery.commons.providers.impl.ViewStateManager#bind(org.apache.sling.discovery.TopologyEventListener)
+     */
+    @Override
+    public void bind(final TopologyEventListener eventListener) {
+        logger.trace("bind: start {}", eventListener);
+        lock.lock();
+        try{
+            logger.debug("bind: Binding TopologyEventListener {}",
+                    eventListener);
+            
+            if (eventListeners.contains(eventListener) || unInitializedEventListeners.contains(eventListener)) {
+                logger.info("bind: TopologyEventListener already registered: "+eventListener);
+                return;
+            }
+    
+            if (activated) {
+                // check to see in which state we are
+                if (isChanging || (previousView==null)) {
+                    // then we cannot send the TOPOLOGY_INIT at this point - need to delay this
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("bind: view is not yet/currently not defined (isChanging: "+isChanging+
+                                ", previousView==null: "+(previousView==null)+
+                                ", delaying INIT to "+eventListener);
+                    }
+                    unInitializedEventListeners.add(eventListener);
+                } else {
+                    // otherwise we can send the TOPOLOGY_INIT now
+                    logger.debug("bind: view is defined, sending INIT now to {}",
+                            eventListener);
+                    enqueue(eventListener, EventFactory.newInitEvent(previousView));
+                    eventListeners.add(eventListener);
+                }
+            } else {
+                logger.debug("bind: not yet activated, delaying INIT to {}",
+                        eventListener);
+                unInitializedEventListeners.add(eventListener);
+            }
+        } finally {
+            lock.unlock();
+            logger.trace("bind: end");
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.sling.discovery.commons.providers.impl.ViewStateManager#unbind(org.apache.sling.discovery.TopologyEventListener)
+     */
+    @Override
+    public boolean unbind(final TopologyEventListener eventListener) {
+        logger.trace("unbind: start {}", eventListener);
+        lock.lock();
+        try{
+            logger.debug("unbind: Releasing TopologyEventListener {}",
+                    eventListener);
+    
+            // even though a listener must always only ever exist in one of the two,
+            // the unbind we do - for safety-paranoia-reasons - remove them from both
+            final boolean a = eventListeners.remove(eventListener);
+            final boolean b = unInitializedEventListeners.remove(eventListener);
+            return a || b;
+        } finally {
+            lock.unlock();
+            logger.trace("unbind: end");
+        }
+    }
+    
+    private void enqueue(final TopologyEventListener da, final TopologyEvent event) {
+        logger.trace("enqueue: start: topologyEvent {}, to {}", event, da);
+        if (asyncEventSender==null) {
+            // this should never happen - sendTopologyEvent should only be called
+            // when activated
+            logger.warn("enqueue: asyncEventSender is null, cannot send event ({}, {})!", da, event);
+            return;
+        }
+        if (lastEventMap.get(da)==event.getType() && event.getType()==Type.TOPOLOGY_CHANGING) {
+            // don't sent TOPOLOGY_CHANGING twice
+            logger.debug("enqueue: listener already got TOPOLOGY_CHANGING: {}", da);
+            return;
+        }
+        logger.debug("enqueue: enqueuing topologyEvent {}, to {}", event, da);
+        asyncEventSender.enqueue(da, event);
+        lastEventMap.put(da, event.getType());
+        logger.trace("enqueue: sending topologyEvent {}, to {}", event, da);
+    }
+
+    /** Internal helper method that sends a given event to a list of listeners **/
+    private void enqueueForAll(final List<TopologyEventListener> audience, final TopologyEvent event) {
+        logger.debug("enqueueForAll: sending topologyEvent {}, to all ({}) listeners", event, audience.size());
+        for (Iterator<TopologyEventListener> it = audience.iterator(); it.hasNext();) {
+            TopologyEventListener topologyEventListener = it.next();
+            enqueue(topologyEventListener, event);
+        }
+        logger.trace("enqueueForAll: sent topologyEvent {}, to all ({}) listeners", event, audience.size());
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.sling.discovery.commons.providers.impl.ViewStateManager#handleActivated()
+     */
+    @Override
+    public void handleActivated() {
+        logger.trace("handleActivated: start");
+        lock.lock();
+        try{
+            logger.debug("handleActivated: activating the ViewStateManager");
+            activated = true;
+            modCnt++;
+            
+            // SLING-4755 : start the asyncEventSender in the background
+            //              will be stopped in deactivate (at which point
+            //              all pending events will still be sent but no
+            //              new events can be enqueued)
+            asyncEventSender = new AsyncEventSender();
+            Thread th = new Thread(asyncEventSender);
+            th.setName("Discovery-AsyncEventSender");
+            th.setDaemon(true);
+            th.start();
+
+            if (previousView!=null && !isChanging) {
+                enqueueForAll(unInitializedEventListeners, EventFactory.newInitEvent(previousView));
+                eventListeners.addAll(unInitializedEventListeners);
+                unInitializedEventListeners.clear();
+            }
+            logger.debug("handleActivated: activated the ViewStateManager");
+        } finally {
+            lock.unlock();
+            logger.trace("handleActivated: finally");
+        }
+    }
+
+    /* (non-Javadoc)
+     * @see org.apache.sling.discovery.commons.providers.impl.ViewStateManager#handleDeactivated()
+     */
+    @Override
+    public void handleDeactivated() {
+        logger.trace("handleDeactivated: start");
+        lock.lock();
+        try{
+            logger.debug("handleDeactivated: deactivating the ViewStateManager");
+            activated = false;
+            modCnt++;
+    
+            if (asyncEventSender!=null) {
+                // it should always be not-null though
+                asyncEventSender.flushThenStop();
+                asyncEventSender = null;
+            }
+
+            if (previousView!=null) {
+                previousView.setNotCurrent();
+                logger.trace("handleDeactivated: setting previousView to null");
+                previousView = null;
+            }
+            logger.trace("handleDeactivated: setting isChanging to false");
+            isChanging = false;
+            
+            eventListeners.clear();
+            unInitializedEventListeners.clear();
+            logger.debug("handleDeactivated: deactivated the ViewStateManager");
+        } finally {
+            lock.unlock();
+            logger.trace("handleDeactivated: finally");
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.sling.discovery.commons.providers.impl.ViewStateManager#handleChanging()
+     */
+    @Override
+    public void handleChanging() {
+        logger.trace("handleChanging: start");
+        lock.lock();
+        try{
+    
+            if (isChanging) {
+                // if isChanging: then this is no news
+                // hence: return asap
+                logger.debug("handleChanging: was already changing - ignoring.");
+                return;
+            }
+            modCnt++;
+            
+            // whether activated or not: set isChanging to true now
+            logger.trace("handleChanging: setting isChanging to true");
+            isChanging = true;
+            
+            if (!activated) {
+                // if not activated: we can only start sending events once activated
+                // hence returning here - after isChanging was set to true accordingly
+                
+                // note however, that if !activated, there should be no eventListeners yet
+                // all of them should be in unInitializedEventListeners at the moment
+                // waiting for activate() and handleNewTopologyView
+                logger.debug("handleChanging: not yet activated - ignoring.");
+                return;
+            }
+            
+            if (previousView==null) {
+                // then nothing further to do - this is a very early changing event
+                // before even the first view was available
+                logger.debug("handleChanging: no previousView set - ignoring.");
+                return;
+            }
+            
+            logger.debug("handleChanging: sending TOPOLOGY_CHANGING to initialized listeners");
+            previousView.setNotCurrent();
+            enqueueForAll(eventListeners, EventFactory.newChangingEvent(previousView));
+        } finally {
+            lock.unlock();
+            logger.trace("handleChanging: finally");
+        }
+    }
+    
+    /* (non-Javadoc)
+     * @see org.apache.sling.discovery.commons.providers.impl.ViewStateManager#handleNewView(org.apache.sling.discovery.commons.providers.BaseTopologyView)
+     */
+    @Override
+    public void handleNewView(final BaseTopologyView newView) {
+        logger.trace("handleNewView: start, newView={}", newView);
+        if (newView==null) {
+            throw new IllegalArgumentException("newView must not be null");
+        }
+        if (!newView.isCurrent()) {
+            logger.debug("handleNewView: newView is not current - calling handleChanging.");
+            handleChanging();
+            return;// false;
+        }
+        // paranoia-testing:
+        InstanceDescription localInstance = newView.getLocalInstance();
+        if (localInstance==null) {
+            throw new IllegalStateException("newView does not contain the local instance - hence cannot be current");
+        }
+        if (!localInstance.isLocal()) {
+            throw new IllegalStateException("newView's local instance is not isLocal - very unexpected - hence cannot be current");
+        }
+        logger.debug("handleNewView: newView is current, so trying with minEventDelayHandler...");
+        if (minEventDelayHandler!=null) {
+            if (minEventDelayHandler.handlesNewView(newView)) {
+                return;// true;
+            }
+        }
+        logger.debug("handleNewView: minEventDelayHandler not set or not applicable this time, invoking hanldeNewViewNonDelayed...");
+        /*return */handleNewViewNonDelayed(newView);
+    }
+
+    boolean handleNewViewNonDelayed(final BaseTopologyView newView) {
+        logger.trace("handleNewViewNonDelayed: start");
+        lock.lock();
+        try{
+            logger.debug("handleNewViewNonDelayed: start, newView={}", newView);
+            if (!newView.isCurrent()) {
+                logger.error("handleNewViewNonDelayed: newView must be current");
+                throw new IllegalArgumentException("newView must be current");
+            }
+            modCnt++;
+            
+            if (!isChanging) {
+                // verify if there is actually a change between previousView and newView
+                // if there isn't, then there is not much point in sending a CHANGING/CHANGED tuple
+                // at all
+                if (previousView!=null && previousView.equals(newView)) {
+                    // then nothing to send - the view has not changed, and we haven't
+                    // sent the CHANGING event - so we should not do anything here
+                    logger.debug("handleNewViewNonDelayed: we were not in changing state and new view matches old, so - ignoring");
+                    return false;
+                }
+                logger.debug("handleNewViewNonDelayed: implicitly triggering a handleChanging as we were not in changing state");
+                handleChanging();
+                logger.debug("handleNewViewNonDelayed: implicitly triggering of a handleChanging done");
+            }
+                
+            if (!activated) {
+                // then all we can do is to pass this on to previoueView
+                logger.trace("handleNewViewNonDelayed: setting previousView to {}", newView);
+                previousView = newView;
+                // plus set the isChanging flag to false
+                logger.trace("handleNewViewNonDelayed: setting isChanging to false");
+                isChanging = false;
+                
+                // other than that, we can't currently send any event, before activate
+                logger.debug("handleNewViewNonDelayed: not yet activated - ignoring");
+                return true;
+            }
+            
+            // now check if the view indeed changed or if it was just the properties
+            if (!isChanging && isPropertiesDiff(newView)) {
+                // well then send a properties changed event only
+                // and that one does not go via consistencyservice
+                logger.info("handleNewViewNonDelayed: properties changed to: "+newView);
+                previousView.setNotCurrent();
+                enqueueForAll(eventListeners, EventFactory.newPropertiesChangedEvent(previousView, newView));
+                logger.trace("handleNewViewNonDelayed: setting previousView to {}", newView);
+                previousView = newView;
+                return true;
+            }
+            
+            final boolean invokeConsistencyService;
+            if (consistencyService==null) {
+                logger.debug("handleNewViewNonDelayed: no consistencyService set - continuing directly.");
+                invokeConsistencyService = false;
+            } else if (previousView==null) {
+                // when there was no previous view, we cannot determine if
+                // any instance left
+                // so for safety reason: always invoke the consistencyservice
+                logger.debug("handleNewViewNonDelayed: no previousView set - invoking consistencyService");
+                invokeConsistencyService = true;
+            } else {
+                final InstancesDiff diff = new InstancesDiff(previousView, newView);
+                InstanceCollection removed = diff.removed();
+//                Collection<InstanceDescription> c = removed.get();
+//                Iterator<InstanceDescription> it = c.iterator();
+//                while(it.hasNext()) {
+//                    logger.info("handleNewViewNonDelayed: removed: "+it.next());
+//                }
+                InstanceCollection inClusterView = removed.
+                        isInClusterView(newView.getLocalInstance().getClusterView());
+//                c = removed.get();
+//                it = c.iterator();
+//                while(it.hasNext()) {
+//                    logger.info("handleNewViewNonDelayed: inClusterView: "+it.next());
+//                }
+                final boolean anyInstanceLeftLocalCluster = inClusterView.
+                        get().size()>0;
+                if (anyInstanceLeftLocalCluster) {
+                    logger.debug("handleNewViewNonDelayed: anyInstanceLeftLocalCluster=true, hence invoking consistencyService next");
+                } else {
+                    logger.debug("handleNewViewNonDelayed: anyInstanceLeftLocalCluster=false - continuing directly.");
+                }
+                invokeConsistencyService = anyInstanceLeftLocalCluster;
+            }
+                        
+            if (invokeConsistencyService) {
+                // if "instances from the local cluster have been removed"
+                // then:
+                // run the set consistencyService
+                final int lastModCnt = modCnt;
+                logger.debug("handleNewViewNonDelayed: invoking consistencyService (modCnt={})", modCnt);
+                consistencyService.sync(newView,
+                        new Runnable() {
+                    
+                    public void run() {
+                        logger.trace("consistencyService.callback.run: start. acquiring lock...");
+                        lock.lock();
+                        try{
+                            logger.debug("consistencyService.callback.run: lock aquired. (modCnt should be {}, is {})", lastModCnt, modCnt);
+                            if (modCnt!=lastModCnt) {
+                                logger.debug("consistencyService.callback.run: modCnt changed (from {} to {}) - ignoring",
+                                        lastModCnt, modCnt);
+                                return;
+                            }
+                            // else:
+                            doHandleConsistent(newView);
+                        } finally {
+                            lock.unlock();
+                            logger.trace("consistencyService.callback.run: end.");
+                        }
+                    }
+                    
+                });
+            } else {
+                // otherwise we're either told not to use any ConsistencyService
+                // or using it is not applicable at this stage - so continue
+                // with sending the TOPOLOGY_CHANGED (or TOPOLOGY_INIT if there
+                // are any newly bound topology listeners) directly
+                doHandleConsistent(newView);
+            }
+            logger.debug("handleNewViewNonDelayed: end");
+            return true;
+        } finally {
+            lock.unlock();
+            logger.trace("handleNewViewNonDelayed: finally");
+        }
+    }
+
+    protected boolean isPropertiesDiff(BaseTopologyView newView) {
+        if (previousView==null) {
+            return false;
+        }
+        if (newView==null) {
+            throw new IllegalArgumentException("newView must not be null");
+        }
+        if (previousView.getInstances().size()!=newView.getInstances().size()) {
+            return false;
+        }
+        if (previousView.equals(newView)) {
+            return false;
+        }
+        Set<String> newIds = new HashSet<String>();
+        for(InstanceDescription newInstance : newView.getInstances()) {
+            newIds.add(newInstance.getSlingId());
+        }
+        
+        for(InstanceDescription oldInstance : previousView.getInstances()) {
+            if (!newIds.contains(oldInstance.getSlingId())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void doHandleConsistent(BaseTopologyView newView) {
+        logger.trace("doHandleConsistent: start");
+        
+        // unset the isChanging flag
+        logger.trace("doHandleConsistent: setting isChanging to false");
+        isChanging = false;
+
+        if (previousView==null) {
+            // this is the first time handleNewTopologyView is called
+            
+            if (eventListeners.size()>0) {
+                logger.info("doHandleConsistent: no previous view available even though listeners already got CHANGED event");
+            } else {
+                logger.debug("doHandleConsistent: no previous view and there are no event listeners yet. very quiet.");
+            }
+
+            // otherwise this is the normal case where there are uninitialized event listeners waiting below
+
+        } else {
+            logger.debug("doHandleConsistent: sending TOPOLOGY_CHANGED to initialized listeners");
+            previousView.setNotCurrent();
+            enqueueForAll(eventListeners, EventFactory.newChangedEvent(previousView, newView));
+        }
+        
+        if (unInitializedEventListeners.size()>0) {
+            // then there were bindTopologyEventListener calls coming in while
+            // we were in CHANGING state - so we must send those the INIT they were 
+            // waiting for oh so long
+            logger.debug("doHandleConsistent: sending TOPOLOGY_INIT to uninitialized listeners ({})", 
+                    unInitializedEventListeners.size());
+            enqueueForAll(unInitializedEventListeners, EventFactory.newInitEvent(newView));
+            eventListeners.addAll(unInitializedEventListeners);
+            unInitializedEventListeners.clear();
+        }
+        
+        logger.trace("doHandleConsistent: setting previousView to {}", newView);
+        previousView = newView;
+        logger.trace("doHandleConsistent: end");
+    }
+
+    /** get-hook for testing only! **/
+    AsyncEventSender getAsyncEventSender() {
+        return asyncEventSender;
+    }
+    
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
new file mode 100644
index 0000000..4678657
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/ConsistencyService.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi;
+
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+
+/**
+ * The ConsistencyService can be used to establish strong
+ * consistency with the underlying (eventually consistent) repository in use.
+ * <p>
+ * The issue is described in length in SLING-4627 - the short
+ * version is composed of two different factors:
+ * <ul>
+ * <li>concurrency of discovery service and its listeners on the 
+ * different instances: upon a change in the topology it is 
+ * important that one listener doesn't do activity based on
+ * an older incarnation of the topologyView than another listener
+ * on another instance. they should change from one view to the
+ * next view based on the same repository state.</li>
+ * </li>
+ * <li>when an instance leaves the cluster (eg crashes), then 
+ * depending on the repository it might have left a backlog around
+ * which would yet have to be processed and which could contain
+ * relevant topology-dependent data that should be waited for
+ * to settle before the topology-dependent activity can continue
+ * </li>
+ * </ul>
+ * Both of these two aspects are handled by this ConsistencyService.
+ * The former one by introducing a 'sync token' that gets written
+ * to the repository and on receiving it by the peers they know
+ * that the writing instance is aware of the ongoing change, that
+ * the writing instance has sent out TOPOLOGY_CHANGING and that
+ * the receiving instance has seen all changes that the writing
+ * instance did prior to sending a TOPOLOGY_CHANGING.
+ * The latter aspect is achieved by making use of the underlying
+ * repository: eg on Oak the 'discovery lite' descriptor is
+ * used to determine if any instance not part of the new view
+ * is still being deactivated (eg has backlog). So this second
+ * part is repository dependent.
+ */
+public interface ConsistencyService {
+
+    /**
+     * Starts the synchronization process and calls the provided
+     * callback upon completion.
+     * <p>
+     * sync() is not thread-safe and should not be invoked 
+     * concurrently.
+     * <p>
+     * If sync() gets called before a previous invocation finished,
+     * that previous invocation will be discarded, ie the callback
+     * of the previous invocation will no longer be called.
+     * <p>
+     * The synchronization process consists of making sure that
+     * the repository has processed any potential backlog of instances
+     * that are no longer part of the provided, new view. Plus 
+     * it writes a 'sync-token' to a well-defined location, with
+     * all peers doing the same, and upon seeing all other sync-tokens
+     * declares successful completion - at which point it calls the
+     * callback.run().
+     * @param view the view which all instances in the local cluster
+     * should agree on having seen
+     * @param callback the runnable which should be called after
+     * successful syncing
+     */
+    void sync(BaseTopologyView view, Runnable callback);
+    
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
new file mode 100644
index 0000000..10cff5a
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/OakSyncTokenConsistencyService.java
@@ -0,0 +1,289 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Inherits the 'sync-token' part from the SyncTokenConsistencyService
+ * and adds the 'wait while backlog' part to it, based on
+ * the Oak discovery-lite descriptor.
+ */
+public class OakSyncTokenConsistencyService extends SyncTokenConsistencyService {
+
+    private static final String IDMAP_PATH = "/var/discovery/commons/idmap";
+
+    static enum BacklogStatus {
+        UNDEFINED /* when there was an error retrieving the backlog status with oak */,
+        HAS_BACKLOG /* when oak's discovery lite descriptor indicated that there is still some backlog */,
+        NO_BACKLOG /* when oak's discovery lite descriptor declared we're backlog-free now */
+    }
+    
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    /** TODO: avoid hardcoding the constant here but use an Oak constant class instead if possible */
+    public static final String OAK_DISCOVERYLITE_CLUSTERVIEW = "oak.discoverylite.clusterview";
+
+    private boolean initialized = false;
+
+    private final long waitWhileBacklogTimeoutMillis;
+    
+    /**
+     * 
+     * @param resourceResolverFactory
+     * @param slingId the local slingId
+     * @param syncTokenTimeoutMillis timeout value in millis after which the
+     * sync-token process is cancelled - or -1 if no timeout should be used there
+     * @param waitWhileBacklogTimeoutMillis timeout value in millis after which
+     * the waiting-while-backlog should be cancelled - or -1 if no timeout should be 
+     * used there
+     * @throws LoginException when the login for initialization failed
+     * @throws JSONException when the descriptor wasn't proper json at init time
+     */
+    public OakSyncTokenConsistencyService(ResourceResolverFactory resourceResolverFactory,
+            String slingId, long syncTokenTimeoutMillis, long waitWhileBacklogTimeoutMillis) {
+        super(resourceResolverFactory, slingId, syncTokenTimeoutMillis);
+        this.waitWhileBacklogTimeoutMillis = waitWhileBacklogTimeoutMillis;
+        startBackgroundCheck("idmap-initializer", new BackgroundCheck() {
+            
+            @Override
+            public boolean check() {
+                return ensureInitialized();
+            }
+        }, null, -1);
+    }
+    
+    private boolean ensureInitialized() {
+        if (initialized) {
+            return true;
+        }
+        logger.info("ensureInitialized: initializing.");
+        try {
+            initialized = init();
+            return initialized;
+        } catch (LoginException e) {
+            logger.error("ensureInitialized: could not login: "+e, e);
+            return false;
+        } catch (JSONException e) {
+            logger.error("ensureInitialized: got JSONException: "+e, e);
+            return false;
+        } catch (PersistenceException e) {
+            logger.error("ensureInitialized: got PersistenceException: "+e, e);
+            return false;
+        }
+    }
+    
+    private boolean init() throws LoginException, JSONException, PersistenceException {
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            JSONObject descriptor = getDescriptor(resourceResolver);
+            if (descriptor == null) {
+                logger.info("init: could not yet get descriptor '"+OAK_DISCOVERYLITE_CLUSTERVIEW+"'!");
+                return false;
+            }
+            Object meObj = descriptor.get("me");
+            if (meObj == null || !(meObj instanceof Number)) {
+                logger.info("init: 'me' value of descriptor not a Number: "+meObj+" (descriptor: "+descriptor+")");
+                return false;
+            }
+            Number me = (Number)meObj;
+            final Resource resource = getOrCreateResource(resourceResolver, IDMAP_PATH);
+            ModifiableValueMap idmap = resource.adaptTo(ModifiableValueMap.class);
+            idmap.put(slingId, me.longValue());
+            resourceResolver.commit();
+            logger.info("init: mapped slingId="+slingId+" to discoveryLiteId="+me);
+            return true;
+        } finally {
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+        
+    }
+    
+    @Override
+    public void sync(final BaseTopologyView view, final Runnable callback) {
+        // cancel the previous backgroundCheck if it's still running
+        cancelPreviousBackgroundCheck();
+
+        // first do the wait-for-backlog part
+        logger.info("sync: doing wait-for-backlog part for view="+view);
+        waitWhileBacklog(view, new Runnable() {
+
+            @Override
+            public void run() {
+                // when done, then do the sync-token part
+                logger.info("sync: doing sync-token part for view="+view);
+                syncToken(view, callback);
+            }
+            
+        });
+    }
+
+    private void waitWhileBacklog(final BaseTopologyView view, final Runnable runnable) {
+        // start backgroundChecking until the backlogStatus 
+        // is NO_BACKLOG
+        startBackgroundCheck("OakSyncTokenConsistencyService-waitWhileBacklog", new BackgroundCheck() {
+            
+            @Override
+            public boolean check() {
+                if (!ensureInitialized()) {
+                    logger.info("waitWhileBacklog: could not initialize...");
+                    return false;
+                }
+                BacklogStatus backlogStatus = getBacklogStatus(view);
+                if (backlogStatus == BacklogStatus.NO_BACKLOG) {
+                    logger.info("waitWhileBacklog: no backlog (anymore), done.");
+                    return true;
+                } else {
+                    logger.info("waitWhileBacklog: backlogStatus still "+backlogStatus);
+                    return false;
+                }
+            }
+        }, runnable, waitWhileBacklogTimeoutMillis);
+    }
+    
+    private BacklogStatus getBacklogStatus(BaseTopologyView view) {
+        logger.trace("getBacklogStatus: start");
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            JSONObject descriptor = getDescriptor(resourceResolver);
+            if (descriptor == null) {
+                logger.warn("getBacklogStatus: could not get descriptor '"+OAK_DISCOVERYLITE_CLUSTERVIEW+"'!");
+                return BacklogStatus.UNDEFINED;
+            }
+            // backlog-free means:
+            // 1) 'deactivating' must be empty 
+            //     (otherwise we indeed have a backlog)
+            // 2) all active ids of the descriptor must have a mapping to slingIds
+            //     (otherwise the init failed or is pending for some instance(s))
+            // 3) all 'active' instances must be in the view 
+            //     (otherwise discovery lite might not yet consider 
+            //     an instance dead but discovery-service does)
+            // instead what is fine from a backlog point of view
+            // * instances in the view but listed as 'inactive'
+            //     (this might be the case for just-started instances)
+            // * instances in the view but not contained in the descriptor at all
+            //     (this might be the case for just-started instances)
+            
+            Object activeObj = descriptor.get("active");
+            JSONArray active = (JSONArray) activeObj;
+            Object deactivatingObj = descriptor.get("deactivating");
+            JSONArray deactivating = (JSONArray) deactivatingObj;
+            // we're not worried about 'inactive' ones - as that could
+            // be a larger list filled with legacy entries too
+            // plus once the instance is inactive there's no need to 
+            // check anything further - that one is then backlog-free
+            
+            // 1) 'deactivating' must be empty 
+            if (deactivating.length()!=0) {
+                logger.info("getBacklogStatus: there are deactivating instances: "+deactivating);
+                return BacklogStatus.HAS_BACKLOG;
+            }
+
+            Resource resource = getOrCreateResource(resourceResolver, IDMAP_PATH);
+            ValueMap idmapValueMap = resource.adaptTo(ValueMap.class);
+            ClusterView cluster = view.getLocalInstance().getClusterView();
+            Set<String> slingIds = new HashSet<String>();
+            for (InstanceDescription instance : cluster.getInstances()) {
+                slingIds.add(instance.getSlingId());
+            }
+            Map<Long, String> idmap = new HashMap<Long, String>();
+            for (String slingId : idmapValueMap.keySet()) {
+                Object value = idmapValueMap.get(slingId);
+                if (value instanceof Number) {
+                    Number number = (Number)value;
+                    idmap.put(number.longValue(), slingId);
+                }
+            }
+            
+            for(int i=0; i<active.length(); i++) {
+                Number activeId = (Number) active.get(i);
+                String slingId = idmap.get(activeId.longValue());
+                // 2) all ids of the descriptor must have a mapping to slingIds
+                if (slingId == null) {
+                    logger.info("getBacklogStatus: no slingId found for active id: "+activeId);
+                    return BacklogStatus.UNDEFINED;
+                }
+                // 3) all 'active' instances must be in the view
+                if (!slingIds.contains(slingId)) {
+                    logger.info("getBacklogStatus: active instance's ("+activeId+") slingId ("+slingId+") not found in cluster ("+cluster+")");
+                    return BacklogStatus.HAS_BACKLOG;
+                }
+            }
+
+            logger.info("getBacklogStatus: no backlog (anymore)");
+            return BacklogStatus.NO_BACKLOG;
+        } catch (LoginException e) {
+            logger.error("getBacklogStatus: could not login: "+e, e);
+            return BacklogStatus.UNDEFINED;
+        } catch (JSONException e) {
+            logger.error("getBacklogStatus: got JSONException: "+e, e);
+            return BacklogStatus.UNDEFINED;
+        } catch (PersistenceException e) {
+            logger.error("getBacklogStatus: got PersistenceException: "+e, e);
+            return BacklogStatus.UNDEFINED;
+        } finally {
+            logger.trace("getBacklogStatus: end");
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+    }
+    
+    /**
+     * {"seq":8,"final":true,"id":"aae34e9a-b08d-409e-be10-9ff4106e5387","me":4,"active":[4],"deactivating":[],"inactive":[1,2,3]}
+     */
+    private JSONObject getDescriptor(ResourceResolver resourceResolver) throws JSONException {
+        Session session = resourceResolver.adaptTo(Session.class);
+        if (session == null) {
+            return null;
+        }
+        String descriptorStr = session.getRepository().getDescriptor(OAK_DISCOVERYLITE_CLUSTERVIEW);
+        if (descriptorStr == null) {
+            return null;
+        }
+        JSONObject descriptor = new JSONObject(descriptorStr);
+        return descriptor;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
new file mode 100644
index 0000000..27d80a7
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/spi/impl/SyncTokenConsistencyService.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ResourceUtil;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Implements the 'sync-token' part of the ConsistencyService,
+ * but not the 'wait while backlog' part (which is left to subclasses
+ * if needed).
+ */
+public class SyncTokenConsistencyService implements ConsistencyService {
+
+    private final Logger logger = LoggerFactory.getLogger(getClass());
+
+    private static final String SYNCTOKEN_PATH = "/var/discovery/commons/synctokens";
+
+    private static final String DEFAULT_RESOURCE_TYPE = "sling:Folder";
+
+    protected static Resource getOrCreateResource(
+            final ResourceResolver resourceResolver, final String path)
+            throws PersistenceException {
+        return ResourceUtil.getOrCreateResource(resourceResolver, path,
+                DEFAULT_RESOURCE_TYPE, DEFAULT_RESOURCE_TYPE, true);
+    }
+
+    private final class BackgroundCheckRunnable implements Runnable {
+        private final Runnable callback;
+        private final BackgroundCheck check;
+        private final long timeoutMillis;
+        private volatile boolean cancelled;
+        private final String threadName;
+        
+        // for testing only:
+        private final Object waitObj = new Object();
+        private int waitCnt;
+        private volatile boolean done;
+
+        private BackgroundCheckRunnable(Runnable callback, 
+                BackgroundCheck check, long timeoutMillis, String threadName) {
+            this.callback = callback;
+            this.check = check;
+            this.timeoutMillis = timeoutMillis;
+            this.threadName = threadName;
+        }
+
+        @Override
+        public void run() {
+            logger.debug("backgroundCheck.run: start");
+            long start = System.currentTimeMillis();
+            try{
+                while(!cancelled()) {
+                    if (check.check()) {
+                        if (callback != null) {
+                            callback.run();
+                        }
+                        return;
+                    }
+                    if (timeoutMillis != -1 && 
+                            (System.currentTimeMillis() > start + timeoutMillis)) {
+                        if (callback == null) {
+                            logger.info("backgroundCheck.run: timeout hit (no callback to invoke)");
+                        } else {
+                            logger.info("backgroundCheck.run: timeout hit, invoking callback.");
+                            callback.run();
+                        }
+                        return;
+                    }
+                    logger.debug("backgroundCheck.run: waiting another sec.");
+                    synchronized(waitObj) {
+                        waitCnt++;
+                        try {
+                            waitObj.notify();
+                            waitObj.wait(1000);
+                        } catch (InterruptedException e) {
+                            logger.info("backgroundCheck.run: got interrupted");
+                        }
+                    }
+                }
+                logger.debug("backgroundCheck.run: this run got cancelled. {}", check);
+            } catch(RuntimeException re) {
+                logger.error("backgroundCheck.run: RuntimeException: "+re, re);
+                // nevertheless calling runnable.run in this case
+                if (callback != null) {
+                    logger.info("backgroundCheck.run: RuntimeException -> invoking callback");
+                    callback.run();
+                }
+                throw re;
+            } catch(Error er) {
+                logger.error("backgroundCheck.run: Error: "+er, er);
+                // not calling runnable.run in this case!
+                // since Error is typically severe
+                logger.info("backgroundCheck.run: NOT invoking callback");
+                throw er;
+            } finally {
+                logger.debug("backgroundCheck.run: end");
+                synchronized(waitObj) {
+                    done = true;
+                    waitObj.notify();
+                }
+            }
+        }
+        
+        boolean cancelled() {
+            return cancelled;
+        }
+
+        void cancel() {
+            logger.info("cancel: "+threadName);
+            cancelled = true;
+        }
+
+        public void triggerCheck() {
+            synchronized(waitObj) {
+                int waitCntAtStart = waitCnt;
+                waitObj.notify();
+                while(!done && waitCnt<=waitCntAtStart) {
+                    try {
+                        waitObj.wait();
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException("got interrupted");
+                    }
+                }
+            }
+        }
+    }
+
+    interface BackgroundCheck {
+        
+        boolean check();
+        
+    }
+    
+    protected final ResourceResolverFactory resourceResolverFactory;
+
+    protected final String slingId;
+
+    private final long syncTokenTimeoutMillis;
+
+    protected BackgroundCheckRunnable backgroundCheckRunnable;
+    
+    public SyncTokenConsistencyService(ResourceResolverFactory resourceResolverFactory,
+            String slingId, long syncTokenTimeoutMillis) {
+        if (resourceResolverFactory == null) {
+            throw new IllegalArgumentException("resourceResolverFactory must not be null");
+        }
+        if (slingId == null || slingId.length() == 0) {
+            throw new IllegalArgumentException("slingId must not be null or empty: "+slingId);
+        }
+        this.slingId = slingId;
+        this.resourceResolverFactory = resourceResolverFactory;
+        this.syncTokenTimeoutMillis = syncTokenTimeoutMillis;
+    }
+    
+    protected void cancelPreviousBackgroundCheck() {
+        BackgroundCheckRunnable current = backgroundCheckRunnable;
+        if (current!=null) {
+            current.cancel();
+            // leave backgroundCheckRunnable field as is
+            // as that does not represent a memory leak
+            // nor is it a problem to invoke cancel multiple times
+            // but properly synchronizing on just setting backgroundCheckRunnable
+            // back to null is error-prone and overkill
+        }
+    }
+    
+    protected void startBackgroundCheck(String threadName, final BackgroundCheck check, final Runnable callback, final long timeoutMillis) {
+        // cancel the current one if it's still running
+        cancelPreviousBackgroundCheck();
+        
+        if (check.check()) {
+            // then we're not even going to start the background-thread
+            // we're already done
+            logger.info("backgroundCheck: already done, backgroundCheck successful, invoking callback");
+            callback.run();
+            return;
+        }
+        logger.info("backgroundCheck: spawning background-thread for '"+threadName+"'");
+        backgroundCheckRunnable = new BackgroundCheckRunnable(callback, check, timeoutMillis, threadName);
+        Thread th = new Thread(backgroundCheckRunnable);
+        th.setName(threadName);
+        th.setDaemon(true);
+        th.start();
+    }
+    
+    /** Get or create a ResourceResolver **/
+    protected ResourceResolver getResourceResolver() throws LoginException {
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+    
+    @Override
+    public void sync(BaseTopologyView view, Runnable callback) {
+        // cancel the previous background-check if it's still running
+        cancelPreviousBackgroundCheck();
+
+        syncToken(view, callback);
+        // this implementation doesn't support wait-for-backlog, so
+        // the above doSyncTokenPart will already terminate with invoking the callback
+    }
+
+    protected void syncToken(final BaseTopologyView view, final Runnable callback) {
+        // 1) first storing my syncToken
+        try {
+            storeMySyncToken(view.getLocalClusterSyncTokenId());
+        } catch (LoginException e) {
+            logger.error("syncToken: will run into timeout: could not login for storing my syncToken: "+e, e);
+        } catch (PersistenceException e) {
+            logger.error("syncToken: will run into timeout: got PersistenceException while storing my syncToken: "+e, e);
+        }
+        // if anything goes wrong above, then this will mean for the others
+        // that they will have to wait until the timeout hits
+        // which means we should do the same..
+        // hence no further action possible on error above
+        
+        // 2) then check if all others have done the same already
+        startBackgroundCheck("SyncTokenConsistencyService", new BackgroundCheck() {
+            
+            @Override
+            public boolean check() {
+                return seenAllSyncTokens(view);
+            }
+        }, callback, syncTokenTimeoutMillis);
+    }
+
+    private void storeMySyncToken(String syncTokenId) throws LoginException, PersistenceException {
+        logger.trace("storeMySyncToken: start");
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            final Resource resource = getOrCreateResource(resourceResolver, SYNCTOKEN_PATH);
+            ModifiableValueMap syncTokens = resource.adaptTo(ModifiableValueMap.class);
+            Object currentValue = syncTokens.get(slingId);
+            if (currentValue == null || !syncTokenId.equals(currentValue)) {
+                syncTokens.put(slingId, syncTokenId);
+            }
+            resourceResolver.commit();
+            logger.info("syncToken: stored syncToken of slingId="+slingId+" as="+syncTokenId);
+        } finally {
+            logger.trace("storeMySyncToken: end");
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    private boolean seenAllSyncTokens(BaseTopologyView view) {
+        logger.trace("seenAllSyncTokens: start");
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            Resource resource = getOrCreateResource(resourceResolver, SYNCTOKEN_PATH);
+            ValueMap syncTokens = resource.adaptTo(ValueMap.class);
+            String syncToken = view.getLocalClusterSyncTokenId();
+            
+            for (InstanceDescription instance : view.getLocalInstance().getClusterView().getInstances()) {
+                Object currentValue = syncTokens.get(instance.getSlingId());
+                if (currentValue == null) {
+                    logger.info("seenAllSyncTokens: no syncToken of "+instance);
+                    return false;
+                }
+                if (!syncToken.equals(currentValue)) {
+                    logger.info("seenAllSyncTokens: old syncToken of " + instance
+                            + " : expected=" + syncToken + " got="+currentValue);
+                    return false;
+                }
+            }
+            
+            resourceResolver.commit();
+            logger.info("seenAllSyncTokens: seen all syncTokens!");
+            return true;
+        } catch (LoginException e) {
+            logger.error("seenAllSyncTokens: could not login: "+e, e);
+            return false;
+        } catch (PersistenceException e) {
+            logger.error("seenAllSyncTokens: got PersistenceException: "+e, e);
+            return false;
+        } finally {
+            logger.trace("seenAllSyncTokens: end");
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    /** for testing only! **/
+    protected void triggerBackgroundCheck() {
+        BackgroundCheckRunnable backgroundOp = backgroundCheckRunnable;
+        if (backgroundOp!=null) {
+            backgroundOp.triggerCheck();
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
deleted file mode 100644
index 3179438..0000000
--- a/src/test/java/org/apache/sling/discovery/commons/providers/TestViewStateManager.java
+++ /dev/null
@@ -1,569 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.sling.discovery.commons.providers;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Random;
-import java.util.Set;
-
-import org.apache.sling.discovery.ClusterView;
-import org.apache.sling.discovery.InstanceDescription;
-import org.apache.sling.discovery.InstanceFilter;
-import org.apache.sling.discovery.TopologyEvent;
-import org.apache.sling.discovery.TopologyEventListener;
-import org.apache.sling.discovery.commons.providers.BaseTopologyView;
-import org.apache.sling.discovery.commons.providers.ViewStateManager;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-public class TestViewStateManager {
-
-    private class Listener implements TopologyEventListener {
-
-        private List<TopologyEvent> events = new LinkedList<TopologyEvent>();
-        private TopologyEvent lastEvent;
-        
-        public synchronized void handleTopologyEvent(TopologyEvent event) {
-            events.add(event);
-            lastEvent = event;
-        }
-        
-        public synchronized List<TopologyEvent> getEvents() {
-            return Collections.unmodifiableList(events);
-        }
-
-        public synchronized int countEvents() {
-            return events.size();
-        }
-        
-        public synchronized TopologyEvent getLastEvent() {
-            return lastEvent;
-        }
-
-        public synchronized void clearEvents() {
-            events.clear();
-        }
-
-        public BaseTopologyView getLastView() {
-            if (lastEvent==null) {
-                return null;
-            } else {
-                switch(lastEvent.getType()) {
-                case TOPOLOGY_INIT:
-                case PROPERTIES_CHANGED:
-                case TOPOLOGY_CHANGED: {
-                    return (BaseTopologyView) lastEvent.getNewView();
-                }
-                case TOPOLOGY_CHANGING:{
-                    return (BaseTopologyView) lastEvent.getOldView();
-                }
-                default: {
-                    fail("no other types supported yet");
-                }
-                }
-            }
-            return null;
-        }
-        
-    }
-    
-    private class View extends BaseTopologyView {
-        
-        private final BaseTopologyView clonedView;
-
-        public View() {
-            clonedView = null;
-        }
-        
-        public View(BaseTopologyView clonedView) {
-            this.clonedView = clonedView;
-        }
-        
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof View)) {
-                return false;
-            }
-            final View other = (View) obj;
-            if (clonedView!=null) {
-                if (obj==clonedView) {
-                    return true;
-                }
-            } else if (other.clonedView==this) {
-                return true;
-            }
-            return super.equals(obj);
-        }
-        
-        @Override
-        public int hashCode() {
-            if (clonedView!=null) {
-                return clonedView.hashCode();
-            }
-            return super.hashCode();
-        }
-
-        public View addInstance() {
-            return this;
-        }
-
-        public InstanceDescription getLocalInstance() {
-            throw new IllegalStateException("not yet implemented");
-        }
-
-        public Set<InstanceDescription> getInstances() {
-            throw new IllegalStateException("not yet implemented");
-        }
-
-        public Set<InstanceDescription> findInstances(InstanceFilter filter) {
-            throw new IllegalStateException("not yet implemented");
-        }
-
-        public Set<ClusterView> getClusterViews() {
-            throw new IllegalStateException("not yet implemented");
-        }
-    }
-    
-    private ViewStateManager mgr;
-    
-    private Random defaultRandom;
-
-    @Before
-    public void setup() throws Exception {
-        mgr = new ViewStateManager();
-        defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
-    }
-    
-    @After
-    public void teardown() throws Exception {
-        mgr = null;
-        defaultRandom= null;
-    }
-    
-    private void assertNoEvents(Listener listener) {
-        assertEquals(0, listener.countEvents());
-    }
-    
-    private void assertEvents(Listener listener, TopologyEvent... events) {
-        assertEquals(events.length, listener.countEvents());
-        for (int i = 0; i < events.length; i++) {
-            TopologyEvent e = events[i];
-            assertEquals(e.getType(), listener.getEvents().get(i).getType());
-            switch(e.getType()) {
-            case TOPOLOGY_INIT: {
-                assertNull(listener.getEvents().get(i).getOldView());
-                assertEquals(e.getNewView(), listener.getEvents().get(i).getNewView());
-                break;
-            }
-            case TOPOLOGY_CHANGING: {
-                assertEquals(e.getOldView(), listener.getEvents().get(i).getOldView());
-                assertNull(listener.getEvents().get(i).getNewView());
-                break;
-            }
-            case PROPERTIES_CHANGED:
-            case TOPOLOGY_CHANGED: {
-                assertEquals(e.getOldView(), listener.getEvents().get(i).getOldView());
-                assertEquals(e.getNewView(), listener.getEvents().get(i).getNewView());
-                break;
-            }
-            default: {
-                fail("no other type supported yet");
-            }
-            }
-        }
-        listener.clearEvents();
-    }
-
-    /** does couple loops randomly calling handleChanging() (or not) and then handleNewView().
-     * Note: random is passed to allow customizing and not hardcoding this method to a particular random **/
-    private void randomEventLoop(final Random random, Listener... listeners) {
-        for(int i=0; i<100; i++) {
-            final boolean shouldCallChanging = random.nextBoolean();
-            if (shouldCallChanging) {
-                // dont always do a changing
-                mgr.handleChanging();
-                for(int j=0; j<listeners.length; j++) {
-                    assertEvents(listeners[j], ViewStateManager.newChangingEvent(listeners[j].getLastView()));
-                }
-            } else {
-                for(int j=0; j<listeners.length; j++) {
-                    assertNoEvents(listeners[j]);
-                }
-            }
-            final BaseTopologyView view = new View().addInstance();
-            BaseTopologyView[] lastViews = new BaseTopologyView[listeners.length];
-            for(int j=0; j<listeners.length; j++) {
-                lastViews[j] = listeners[j].getLastView();
-            }
-            mgr.handleNewView(view);
-            if (!shouldCallChanging) {
-                // in that case I should still get a CHANGING - by contract
-                for(int j=0; j<listeners.length; j++) {
-                    assertEvents(listeners[j], ViewStateManager.newChangingEvent(lastViews[j]), ViewStateManager.newChangedEvent(lastViews[j], view));
-                }
-            } else {
-                for(int j=0; j<listeners.length; j++) {
-                    assertEvents(listeners[j], ViewStateManager.newChangedEvent(lastViews[j], view));
-                }
-            }
-        }
-    }
-    
-    @Test
-    public void testDuplicateListeners() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        mgr.bind(listener); // we should be generous and allow duplicate registration
-        assertTrue(mgr.unbind(listener));
-        assertFalse(mgr.unbind(listener));
-        
-        mgr.handleActivated();
-        assertFalse(mgr.unbind(listener));
-        mgr.bind(listener);
-        mgr.bind(listener); // we should be generous and allow duplicate registration
-        assertTrue(mgr.unbind(listener));
-        assertFalse(mgr.unbind(listener));
-    }
-    
-    @Test
-    public void testBindActivateChangingChanged() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindChangingActivateChanged() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindChangingChangedActivate() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindChangingChangedChangingActivate() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view2 = new View().addInstance();
-        mgr.handleNewView(view2);
-        assertEvents(listener, ViewStateManager.newInitEvent(view2));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindChangedChangingActivate() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view2 = new View().addInstance();
-        mgr.handleNewView(view2);
-        assertEvents(listener, ViewStateManager.newInitEvent(view2));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testActivateBindChangingChanged() throws Exception {
-        final Listener listener = new Listener();
-        // first activate
-        mgr.handleActivated();
-        assertNoEvents(listener); // paranoia
-        // then bind
-        mgr.bind(listener);
-        assertNoEvents(listener); // there was no changing or changed yet
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener);
-    }
-
-    @Test
-    public void testActivateChangingBindChanged() throws Exception {
-        final Listener listener = new Listener();
-        // first activate
-        mgr.handleActivated();
-        assertNoEvents(listener); // paranoia
-        mgr.handleChanging();
-        assertNoEvents(listener); // no listener yet
-        // then bind
-        mgr.bind(listener);
-        assertNoEvents(listener); // no changed event yet
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener);
-    }
-
-    @Test
-    public void testActivateChangingChangedBind() throws Exception {
-        final Listener listener = new Listener();
-        // first activate
-        mgr.handleActivated();
-        assertNoEvents(listener); // paranoia
-        mgr.handleChanging();
-        assertNoEvents(listener); // no listener yet
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertNoEvents(listener); // no listener yet
-        // then bind
-        mgr.bind(listener);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindActivateBindChangingChanged() throws Exception {
-        final Listener listener1 = new Listener();
-        final Listener listener2 = new Listener();
-        
-        mgr.bind(listener1);
-        assertNoEvents(listener1);
-        mgr.handleActivated();
-        assertNoEvents(listener1);
-        mgr.bind(listener2);
-        assertNoEvents(listener1);
-        assertNoEvents(listener2);
-        mgr.handleChanging();
-        assertNoEvents(listener1);
-        assertNoEvents(listener2);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener1, ViewStateManager.newInitEvent(view));
-        assertEvents(listener2, ViewStateManager.newInitEvent(view));
-        
-        randomEventLoop(defaultRandom, listener1, listener2);
-    }
-
-    @Test
-    public void testBindActivateChangingBindChanged() throws Exception {
-        final Listener listener1 = new Listener();
-        final Listener listener2 = new Listener();
-        
-        mgr.bind(listener1);
-        assertNoEvents(listener1);
-        mgr.handleActivated();
-        assertNoEvents(listener1);
-        mgr.handleChanging();
-        assertNoEvents(listener1);
-        mgr.bind(listener2);
-        assertNoEvents(listener1);
-        assertNoEvents(listener2);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener1, ViewStateManager.newInitEvent(view));
-        assertEvents(listener2, ViewStateManager.newInitEvent(view));
-
-        randomEventLoop(defaultRandom, listener1, listener2);
-    }
-    
-    @Test
-    public void testActivateBindChangingDuplicateHandleNewView() throws Exception {
-        final Listener listener = new Listener();
-        mgr.handleActivated();
-        mgr.bind(listener);
-        mgr.handleChanging();
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        mgr.handleNewView(clone(view));
-        assertNoEvents(listener);
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testActivateBindChangingChangedBindDuplicateHandleNewView() throws Exception {
-        final Listener listener1 = new Listener();
-        mgr.handleActivated();
-        mgr.bind(listener1);
-        mgr.handleChanging();
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertEvents(listener1, ViewStateManager.newInitEvent(view));
-        
-        final Listener listener2 = new Listener();
-        mgr.bind(listener2);
-        mgr.handleNewView(clone(view));
-        assertNoEvents(listener1);
-        assertEvents(listener2, ViewStateManager.newInitEvent(view));
-        randomEventLoop(defaultRandom, listener1, listener2);
-    }
-    
-    @Test
-    public void testActivateChangedBindDuplicateHandleNewView() throws Exception {
-        final Listener listener = new Listener();
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view = new View().addInstance();
-        mgr.handleNewView(view);
-        assertNoEvents(listener);
-        mgr.bind(listener);
-        assertEvents(listener, ViewStateManager.newInitEvent(view));
-        mgr.handleNewView(clone(view));
-        assertNoEvents(listener);
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindActivateChangedChanged() throws Exception {
-        final Listener listener = new Listener();
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        final BaseTopologyView view1 = new View().addInstance();
-        mgr.handleNewView(view1);
-        assertEvents(listener, ViewStateManager.newInitEvent(view1));
-        final BaseTopologyView view2 = new View().addInstance();
-        mgr.handleNewView(view2);
-        assertEvents(listener, ViewStateManager.newChangingEvent(view1), ViewStateManager.newChangedEvent(view1, view2));
-        randomEventLoop(defaultRandom, listener);
-    }
-    
-    @Test
-    public void testBindActivateChangedDeactivateChangingActivateChanged() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view1 = new View().addInstance();
-        mgr.handleNewView(view1);
-        assertEvents(listener, ViewStateManager.newInitEvent(view1));
-        mgr.handleDeactivated();
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        mgr.bind(listener); // need to bind again after deactivate
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view2 = new View().addInstance();
-        mgr.handleNewView(view2);
-        assertEvents(listener, ViewStateManager.newInitEvent(view2));
-    }
-
-    @Test
-    public void testBindActivateChangedDeactivateChangedActivateChanged() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view1 = new View().addInstance();
-        mgr.handleNewView(view1);
-        assertEvents(listener, ViewStateManager.newInitEvent(view1));
-        mgr.handleDeactivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view2 = new View().addInstance();
-        mgr.handleNewView(view2);
-        assertNoEvents(listener);
-        mgr.bind(listener); // need to bind again after deactivate
-        mgr.handleActivated();
-        assertEvents(listener, ViewStateManager.newInitEvent(view2));
-        final BaseTopologyView view3 = new View().addInstance();
-        mgr.handleNewView(view3);
-        assertEvents(listener, ViewStateManager.newChangingEvent(view2), ViewStateManager.newChangedEvent(view2, view3));
-    }
-
-    @Test
-    public void testBindActivateChangedChangingDeactivateActivateChangingChanged() throws Exception {
-        final Listener listener = new Listener();
-        mgr.bind(listener);
-        assertNoEvents(listener);
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        final BaseTopologyView view1 = new View().addInstance();
-        mgr.handleNewView(view1);
-        assertEvents(listener, ViewStateManager.newInitEvent(view1));
-        mgr.handleChanging();
-        assertEvents(listener, ViewStateManager.newChangingEvent(view1));
-        mgr.handleDeactivated();
-        assertNoEvents(listener);
-        mgr.bind(listener); // need to bind again after deactivate
-        mgr.handleActivated();
-        assertNoEvents(listener);
-        mgr.handleChanging();
-        assertNoEvents(listener);
-        final BaseTopologyView view2 = new View().addInstance();
-        mgr.handleNewView(view2);
-        assertEvents(listener, ViewStateManager.newInitEvent(view2));
-    }
-
-    private BaseTopologyView clone(final BaseTopologyView view) {
-        return new View(view);
-    }
-}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
new file mode 100644
index 0000000..d43a94c
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/ClusterTest.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClusterTest {
+    
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private List<ViewStateManagerImpl> mgrList;
+    
+    private Random defaultRandom;
+
+    @Before
+    public void setup() throws Exception {
+        mgrList = new LinkedList<ViewStateManagerImpl>();
+        defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
+    }
+    
+    @After
+    public void teardown() throws Exception {
+        mgrList = null;
+        defaultRandom= null;
+    }
+    
+    private ViewStateManagerImpl newMgr() {
+        ViewStateManagerImpl mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+            
+            public void sync(BaseTopologyView view, Runnable callback) {
+                callback.run();
+            }
+        });
+        mgrList.add(mgr);
+        return mgr;
+    }
+    
+    private void waitForInflightEvents(ViewStateManagerImpl mgr) throws InterruptedException {
+        if (mgr==null) {
+            throw new IllegalArgumentException("mgr must not be null");
+        }
+        if (mgr.getAsyncEventSender()==null) {
+            logger.info("waitForInflightEvents: mgr not yet activated...");
+            return;
+        }
+        while(mgr.getAsyncEventSender().hasInFlightEvent()) {
+            logger.info("waitForInflightEvents: waiting 10ms...");
+            Thread.sleep(10);
+        }
+    }
+    
+    private void assertCountEvents(ViewStateManagerImpl mgr, Listener l, TopologyEvent.Type... types) throws InterruptedException {
+        waitForInflightEvents(mgr);
+        assertEquals(types.length, l.countEvents());
+        Iterator<TopologyEvent> it = l.getEvents().iterator();
+        int i=0;
+        while(it.hasNext() && (i<types.length)) {
+            TopologyEvent expectedEvent = it.next();
+            Type gotType = types[i++];
+            assertEquals(expectedEvent.getType(), gotType);
+        }
+        if (it.hasNext()) {
+            StringBuffer additionalTypes = new StringBuffer();
+            while(it.hasNext()) {
+                additionalTypes.append(",");
+                additionalTypes.append(it.next().getType());
+            }
+            fail("got more events than expected : "+additionalTypes);
+        }
+        if (i<types.length) {
+            StringBuffer additionalTypes = new StringBuffer();
+            while(i<types.length) {
+                additionalTypes.append(",");
+                additionalTypes.append(types[i++]);
+            }
+            fail("did not get all events, also expected : "+additionalTypes);
+        }
+    }
+
+    private void fail(String string) {
+        // TODO Auto-generated method stub
+        
+    }
+
+    @Test
+    public void testTwoNodes() throws Exception {
+        final ViewStateManagerImpl mgr1 = newMgr();
+        final String slingId1 = UUID.randomUUID().toString();
+        final ViewStateManagerImpl mgr2 = newMgr();
+        final String slingId2 = UUID.randomUUID().toString();
+        
+        // bind l1
+        Listener l1 = new Listener();
+        mgr1.bind(l1);
+        assertCountEvents(mgr1, l1);
+        
+        // bind l2
+        Listener l2 = new Listener();
+        mgr2.bind(l2);
+        assertCountEvents(mgr2, l2);
+        
+        // fiddle with l1 - without any events expected to be sent
+        mgr1.handleChanging();
+        assertCountEvents(mgr1, l1);
+        mgr1.handleActivated();
+        assertCountEvents(mgr1, l1);
+        mgr1.handleChanging();
+        assertCountEvents(mgr1, l1);
+
+        // fiddle with l2 - without any events expected to be sent
+        mgr2.handleChanging();
+        assertCountEvents(mgr2, l2);
+        mgr2.handleActivated();
+        assertCountEvents(mgr2, l2);
+        mgr2.handleChanging();
+        assertCountEvents(mgr2, l2);
+        
+        // call handleNewView with not-current views first...
+        BaseTopologyView vA1 = TestHelper.newView(false, slingId1, slingId1, slingId1, slingId2);
+        mgr1.handleNewView(vA1);
+        assertCountEvents(mgr1, l1);
+        assertCountEvents(mgr2, l2);
+        BaseTopologyView vB1 = TestHelper.newView(false, slingId1, slingId2, slingId1, slingId2);
+        mgr2.handleNewView(vB1);
+        assertCountEvents(mgr1, l1);
+        assertCountEvents(mgr2, l2);
+        
+        // then call handleNewView with a current view - that should now sent the INIT
+        BaseTopologyView vA2 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
+        mgr1.handleNewView(vA2);
+        assertCountEvents(mgr1, l1, Type.TOPOLOGY_INIT);
+        assertCountEvents(mgr2, l2);
+        BaseTopologyView vB2 = TestHelper.newView(true, slingId1, slingId2, slingId1, slingId2);
+        mgr2.handleNewView(vB2);
+        assertCountEvents(mgr1, l1, Type.TOPOLOGY_INIT);
+        assertCountEvents(mgr2, l2, Type.TOPOLOGY_INIT);
+        
+        // now let instance1 get decoupled from the cluster (pseudo-network-partitioning)
+        BaseTopologyView vB3 = TestHelper.newView(true, slingId2, slingId2, slingId2);
+        mgr2.handleNewView(vB3);
+        assertCountEvents(mgr1, l1, Type.TOPOLOGY_INIT);
+        assertCountEvents(mgr2, l2, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+    
+        // now let instance1 take note of this decoupling
+        mgr1.handleChanging();
+        assertCountEvents(mgr1, l1, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING);
+        assertCountEvents(mgr2, l2, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+        
+        // and now let instance1 rejoin
+        BaseTopologyView vA4 = TestHelper.newView(true, slingId2, slingId1, slingId1, slingId2);
+        mgr1.handleNewView(vA4);
+        assertCountEvents(mgr1, l1, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+        assertCountEvents(mgr2, l2, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+        BaseTopologyView vB4 = TestHelper.newView(true, slingId2, slingId2, slingId1, slingId2);
+        mgr2.handleNewView(vA4);
+        assertCountEvents(mgr1, l1, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+        assertCountEvents(mgr2, l2, Type.TOPOLOGY_INIT, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED, Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/Listener.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/Listener.java
new file mode 100644
index 0000000..fa07cd5
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/Listener.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import static org.junit.Assert.fail;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+
+public class Listener implements TopologyEventListener {
+
+    private List<TopologyEvent> events = new LinkedList<TopologyEvent>();
+    private TopologyEvent lastEvent;
+    
+    public synchronized void handleTopologyEvent(TopologyEvent event) {
+        events.add(event);
+        lastEvent = event;
+    }
+    
+    public synchronized List<TopologyEvent> getEvents() {
+        return Collections.unmodifiableList(events);
+    }
+
+    public synchronized int countEvents() {
+        return events.size();
+    }
+    
+    public synchronized TopologyEvent getLastEvent() {
+        return lastEvent;
+    }
+
+    public synchronized void clearEvents() {
+        events.clear();
+    }
+
+    public BaseTopologyView getLastView() {
+        if (lastEvent==null) {
+            return null;
+        } else {
+            switch(lastEvent.getType()) {
+            case TOPOLOGY_INIT:
+            case PROPERTIES_CHANGED:
+            case TOPOLOGY_CHANGED: {
+                return (BaseTopologyView) lastEvent.getNewView();
+            }
+            case TOPOLOGY_CHANGING:{
+                return (BaseTopologyView) lastEvent.getOldView();
+            }
+            default: {
+                fail("no other types supported yet");
+            }
+            }
+        }
+        return null;
+    }
+    
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleClusterView.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleClusterView.java
new file mode 100644
index 0000000..74b7812
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleClusterView.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+
+public class SimpleClusterView implements ClusterView {
+
+    private final String id;
+    private List<InstanceDescription> instances = new LinkedList<InstanceDescription>();
+
+    public SimpleClusterView(String id) {
+        this.id = id;
+    }
+    
+    @Override
+    public String getId() {
+        return id;
+    }
+    
+    public void addInstanceDescription(InstanceDescription id) {
+        instances.add(id);
+    }
+
+    public boolean removeInstanceDescription(InstanceDescription id) {
+        return instances.remove(id);
+    }
+
+    @Override
+    public List<InstanceDescription> getInstances() {
+        return Collections.unmodifiableList(instances);
+    }
+
+    @Override
+    public InstanceDescription getLeader() {
+        for (InstanceDescription instanceDescription : instances) {
+            if (instanceDescription.isLeader()) {
+                return instanceDescription;
+            }
+        }
+        throw new IllegalStateException("no leader");
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleDiscoveryService.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleDiscoveryService.java
new file mode 100644
index 0000000..fabe0f6
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleDiscoveryService.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+
+public class SimpleDiscoveryService implements DiscoveryService {
+
+    private BaseTopologyView topologyView;
+
+    public void setTopoology(BaseTopologyView topologyView) {
+        this.topologyView = topologyView;
+    }
+    
+    @Override
+    public TopologyView getTopology() {
+        return topologyView;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
new file mode 100644
index 0000000..f5a485f
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleInstanceDescription.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+
+public class SimpleInstanceDescription implements InstanceDescription {
+
+    private ClusterView clusterView;
+    private final boolean isLeader;
+    private final boolean isLocal;
+    private final String slingId;
+    private final Map<String, String> properties;
+
+    public SimpleInstanceDescription(boolean isLeader, boolean isLocal, String slingId,
+            Map<String, String> properties) {
+        this.isLeader = isLeader;
+        this.isLocal = isLocal;
+        this.slingId = slingId;
+        this.properties = properties;
+    }
+    
+    @Override
+    public String toString() {
+        return "Instance["+slingId+"]";
+    }
+    
+    @Override
+    public int hashCode() {
+        return slingId.hashCode() + (isLeader?0:1) + (isLocal?0:1);
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SimpleInstanceDescription)) {
+            return false;
+        }
+        SimpleInstanceDescription other = (SimpleInstanceDescription) obj;
+        if (!slingId.equals(other.slingId)) {
+            return false;
+        }
+        if (isLeader!=other.isLeader) {
+            return false;
+        }
+        if (isLocal!=other.isLocal) {
+            return false;
+        }
+        return true;
+    }
+
+    public void setClusterView(ClusterView clusterView) {
+        this.clusterView = clusterView;
+    }
+    
+    @Override
+    public ClusterView getClusterView() {
+        return clusterView;
+    }
+
+    @Override
+    public boolean isLeader() {
+        return isLeader;
+    }
+
+    @Override
+    public boolean isLocal() {
+        return isLocal;
+    }
+
+    @Override
+    public String getSlingId() {
+        return slingId;
+    }
+
+    @Override
+    public String getProperty(String name) {
+        return properties.get(name);
+    }
+
+    @Override
+    public Map<String, String> getProperties() {
+        if (properties==null) {
+            return new HashMap<String, String>();
+        }
+        return new HashMap<String,String>(properties);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleScheduler.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleScheduler.java
new file mode 100644
index 0000000..be28878
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleScheduler.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+import org.apache.sling.commons.scheduler.Scheduler;
+
+public class SimpleScheduler implements Scheduler {
+
+    private boolean failMode;
+
+    @Override
+    public void addJob(String name, Object job, Map<String, Serializable> config, String schedulingExpression,
+            boolean canRunConcurrently) throws Exception {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public void addPeriodicJob(String name, Object job, Map<String, Serializable> config, long period, boolean canRunConcurrently)
+            throws Exception {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public void addPeriodicJob(String name, Object job, Map<String, Serializable> config, long period, boolean canRunConcurrently,
+            boolean startImmediate) throws Exception {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public void fireJob(Object job, Map<String, Serializable> config) throws Exception {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public boolean fireJob(Object job, Map<String, Serializable> config, int times, long period) {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public void fireJobAt(String name, final Object job, Map<String, Serializable> config, final Date date) throws Exception {
+        if (!(job instanceof Runnable)) {
+            throw new IllegalArgumentException("only runnables supported for now");
+        }
+        final Runnable j = (Runnable)job;
+        Runnable r = new Runnable() {
+
+            @Override
+            public void run() {
+                while (System.currentTimeMillis()<date.getTime()) {
+                    try {
+                        Thread.sleep(10);
+                    } catch (InterruptedException e) {
+                        e.printStackTrace();
+                        Thread.yield();
+                    }
+                }
+                j.run();
+            }
+            
+        };
+        async(r, name);
+    }
+
+    private void async(Runnable r, String name) {
+        if (failMode) {
+            throw new IllegalStateException("failMode");
+        }
+        Thread th = new Thread(r);
+        th.setName("async test thread for "+name);
+        th.setDaemon(true);
+        th.start();
+    }
+
+    @Override
+    public boolean fireJobAt(String name, Object job, Map<String, Serializable> config, Date date, int times, long period) {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    @Override
+    public void removeJob(String name) throws NoSuchElementException {
+        throw new IllegalStateException("not yet impl");
+    }
+
+    public void failMode() {
+        failMode = true;
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
new file mode 100644
index 0000000..ac9e292
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/SimpleTopologyView.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+
+public class SimpleTopologyView extends BaseTopologyView {
+
+    private List<InstanceDescription> instances = new LinkedList<InstanceDescription>();
+
+    private final String id;
+
+    public SimpleTopologyView() {
+        id = UUID.randomUUID().toString();
+    }
+    
+    public SimpleTopologyView(String id) {
+        this.id = id;
+    }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof SimpleTopologyView)) {
+            return false;
+        }
+        final SimpleTopologyView other = (SimpleTopologyView) obj;
+        if (this==other) {
+            return true;
+        }
+        if (!id.equals(other.id)) {
+            return false;
+        }
+        if (this.instances.size()!=other.instances.size()) {
+            return false;
+        }
+        for (Iterator<InstanceDescription> it = instances.iterator(); it.hasNext();) {
+            InstanceDescription instanceDescription = (InstanceDescription) it
+                    .next();
+            boolean found = false;
+            for (Iterator<?> it2 = other.instances.iterator(); it2
+                    .hasNext();) {
+                InstanceDescription otherId = (InstanceDescription) it2
+                        .next();
+                if (instanceDescription.equals(otherId)) {
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                return false;
+            }
+        }
+        return true;
+    }
+    
+    @Override
+    public int hashCode() {
+        int c=0;
+        for (Iterator<InstanceDescription> it = instances.iterator(); it.hasNext();) {
+            InstanceDescription instanceDescription = (InstanceDescription) it
+                    .next();
+            c+=instanceDescription.hashCode();
+        }
+        return c;
+    }
+
+    public void addInstanceDescription(InstanceDescription id) {
+        instances.add(id);
+    }
+    
+    @Override
+    public InstanceDescription getLocalInstance() {
+        InstanceDescription result = null;
+        for (Iterator<InstanceDescription> it = instances.iterator(); it.hasNext();) {
+            InstanceDescription instanceDescription = (InstanceDescription) it
+                    .next();
+            if (instanceDescription.isLocal()) {
+                if (result!=null) {
+                    throw new IllegalStateException("multiple local instances");
+                }
+                result = instanceDescription;
+            }
+        }
+        if (result==null) {
+            throw new IllegalStateException("no local instance found");
+        }
+        return result;
+    }
+
+    @Override
+    public Set<InstanceDescription> getInstances() {
+        return new HashSet<InstanceDescription>(instances);
+    }
+
+    @Override
+    public Set<InstanceDescription> findInstances(InstanceFilter filter) {
+        throw new IllegalStateException("not yet implemented");
+    }
+
+    @Override
+    public Set<ClusterView> getClusterViews() {
+        Set<ClusterView> clusters = new HashSet<ClusterView>();
+        for (InstanceDescription instanceDescription : instances) {
+            clusters.add(instanceDescription.getClusterView());
+        }
+        return clusters;
+    }
+
+    @Override
+    public String getLocalClusterSyncTokenId() {
+        return id;
+    }
+
+    public SimpleTopologyView addInstance() {
+        final String slingId = UUID.randomUUID().toString();
+        final String clusterId = UUID.randomUUID().toString();
+        final SimpleClusterView cluster = new SimpleClusterView(clusterId);
+        final SimpleInstanceDescription instance = new SimpleInstanceDescription(true, true, slingId, null);
+        instance.setClusterView(cluster);
+        cluster.addInstanceDescription(instance);
+        instances.add(instance);
+        return this;
+    }
+
+    public SimpleTopologyView addInstance(String slingId, SimpleClusterView cluster, boolean isLeader, boolean isLocal) {
+        final SimpleInstanceDescription instance = new SimpleInstanceDescription(isLeader, isLocal, slingId, null);
+        instance.setClusterView(cluster);
+        cluster.addInstanceDescription(instance);
+        instances.add(instance);
+        return this;
+    }
+
+    public SimpleTopologyView addInstance(InstanceDescription artefact) {
+        final String slingId = artefact.getSlingId();
+        final boolean isLeader = artefact.isLeader();
+        final boolean isLocal = artefact.isLocal();
+        SimpleClusterView cluster = (SimpleClusterView) artefact.getClusterView();
+        final SimpleInstanceDescription instance = new SimpleInstanceDescription(isLeader, isLocal, slingId, null);
+        instance.setClusterView(cluster);
+        cluster.addInstanceDescription(instance);
+        instances.add(instance);
+        return this;
+    }
+
+    public SimpleTopologyView removeInstance(String slingId) {
+        for (Iterator<InstanceDescription> it = instances.iterator(); it.hasNext();) {
+            InstanceDescription id = (InstanceDescription) it.next();
+            if (id.getSlingId().equals(slingId)) {
+                it.remove();
+                SimpleClusterView cluster = (SimpleClusterView) id.getClusterView();
+                if (!cluster.removeInstanceDescription(id)) {
+                    throw new IllegalStateException("could not remove id: "+id);
+                }
+                return this;
+            }
+        }
+        throw new IllegalStateException("instance not found: "+slingId);
+    }
+
+    public static SimpleTopologyView clone(final SimpleTopologyView view) {
+        final SimpleTopologyView result = new SimpleTopologyView(view.id);
+        final Iterator<InstanceDescription> it = view.getInstances().iterator();
+        Map<String,SimpleClusterView> clusters = new HashMap<String, SimpleClusterView>();
+        while(it.hasNext()) {
+            InstanceDescription id = it.next();
+            SimpleInstanceDescription clone = clone(id);
+            String clusterId = id.getClusterView().getId();
+            SimpleClusterView cluster = clusters.get(clusterId);
+            if (cluster==null) {
+                cluster = new SimpleClusterView(clusterId);
+                clusters.put(clusterId, cluster);
+            }
+            clone.setClusterView(cluster);
+            result.addInstance(clone);
+        }
+        return result;
+    }
+    
+    private static SimpleInstanceDescription clone(InstanceDescription id) {
+        return new SimpleInstanceDescription(id.isLeader(), id.isLocal(), id.getSlingId(), id.getProperties());
+    }
+
+    public SimpleTopologyView clone() {
+        return SimpleTopologyView.clone(this);
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
new file mode 100644
index 0000000..057aeae
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestHelper.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import java.util.Random;
+import java.util.UUID;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestHelper {
+
+    private static final Logger logger = LoggerFactory.getLogger(TestHelper.class);
+
+    public static void assertEvents(ViewStateManagerImpl mgr, Listener listener, TopologyEvent... events) {
+        waitForAsyncEvents(mgr);
+        assertEquals(events.length, listener.countEvents());
+        for (int i = 0; i < events.length; i++) {
+            TopologyEvent e = events[i];
+            assertEquals(e.getType(), listener.getEvents().get(i).getType());
+            switch(e.getType()) {
+            case TOPOLOGY_INIT: {
+                assertNull(listener.getEvents().get(i).getOldView());
+                assertEquals(e.getNewView(), listener.getEvents().get(i).getNewView());
+                break;
+            }
+            case TOPOLOGY_CHANGING: {
+                assertEquals(e.getOldView(), listener.getEvents().get(i).getOldView());
+                assertNull(listener.getEvents().get(i).getNewView());
+                break;
+            }
+            case PROPERTIES_CHANGED:
+            case TOPOLOGY_CHANGED: {
+                assertEquals(e.getOldView(), listener.getEvents().get(i).getOldView());
+                assertEquals(e.getNewView(), listener.getEvents().get(i).getNewView());
+                break;
+            }
+            default: {
+                fail("no other type supported yet");
+            }
+            }
+        }
+        listener.clearEvents();
+    }
+
+    public static void waitForAsyncEvents(ViewStateManagerImpl mgr) {
+        int sleep = 1;
+        while(true) {
+            if (!mgr.getAsyncEventSender().hasInFlightEvent()) {
+                return;
+            }
+            
+            // sleep outside of synchronized to keep test-influence
+            // to a minimum
+            try {
+                Thread.sleep(sleep);
+            } catch (InterruptedException e) {
+                logger.error("waitForFlush: got interrupted: "+e, e);
+            }
+            // minor back-off up until 20ms
+            sleep = Math.min(20, sleep+1);
+        }
+    }
+
+    public static void assertNoEvents(Listener listener) {
+        assertEquals(0, listener.countEvents());
+    }
+
+    /** does couple loops randomly calling handleChanging() (or not) and then handleNewView().
+     * Note: random is passed to allow customizing and not hardcoding this method to a particular random 
+     * @throws InterruptedException **/
+    public static void randomEventLoop(ViewStateManagerImpl mgr, SimpleDiscoveryService sds, int loopSize, int delayInMillis, final Random random, Listener... listeners) throws InterruptedException {
+        for(int i=0; i<loopSize; i++) {
+            final boolean shouldCallChanging = random.nextBoolean();
+            if (shouldCallChanging) {
+                // dont always do a changing
+                logger.debug("randomEventLoop: calling handleChanging...");
+                mgr.handleChanging();
+                // must first wait for async events to have been processed - as otherwise
+                // the 'getLastView()' might not return the correct view
+                logger.debug("randomEventLoop: waiting for async events....");
+                waitForAsyncEvents(mgr);
+                logger.debug("randomEventLoop: asserting CHANGING event was sent...");
+                for(int j=0; j<listeners.length; j++) {
+                    assertEvents(mgr, listeners[j], EventFactory.newChangingEvent(listeners[j].getLastView()));
+                }
+            } else {
+                logger.debug("randomEventLoop: asserting no events...");
+                for(int j=0; j<listeners.length; j++) {
+                    assertNoEvents(listeners[j]);
+                }
+            }
+            final BaseTopologyView view = new SimpleTopologyView().addInstance();
+            BaseTopologyView[] lastViews = new BaseTopologyView[listeners.length];
+            for(int j=0; j<listeners.length; j++) {
+                lastViews[j] = listeners[j].getLastView();
+            }
+            logger.debug("randomEventLoop: calling handleNewView");
+            if (sds!=null) {
+                sds.setTopoology(view);
+            }
+            mgr.handleNewView(view);
+            if (delayInMillis>0) {
+                logger.debug("randomEventLoop: waiting "+delayInMillis+"ms ...");
+                Thread.sleep(delayInMillis);
+                logger.debug("randomEventLoop: waiting "+delayInMillis+"ms done.");
+            }
+            if (!shouldCallChanging) {
+                // in that case I should still get a CHANGING - by contract
+                logger.debug("randomEventLoop: asserting CHANGING, CHANGED events were sent");
+                for(int j=0; j<listeners.length; j++) {
+                    assertEvents(mgr, listeners[j], EventFactory.newChangingEvent(lastViews[j]), EventFactory.newChangedEvent(lastViews[j], view));
+                }
+            } else {
+                logger.debug("randomEventLoop: asserting CHANGED event was sent");
+                for(int j=0; j<listeners.length; j++) {
+                    assertEvents(mgr, listeners[j], EventFactory.newChangedEvent(lastViews[j], view));
+                }
+            }
+        }
+    }
+
+    public static SimpleTopologyView newView(boolean isCurrent, String leaderId, String localId, String... slingIds) {
+        return newView(UUID.randomUUID().toString(), UUID.randomUUID().toString(), isCurrent, leaderId, localId, slingIds);
+    }
+
+    public static SimpleTopologyView newView(String syncId, String clusterId, boolean isCurrent, String leaderId, String localId, String... slingIds) {
+        SimpleTopologyView topology = new SimpleTopologyView(syncId);
+        SimpleClusterView cluster = new SimpleClusterView(clusterId);
+        for (String slingId : slingIds) {
+            SimpleInstanceDescription id = new SimpleInstanceDescription(
+                    slingId.equals(leaderId), slingId.equals(localId), slingId, null);
+            id.setClusterView(cluster);
+            cluster.addInstanceDescription(id);
+            topology.addInstanceDescription(id);
+        }
+        if (!isCurrent) {
+            topology.setNotCurrent();
+        }
+        return topology;
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
new file mode 100644
index 0000000..f514676
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestMinEventDelayHandler.java
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Random;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestMinEventDelayHandler {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private ViewStateManagerImpl mgr;
+    
+    private Random defaultRandom;
+    
+    private SimpleDiscoveryService sds;
+
+    private Level logLevel;
+
+    private SimpleScheduler scheduler;
+
+    @Before
+    public void setup() throws Exception {
+        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+            
+            public void sync(BaseTopologyView view, Runnable callback) {
+                callback.run();
+            }
+        });
+        defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
+        
+        scheduler = new SimpleScheduler();
+        sds = new SimpleDiscoveryService();
+        mgr.installMinEventDelayHandler(sds, scheduler, 1);
+
+        final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+        logLevel = discoveryLogger.getLevel();
+        discoveryLogger.setLevel(Level.DEBUG);
+    }
+    
+    @After
+    public void teardown() throws Exception {
+        mgr = null;
+        defaultRandom= null;
+        final org.apache.log4j.Logger discoveryLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery");
+        discoveryLogger.setLevel(logLevel);
+    }
+    
+    private void assertNoEvents(Listener listener) {
+        assertEquals(0, listener.countEvents());
+    }
+
+    @Test
+    public void testNormalDelaying() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        logger.info("testNormalDelaying: calling handleActivated...");
+        mgr.handleActivated();
+        assertNoEvents(listener); // paranoia
+        // then bind
+        logger.info("testNormalDelaying: calling bind...");
+        mgr.bind(listener);
+        assertNoEvents(listener); // there was no changing or changed yet
+        logger.info("testNormalDelaying: calling handleChanging...");
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        logger.info("testNormalDelaying: calling handleNewView...");
+        mgr.handleNewView(view);
+        TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
+        for(int i=0; i<7; i++) {
+            logger.info("testNormalDelaying: calling randomEventLoop...");
+            TestHelper.randomEventLoop(mgr, sds, 4, 1500, defaultRandom, listener);
+            Thread.sleep(1000);
+        }
+    }
+
+    @Test
+    public void testFailedDelaying() throws Exception {
+        scheduler.failMode();
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        assertNoEvents(listener); // paranoia
+        // then bind
+        mgr.bind(listener);
+        assertNoEvents(listener); // there was no changing or changed yet
+        mgr.handleChanging();
+        assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        TestHelper.assertEvents(mgr, listener, EventFactory.newInitEvent(view));
+        for(int i=0; i<7; i++) {
+            TestHelper.randomEventLoop(mgr, sds, 100, -1, defaultRandom, listener);
+            Thread.sleep(1000);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
new file mode 100644
index 0000000..48f88fb
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/impl/TestViewStateManager.java
@@ -0,0 +1,590 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Random;
+import java.util.UUID;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.LogManager;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.commons.providers.BaseTopologyView;
+import org.apache.sling.discovery.commons.providers.EventFactory;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TestViewStateManager {
+
+    private static final Logger logger = LoggerFactory.getLogger(TestViewStateManager.class);
+
+    private class ConsistencyServiceWithSemaphore implements ConsistencyService {
+
+        private final Semaphore semaphore;
+        private final Lock lock;
+
+        public ConsistencyServiceWithSemaphore(Lock lock, Semaphore semaphore) {
+            this.lock = lock;
+            this.semaphore = semaphore;
+        }
+        
+        public void sync(BaseTopologyView view, Runnable callback) {
+            try {
+                lock.unlock();
+                try{
+                    semaphore.acquire();
+                } finally {
+                    lock.lock();
+                }
+                callback.run();
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        
+    }
+    
+    private ViewStateManagerImpl mgr;
+    
+    private Random defaultRandom;
+
+    @Before
+    public void setup() throws Exception {
+        mgr = new ViewStateManagerImpl(new ReentrantLock(), new ConsistencyService() {
+            
+            public void sync(BaseTopologyView view, Runnable callback) {
+                callback.run();
+            }
+        });
+        defaultRandom = new Random(1234123412); // I want randomness yes, but deterministic, for some methods at least
+    }
+    
+    @After
+    public void teardown() throws Exception {
+        mgr = null;
+        defaultRandom= null;
+    }
+    
+    void assertEvents(Listener listener, TopologyEvent... events) {
+        TestHelper.assertEvents(mgr, listener, events);
+    }
+    
+    /** does couple loops randomly calling handleChanging() (or not) and then handleNewView().
+     * Note: random is passed to allow customizing and not hardcoding this method to a particular random 
+     * @throws InterruptedException **/
+    private void randomEventLoop(final Random random, Listener... listeners) throws InterruptedException {
+        TestHelper.randomEventLoop(mgr, null, 100, -1, random, listeners);
+    }
+    
+    @Test
+    public void testDuplicateListeners() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        mgr.bind(listener); // we should be generous and allow duplicate registration
+        assertTrue(mgr.unbind(listener));
+        assertFalse(mgr.unbind(listener));
+        
+        mgr.handleActivated();
+        assertFalse(mgr.unbind(listener));
+        mgr.bind(listener);
+        mgr.bind(listener); // we should be generous and allow duplicate registration
+        assertTrue(mgr.unbind(listener));
+        assertFalse(mgr.unbind(listener));
+    }
+    
+    @Test
+    public void testBindActivateChangingChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangingActivateChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangingChangedActivate() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangingChangedChangingActivate() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, EventFactory.newInitEvent(view2));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindChangedChangingActivate() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, EventFactory.newInitEvent(view2));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testActivateBindChangingChanged() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener); // paranoia
+        // then bind
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener); // there was no changing or changed yet
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+
+    @Test
+    public void testActivateChangingBindChanged() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener); // paranoia
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener); // no listener yet
+        // then bind
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener); // no changed event yet
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+
+    @Test
+    public void testActivateChangingChangedBind() throws Exception {
+        final Listener listener = new Listener();
+        // first activate
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener); // paranoia
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener); // no listener yet
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        TestHelper.assertNoEvents(listener); // no listener yet
+        // then bind
+        mgr.bind(listener);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindActivateBindChangingChanged() throws Exception {
+        final Listener listener1 = new Listener();
+        final Listener listener2 = new Listener();
+        
+        mgr.bind(listener1);
+        TestHelper.assertNoEvents(listener1);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener1);
+        mgr.bind(listener2);
+        TestHelper.assertNoEvents(listener1);
+        TestHelper.assertNoEvents(listener2);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener1);
+        TestHelper.assertNoEvents(listener2);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener1, EventFactory.newInitEvent(view));
+        assertEvents(listener2, EventFactory.newInitEvent(view));
+        
+        randomEventLoop(defaultRandom, listener1, listener2);
+    }
+
+    @Test
+    public void testBindActivateChangingBindChanged() throws Exception {
+        final Listener listener1 = new Listener();
+        final Listener listener2 = new Listener();
+        
+        mgr.bind(listener1);
+        TestHelper.assertNoEvents(listener1);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener1);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener1);
+        mgr.bind(listener2);
+        TestHelper.assertNoEvents(listener1);
+        TestHelper.assertNoEvents(listener2);
+        final BaseTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener1, EventFactory.newInitEvent(view));
+        assertEvents(listener2, EventFactory.newInitEvent(view));
+
+        randomEventLoop(defaultRandom, listener1, listener2);
+    }
+    
+    @Test
+    public void testActivateBindChangingDuplicateHandleNewView() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        mgr.bind(listener);
+        mgr.handleChanging();
+        final SimpleTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        mgr.handleNewView(SimpleTopologyView.clone(view));
+        TestHelper.assertNoEvents(listener);
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testActivateBindChangingChangedBindDuplicateHandleNewView() throws Exception {
+        final Listener listener1 = new Listener();
+        mgr.handleActivated();
+        mgr.bind(listener1);
+        mgr.handleChanging();
+        final SimpleTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        assertEvents(listener1, EventFactory.newInitEvent(view));
+        
+        final Listener listener2 = new Listener();
+        mgr.bind(listener2);
+        mgr.handleNewView(SimpleTopologyView.clone(view));
+        TestHelper.assertNoEvents(listener1);
+        assertEvents(listener2, EventFactory.newInitEvent(view));
+        randomEventLoop(defaultRandom, listener1, listener2);
+    }
+    
+    @Test
+    public void testActivateChangedBindDuplicateHandleNewView() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final SimpleTopologyView view = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view);
+        TestHelper.assertNoEvents(listener);
+        mgr.bind(listener);
+        assertEvents(listener, EventFactory.newInitEvent(view));
+        mgr.handleNewView(SimpleTopologyView.clone(view));
+        TestHelper.assertNoEvents(listener);
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindActivateChangedChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, EventFactory.newInitEvent(view1));
+        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, EventFactory.newChangingEvent(view1), EventFactory.newChangedEvent(view1, view2));
+        randomEventLoop(defaultRandom, listener);
+    }
+    
+    @Test
+    public void testBindActivateChangedDeactivateChangingActivateChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, EventFactory.newInitEvent(view1));
+        mgr.handleDeactivated();
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        mgr.bind(listener); // need to bind again after deactivate
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, EventFactory.newInitEvent(view2));
+    }
+
+    @Test
+    public void testBindActivateChangedDeactivateChangedActivateChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, EventFactory.newInitEvent(view1));
+        mgr.handleDeactivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view2);
+        TestHelper.assertNoEvents(listener);
+        mgr.bind(listener); // need to bind again after deactivate
+        mgr.handleActivated();
+        assertEvents(listener, EventFactory.newInitEvent(view2));
+        final BaseTopologyView view3 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view3);
+        assertEvents(listener, EventFactory.newChangingEvent(view2), EventFactory.newChangedEvent(view2, view3));
+    }
+
+    @Test
+    public void testBindActivateChangedChangingDeactivateActivateChangingChanged() throws Exception {
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view1 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view1);
+        assertEvents(listener, EventFactory.newInitEvent(view1));
+        mgr.handleChanging();
+        assertEvents(listener, EventFactory.newChangingEvent(view1));
+        mgr.handleDeactivated();
+        TestHelper.assertNoEvents(listener);
+        mgr.bind(listener); // need to bind again after deactivate
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        mgr.handleChanging();
+        TestHelper.assertNoEvents(listener);
+        final BaseTopologyView view2 = new SimpleTopologyView().addInstance();
+        mgr.handleNewView(view2);
+        assertEvents(listener, EventFactory.newInitEvent(view2));
+    }
+    
+    @Test
+    public void testConsistencyService_noConcurrency() throws Exception {
+        final org.apache.log4j.Logger commonsLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery.commons.providers");
+        final org.apache.log4j.Level logLevel = commonsLogger.getLevel();
+        commonsLogger.setLevel(Level.INFO); // change here to DEBUG in case of issues with this test
+        final Semaphore serviceSemaphore = new Semaphore(0);
+        final ReentrantLock lock = new ReentrantLock();
+        final ConsistencyServiceWithSemaphore cs = new ConsistencyServiceWithSemaphore(lock, serviceSemaphore );
+        mgr = new ViewStateManagerImpl(lock, cs);
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final String slingId1 = UUID.randomUUID().toString();
+        final String slingId2 = UUID.randomUUID().toString();
+        final String clusterId = UUID.randomUUID().toString();
+        final SimpleClusterView cluster = new SimpleClusterView(clusterId);
+        final SimpleTopologyView view1 = new SimpleTopologyView()
+                .addInstance(slingId1, cluster, true, true)
+                .addInstance(slingId2, cluster, false, false);
+        async(new Runnable() {
+
+            public void run() {
+                mgr.handleNewView(view1);
+            }
+            
+        });
+        Thread.sleep(1000);
+        TestHelper.assertNoEvents(listener);
+        serviceSemaphore.release(1);
+        Thread.sleep(1000);
+        assertEvents(listener, EventFactory.newInitEvent(view1));
+        mgr.handleChanging();
+        assertEvents(listener, EventFactory.newChangingEvent(view1));
+        final SimpleTopologyView view2 = SimpleTopologyView.clone(view1).removeInstance(slingId2);
+        async(new Runnable() {
+
+            public void run() {
+                mgr.handleNewView(view2);
+            }
+            
+        });
+        logger.debug("run: waiting for 1sec");
+        Thread.sleep(1000);
+        logger.debug("run: asserting no events");
+        TestHelper.assertNoEvents(listener);
+        logger.debug("run: releasing consistencyService");
+        serviceSemaphore.release(1);
+        logger.debug("run: waiting 1sec");
+        Thread.sleep(1000);
+        logger.debug("run: asserting 1 event");
+        assertEvents(listener, EventFactory.newChangedEvent(view1, view2));
+        commonsLogger.setLevel(Level.INFO); // back to default
+    }
+
+    private void async(Runnable runnable) {
+        new Thread(runnable).start();
+    }
+
+    @Test
+    public void testConsistencyService_withConcurrency() throws Exception {
+        final org.apache.log4j.Logger commonsLogger = LogManager.getRootLogger().getLogger("org.apache.sling.discovery.commons.providers");
+        final org.apache.log4j.Level logLevel = commonsLogger.getLevel();
+        commonsLogger.setLevel(Level.INFO); // change here to DEBUG in case of issues with this test
+        final Semaphore serviceSemaphore = new Semaphore(0);
+        final Semaphore testSemaphore = new Semaphore(0);
+        final ReentrantLock lock = new ReentrantLock();
+        final ConsistencyServiceWithSemaphore cs = new ConsistencyServiceWithSemaphore(lock, serviceSemaphore );
+        mgr = new ViewStateManagerImpl(lock, cs);
+        final Listener listener = new Listener();
+        mgr.bind(listener);
+        TestHelper.assertNoEvents(listener);
+        mgr.handleActivated();
+        TestHelper.assertNoEvents(listener);
+        final String slingId1 = UUID.randomUUID().toString();
+        final String slingId2 = UUID.randomUUID().toString();
+        final String slingId3 = UUID.randomUUID().toString();
+        final String clusterId = UUID.randomUUID().toString();
+        final SimpleClusterView cluster = new SimpleClusterView(clusterId);
+        final SimpleTopologyView view1 = new SimpleTopologyView()
+                .addInstance(slingId1, cluster, true, true)
+                .addInstance(slingId2, cluster, false, false)
+                .addInstance(slingId3, cluster, false, false);
+        final SimpleTopologyView view2 = SimpleTopologyView.clone(view1).removeInstance(slingId2);
+        final SimpleTopologyView view3 = SimpleTopologyView.clone(view1).removeInstance(slingId2).removeInstance(slingId3);
+        async(new Runnable() {
+
+            public void run() {
+                mgr.handleNewView(view1);
+            }
+            
+        });
+        Thread.sleep(1000);
+        TestHelper.assertNoEvents(listener);
+        serviceSemaphore.release(1); // release the first one only
+        Thread.sleep(1000);
+        assertEvents(listener, EventFactory.newInitEvent(view1));
+        mgr.handleChanging();
+        assertEvents(listener, EventFactory.newChangingEvent(view1));
+        async(new Runnable() {
+
+            public void run() {
+                mgr.handleNewView(view2);
+            }
+            
+        });
+        logger.debug("run: waiting 1sec");
+        Thread.sleep(1000);
+        logger.debug("run: asserting no events");
+        TestHelper.assertNoEvents(listener);
+        assertFalse("should not be locked", lock.isLocked());
+
+        logger.debug("run: issuing a second event");
+        // before releasing, issue another event, lets do a combination of changing/changed
+        async(new Runnable() {
+
+            public void run() {
+                logger.debug("run2: calling handleChanging...");
+                mgr.handleChanging();
+                try {
+                    logger.debug("run2: done with handleChanging, acquiring testSemaphore...");
+                    testSemaphore.acquire();
+                    logger.debug("run2: calling handleNewView...");
+                    mgr.handleNewView(view3);
+                    logger.debug("run2: done with handleNewView...");
+                } catch (InterruptedException e) {
+                    // fail
+                    logger.error("interrupted: "+e, e);
+                }
+            }
+            
+        });
+        logger.debug("run: waiting 1sec");
+        Thread.sleep(1000);
+        assertEquals("should be acquiring (by thread2)", 1, testSemaphore.getQueueLength());
+        // releasing the testSemaphore
+        testSemaphore.release();
+        logger.debug("run: waiting 1sec");
+        Thread.sleep(1000);
+        assertEquals("should have both threads now waiting", 2, serviceSemaphore.getQueueLength());
+        logger.debug("run: releasing consistencyService");
+        serviceSemaphore.release(1); // release the first one only
+        logger.debug("run: waiting 1sec");
+        Thread.sleep(1000);
+        assertFalse("should not be locked", lock.isLocked());
+        TestHelper.assertNoEvents(listener); // this should not have triggered any event 
+        serviceSemaphore.release(1); // then release the 2nd one
+        logger.debug("run: waiting 1sec");
+        Thread.sleep(1000);
+        logger.debug("run: asserting 1 event");
+        final TopologyEvent changedEvent = EventFactory.newChangedEvent(view1, view3);
+        assertEvents(listener, changedEvent);
+        commonsLogger.setLevel(Level.INFO); // back to default
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoLite.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoLite.java
new file mode 100644
index 0000000..fcf361f
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/DiscoLite.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.util.Arrays;
+
+import org.apache.sling.commons.json.JSONArray;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.commons.json.JSONObject;
+
+// {"seq":8,"final":true,"id":"aae34e9a-b08d-409e-be10-9ff4106e5387","me":4,"active":[4],"deactivating":[],"inactive":[1,2,3]}
+public class DiscoLite {
+    
+    private int seqNum;
+    private int me;
+    private Integer[] activeIds = new Integer[0];
+    private Integer[] inactiveIds = new Integer[0];
+    private Integer[] deactivating = new Integer[0];
+
+    public DiscoLite() {
+        // nothing here
+    }
+    
+    public DiscoLite seq(int seqNum) {
+        this.seqNum = seqNum;
+        return this;
+    }
+
+    public DiscoLite me(int me) {
+        this.me = me;
+        return this;
+    }
+
+    public DiscoLite activeIds(Integer... activeIds) {
+        this.activeIds = activeIds;
+        return this;
+    }
+
+    public DiscoLite inactiveIds(Integer... inactiveIds) {
+        this.inactiveIds = inactiveIds;
+        return this;
+    }
+
+    public DiscoLite deactivatingIds(Integer... deactivating) {
+        this.deactivating = deactivating;
+        return this;
+    }
+    
+    public String asJson() throws JSONException {
+        JSONObject json = new JSONObject();
+        json.put("me", me);
+        json.put("seq", seqNum);
+        json.put("active", new JSONArray(Arrays.asList(activeIds)));
+        json.put("inactive", new JSONArray(Arrays.asList(inactiveIds)));
+        json.put("deactivating", new JSONArray(Arrays.asList(deactivating)));
+        return json.toString();
+    }
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockFactory.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockFactory.java
new file mode 100644
index 0000000..895ebbe
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockFactory.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.testing.jcr.RepositoryProvider;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.hamcrest.Description;
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.api.Action;
+import org.jmock.api.Invocation;
+import org.jmock.integration.junit4.JUnit4Mockery;
+
+public class MockFactory {
+
+    public final Mockery context = new JUnit4Mockery();
+
+    public static ResourceResolverFactory mockResourceResolverFactory()
+            throws Exception {
+    	return mockResourceResolverFactory(null);
+    }
+
+    public static ResourceResolverFactory mockResourceResolverFactory(final SlingRepository repositoryOrNull)
+            throws Exception {
+        Mockery context = new JUnit4Mockery();
+
+        final ResourceResolverFactory resourceResolverFactory = context
+                .mock(ResourceResolverFactory.class);
+        // final ResourceResolver resourceResolver = new MockResourceResolver();
+        // final ResourceResolver resourceResolver = new
+        // MockedResourceResolver();
+
+        context.checking(new Expectations() {
+            {
+                allowing(resourceResolverFactory)
+                        .getAdministrativeResourceResolver(null);
+                will(new Action() {
+
+                    public Object invoke(Invocation invocation)
+                            throws Throwable {
+                    	return new MockedResourceResolver(repositoryOrNull);
+                    }
+
+                    public void describeTo(Description arg0) {
+                        arg0.appendText("whateva - im going to create a new mockedresourceresolver");
+                    }
+                });
+            }
+        });
+        return resourceResolverFactory;
+    }
+
+    public static void resetRepo() throws Exception {
+        Session l = RepositoryProvider.instance().getRepository()
+                .loginAdministrative(null);
+        try {
+            l.removeItem("/var");
+            l.save();
+            l.logout();
+        } catch (Exception e) {
+            l.refresh(false);
+            l.logout();
+        }
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResource.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResource.java
new file mode 100644
index 0000000..5063bbe
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResource.java
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.ModifiableValueMap;
+import org.apache.sling.api.resource.SyntheticResource;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.api.wrappers.ValueMapDecorator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class MockedResource extends SyntheticResource {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    private final MockedResourceResolver mockedResourceResolver;
+    private Session session;
+
+    public MockedResource(MockedResourceResolver resourceResolver, String path,
+            String resourceType) {
+        super(resourceResolver, path, resourceType);
+        mockedResourceResolver = resourceResolver;
+
+        resourceResolver.register(this);
+    }
+
+    private Session getSession() {
+        synchronized (this) {
+            if (session == null) {
+                try {
+                    session = mockedResourceResolver.getSession();
+                } catch (RepositoryException e) {
+                    throw new RuntimeException("RepositoryException: " + e, e);
+                }
+            }
+            return session;
+        }
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+//        close();
+        super.finalize();
+    }
+
+    public void close() {
+        synchronized (this) {
+            if (session != null) {
+                if (session.isLive()) {
+                    session.logout();
+                }
+                session = null;
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if (type.equals(Node.class)) {
+            try {
+                return (AdapterType) getSession().getNode(getPath());
+            } catch (Exception e) {
+                logger.error("Exception occurred: "+e, e);
+                throw new RuntimeException("Exception occurred: " + e, e);
+            }
+        } else if (type.equals(ValueMap.class)) {
+            try {
+                Session session = getSession();
+                Node node = session.getNode(getPath());
+                HashMap<String, Object> map = new HashMap<String, Object>();
+
+                PropertyIterator properties = node.getProperties();
+                while (properties.hasNext()) {
+                    Property p = properties.nextProperty();
+                    if (p.getType() == PropertyType.BOOLEAN) {
+                        map.put(p.getName(), p.getBoolean());
+                    } else if (p.getType() == PropertyType.STRING) {
+                        map.put(p.getName(), p.getString());
+                    } else if (p.getType() == PropertyType.DATE) {
+                        map.put(p.getName(), p.getDate().getTime());
+                    } else if (p.getType() == PropertyType.NAME) {
+                        map.put(p.getName(), p.getName());
+                    } else if (p.getType() == PropertyType.LONG) {
+                        map.put(p.getName(), p.getLong());
+                    } else {
+                        throw new RuntimeException(
+                                "Unsupported property type: " + p.getType());
+                    }
+                }
+                ValueMap valueMap = new ValueMapDecorator(map);
+                return (AdapterType) valueMap;
+            } catch (Exception e) {
+                e.printStackTrace();
+                return null;
+            }
+        } else if (type.equals(ModifiableValueMap.class)) {
+            return (AdapterType) new ModifiableValueMap() {
+                
+                public Collection<Object> values() {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public int size() {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public Object remove(Object arg0) {
+                    Session session = getSession();
+                    try{
+                        final Node node = session.getNode(getPath());
+                        final Property p = node.getProperty(String.valueOf(arg0));
+                        if (p!=null) {
+                        	p.remove();
+                        }
+                        // this is not according to the spec - but OK for tests since
+                        // the return value is never used
+                        return null;
+                    } catch(PathNotFoundException pnfe) {
+                    	// perfectly fine
+                    	return null;
+                    } catch(RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                
+                public void putAll(Map<? extends String, ? extends Object> arg0) {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public Object put(String arg0, Object arg1) {
+                    Session session = getSession();
+                    try{
+                        final Node node = session.getNode(getPath());
+                        Object result = null;
+                        if (node.hasProperty(arg0)) {
+                            final Property previous = node.getProperty(arg0);
+                            if (previous==null) {
+                                // null
+                            } else if (previous.getType() == PropertyType.STRING) {
+                                result = previous.getString();
+                            } else if (previous.getType() == PropertyType.DATE) {
+                                result = previous.getDate();
+                            } else if (previous.getType() == PropertyType.BOOLEAN) {
+                                result = previous.getBoolean();
+                            } else {
+                                throw new UnsupportedOperationException();
+                            }
+                        }
+                        if (arg1 instanceof String) {
+                            node.setProperty(arg0, (String)arg1);
+                        } else if (arg1 instanceof Calendar) {
+                            node.setProperty(arg0, (Calendar)arg1);
+                        } else if (arg1 instanceof Boolean) {
+                            node.setProperty(arg0, (Boolean)arg1);
+                        } else if (arg1 instanceof Date) {
+                            final Calendar c = Calendar.getInstance();
+                            c.setTime((Date)arg1);
+                            node.setProperty(arg0, c);
+                        } else if (arg1 instanceof Number) {
+                            Number n = (Number)arg1;
+                            node.setProperty(arg0, n.longValue());
+                        } else {
+                            throw new UnsupportedOperationException();
+                        }
+                        return result;
+                    } catch(RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                
+                public Set<String> keySet() {
+                    Session session = getSession();
+                    try {
+                        final Node node = session.getNode(getPath());
+                        final PropertyIterator pi = node.getProperties();
+                        final Set<String> result = new HashSet<String>();
+                        while(pi.hasNext()) {
+                            final Property p = pi.nextProperty();
+                            result.add(p.getName());
+                        }
+                        return result;
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                
+                public boolean isEmpty() {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public Object get(Object arg0) {
+                    Session session = getSession();
+                    try{
+                        final Node node = session.getNode(getPath());
+                        final String key = String.valueOf(arg0);
+                        if (node.hasProperty(key)) {
+                            return node.getProperty(key);
+                        } else {
+                            return null;
+                        }
+                    } catch(RepositoryException re) {
+                        throw new RuntimeException(re);
+                    }
+                }
+                
+                public Set<Entry<String, Object>> entrySet() {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public boolean containsValue(Object arg0) {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public boolean containsKey(Object arg0) {
+                    Session session = getSession();
+                    try{
+                        final Node node = session.getNode(getPath());
+                        return node.hasProperty(String.valueOf(arg0));
+                    } catch(RepositoryException re) {
+                        throw new RuntimeException(re);
+                    }
+                }
+                
+                public void clear() {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public <T> T get(String name, T defaultValue) {
+                    throw new UnsupportedOperationException();
+                }
+                
+                public <T> T get(String name, Class<T> type) {
+                    Session session = getSession();
+                    try{
+                        final Node node = session.getNode(getPath());
+                        if (node==null) {
+                        	return null;
+                        }
+                        if (!node.hasProperty(name)) {
+                            return null;
+                        }
+                        Property p = node.getProperty(name);
+                        if (p==null) {
+                        	return null;
+                        }
+                        if (type.equals(Calendar.class)) {
+                        	return (T) p.getDate();
+                        } else if (type.equals(String.class)) {
+                        	return (T) p.getString();
+                        } else {
+                            throw new UnsupportedOperationException();
+                        }
+                    } catch(RepositoryException e) {
+                    	throw new RuntimeException(e);
+                    }
+                }
+            };
+        } else {
+            return super.adaptTo(type);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResourceResolver.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResourceResolver.java
new file mode 100644
index 0000000..0350fd2
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/MockedResourceResolver.java
@@ -0,0 +1,329 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.io.IOException;
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.jcr.Credentials;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.testing.jcr.RepositoryProvider;
+import org.apache.sling.commons.testing.jcr.RepositoryUtil;
+import org.apache.sling.jcr.api.SlingRepository;
+
+public class MockedResourceResolver implements ResourceResolver {
+
+	private final SlingRepository repository;
+
+	private Session session;
+
+    private List<MockedResource> resources = new LinkedList<MockedResource>();
+
+    public MockedResourceResolver() throws RepositoryException {
+    	this(null);
+    }
+
+    public MockedResourceResolver(SlingRepository repositoryOrNull) throws RepositoryException {
+    	if (repositoryOrNull==null) {
+    		this.repository = RepositoryProvider.instance().getRepository();
+    		Session adminSession = null;
+    		try {
+    		    adminSession = this.repository.loginAdministrative(null);
+                RepositoryUtil.registerSlingNodeTypes(adminSession);
+    		} catch ( final IOException ioe ) {
+    		    throw new RepositoryException(ioe);
+    		} finally {
+    		    if ( adminSession != null ) {
+    		        adminSession.logout();
+    		    }
+    		}
+    	} else {
+    		this.repository = repositoryOrNull;
+    	}
+    }
+
+    public Session getSession() throws RepositoryException {
+        synchronized (this) {
+            if (session != null) {
+                return session;
+            }
+            session = createSession();
+            return session;
+        }
+    }
+
+    private Repository getRepository() {
+    	return repository;
+    }
+
+    private Session createSession() throws RepositoryException {
+        final Credentials credentials = new SimpleCredentials("admin",
+                "admin".toCharArray());
+        return repository.login(credentials, "default");
+    }
+
+
+    @SuppressWarnings("unchecked")
+    public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
+        if (type.equals(Session.class)) {
+            try {
+                return (AdapterType) getSession();
+            } catch (RepositoryException e) {
+                throw new RuntimeException("RepositoryException: " + e, e);
+            }
+        } else if (type.equals(Repository.class)) {
+        	return (AdapterType) getRepository();
+        }
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Resource resolve(HttpServletRequest request, String absPath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Resource resolve(String absPath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Deprecated
+    public Resource resolve(HttpServletRequest request) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public String map(String resourcePath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public String map(HttpServletRequest request, String resourcePath) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Resource getResource(String path) {
+        Session session;
+        try {
+            session = getSession();
+            session.getNode(path);
+        } catch (PathNotFoundException e) {
+            return null;
+        } catch (RepositoryException e) {
+            throw new RuntimeException("RepositoryException: " + e, e);
+        }
+        return new MockedResource(this, path, "nt:unstructured");
+    }
+
+    public Resource getResource(Resource base, String path) {
+        if (base.getPath().equals("/")) {
+            return getResource("/" + path);
+        } else {
+            return getResource(base.getPath() + "/" + path);
+        }
+    }
+
+    public String[] getSearchPath() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Iterator<Resource> listChildren(Resource parent) {
+        try {
+            Node node = parent.adaptTo(Node.class);
+            final NodeIterator nodes = node.getNodes();
+            return new Iterator<Resource>() {
+
+                public void remove() {
+                    throw new UnsupportedOperationException();
+                }
+
+                public Resource next() {
+                    Node next = nodes.nextNode();
+                    try {
+                        return new MockedResource(MockedResourceResolver.this,
+                                next.getPath(), "nt:unstructured");
+                    } catch (RepositoryException e) {
+                        throw new RuntimeException("RepositoryException: " + e,
+                                e);
+                    }
+                }
+
+                public boolean hasNext() {
+                    return nodes.hasNext();
+                }
+            };
+        } catch (RepositoryException e) {
+            throw new RuntimeException("RepositoryException: " + e, e);
+        }
+    }
+
+    public Iterable<Resource> getChildren(Resource parent) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Iterator<Resource> findResources(String query, String language) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Iterator<Map<String, Object>> queryResources(String query,
+            String language) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public ResourceResolver clone(Map<String, Object> authenticationInfo)
+            throws LoginException {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public boolean isLive() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public void close() {
+        Iterator<MockedResource> it = resources.iterator();
+        while (it.hasNext()) {
+            MockedResource r = it.next();
+            r.close();
+        }
+        if (session != null) {
+            if (session.isLive()) {
+                session.logout();
+            }
+            session = null;
+        }
+    }
+
+    public void register(MockedResource mockedResource) {
+        resources.add(mockedResource);
+    }
+
+    public String getUserID() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Iterator<String> getAttributeNames() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public Object getAttribute(String name) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public void delete(Resource resource) throws PersistenceException {
+        if (resources.contains(resource)) {
+            resources.remove(resource);
+            Node node = resource.adaptTo(Node.class);
+            try {
+                node.remove();
+            } catch (RepositoryException e) {
+                throw new PersistenceException("RepositoryException: "+e, e);
+            }
+        } else {
+            throw new UnsupportedOperationException("Not implemented");
+        }
+    }
+
+    public Resource create(Resource parent, String name,
+            Map<String, Object> properties) throws PersistenceException {
+        final Node parentNode = parent.adaptTo(Node.class);
+        try {
+            final Node child;
+            if (properties!=null && properties.containsKey("jcr:primaryType")) {
+                child = parentNode.addNode(name, (String) properties.get("jcr:primaryType"));
+            } else {
+                child = parentNode.addNode(name);
+            }
+            if (properties!=null) {
+                final Iterator<Entry<String, Object>> it = properties.entrySet().iterator();
+                while(it.hasNext()) {
+                    final Entry<String, Object> entry = it.next();
+                    if (entry.getKey().equals("jcr:primaryType")) {
+                        continue;
+                    }
+                    if (entry.getValue() instanceof String) {
+                        child.setProperty(entry.getKey(), (String)entry.getValue());
+                    } else if (entry.getValue() instanceof Boolean) {
+                        child.setProperty(entry.getKey(), (Boolean)entry.getValue());
+                    } else if (entry.getValue() instanceof Calendar) {
+                        child.setProperty(entry.getKey(), (Calendar)entry.getValue());
+                    } else {
+                        throw new UnsupportedOperationException("Not implemented (entry.getValue(): "+entry.getValue()+")");
+                    }
+                }
+            }
+            return getResource(parent, name);
+        } catch (RepositoryException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void revert() {
+        try {
+            this.session.refresh(false);
+        } catch (final RepositoryException re) {
+            throw new RuntimeException("Unable to commit changes.", re);
+        }
+    }
+
+    public void commit() throws PersistenceException {
+        try {
+            this.session.save();
+        } catch (final RepositoryException re) {
+            throw new PersistenceException("Unable to commit changes.", re);
+        }
+    }
+
+    public boolean hasChanges() {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    public String getParentResourceType(Resource resource) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public String getParentResourceType(String resourceType) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    public boolean isResourceType(Resource resource, String resourceType) {
+        // TODO Auto-generated method stub
+        return false;
+    }
+
+    public void refresh() {
+        // TODO Auto-generated method stub
+
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryHelper.java
new file mode 100644
index 0000000..374f0f6
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/RepositoryHelper.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.naming.NamingException;
+
+import org.apache.jackrabbit.commons.JcrUtils;
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.jcr.repository.RepositoryImpl;
+import org.apache.jackrabbit.oak.plugins.commit.ConflictValidatorProvider;
+import org.apache.jackrabbit.oak.plugins.commit.JcrConflictHandler;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.name.NamespaceEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.TypeEditorProvider;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.plugins.version.VersionEditorProvider;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.DefaultWhiteboard;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.testing.jcr.RepositoryUtil;
+import org.apache.sling.commons.testing.jcr.RepositoryUtil.RepositoryWrapper;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RepositoryHelper {
+
+    private static class ShutdownThread extends Thread {
+        
+        private SlingRepository repository;
+        
+        public ShutdownThread(SlingRepository repository) {
+            this.repository = repository;
+        }
+        @Override
+        public void run() {
+            try {
+                stopRepository(repository);
+            } catch(Exception e) {
+                System.out.println("Exception in ShutdownThread:" + e);
+            }
+        }
+        
+    }
+
+    private final static Logger logger = LoggerFactory.getLogger(RepositoryHelper.class);
+    
+    public static void dumpRepo(ResourceResolverFactory resourceResolverFactory) throws Exception {
+        Session session = resourceResolverFactory
+                .getAdministrativeResourceResolver(null).adaptTo(Session.class);
+        logger.info("dumpRepo: ====== START =====");
+        logger.info("dumpRepo: repo = " + session.getRepository());
+
+        dump(session.getRootNode());
+
+        // session.logout();
+        logger.info("dumpRepo: ======  END  =====");
+
+        session.logout();
+    }
+    
+    public static void dump(Node node) throws RepositoryException {
+        if (node.getPath().equals("/jcr:system")
+                || node.getPath().equals("/rep:policy")) {
+            // ignore that one
+            return;
+        }
+        PropertyIterator pi = node.getProperties();
+        StringBuilder sb = new StringBuilder();
+        while (pi.hasNext()) {
+            Property p = pi.nextProperty();
+            sb.append(" ");
+            sb.append(p.getName());
+            sb.append("=");
+            if (p.getType() == PropertyType.BOOLEAN) {
+                sb.append(p.getBoolean());
+            } else if (p.getType() == PropertyType.STRING) {
+                sb.append(p.getString());
+            } else if (p.getType() == PropertyType.DATE) {
+                sb.append(p.getDate().getTime());
+            } else if (p.getType() == PropertyType.LONG) {
+                sb.append(p.getLong());
+            } else {
+                sb.append("<unknown type=" + p.getType() + "/>");
+            }
+        }
+
+        StringBuffer depth = new StringBuffer();
+        for(int i=0; i<node.getDepth(); i++) {
+            depth.append(" ");
+        }
+        logger.info(depth + "/" + node.getName() + " -- " + sb);
+        NodeIterator it = node.getNodes();
+        while (it.hasNext()) {
+            Node child = it.nextNode();
+            dump(child);
+        }
+    }
+
+    static Map<SlingRepository,Session> adminSessions = new HashMap<SlingRepository, Session>();
+    /** from commons.testing.jcr **/
+    public static final String CONFIG_FILE = "jackrabbit-test-config.xml";
+
+    public static SlingRepository newRepository(String homeDir) throws RepositoryException {
+        SlingRepository repository = startRepository(homeDir);
+        Runtime.getRuntime().addShutdownHook(new ShutdownThread(repository));
+        return repository;
+    }
+
+    public static SlingRepository newOakRepository(NodeStore nodeStore) throws RepositoryException {
+            SlingRepository repository = new RepositoryWrapper(createOakRepository(nodeStore));
+    //        Runtime.getRuntime().addShutdownHook(new ShutdownThread(repository));
+            return repository;
+        }
+
+    public static void initSlingNodeTypes(SlingRepository repository) throws RepositoryException {
+        Session adminSession = null;
+        try {
+            adminSession = repository.loginAdministrative(null);
+            RepositoryUtil.registerSlingNodeTypes(adminSession);
+        } catch ( final IOException ioe ) {
+            throw new RepositoryException(ioe);
+        } finally {
+            if ( adminSession != null ) {
+                adminSession.logout();
+            }
+        }
+    }
+
+    /**
+     * Start a new repository
+     * @return 
+     *
+     * @throws RepositoryException when it is not possible to start the
+     *             repository.
+     */
+    private static RepositoryWrapper startRepository(String homeDir) throws RepositoryException {
+        // copy the repository configuration file to the repository HOME_DIR
+        InputStream ins = RepositoryUtil.class.getClassLoader().getResourceAsStream(
+            CONFIG_FILE);
+        if (ins == null) {
+            throw new RepositoryException("Cannot get " + CONFIG_FILE);
+        }
+    
+        File configFile = new File(homeDir, "repository.xml");
+        configFile.getParentFile().mkdirs();
+    
+        FileOutputStream out = null;
+        try {
+            out = new FileOutputStream(configFile);
+            byte[] buf = new byte[1024];
+            int rd;
+            while ((rd = ins.read(buf)) >= 0) {
+                out.write(buf, 0, rd);
+            }
+        } catch (IOException ioe) {
+            throw new RepositoryException("Cannot copy configuration file to "
+                + configFile);
+        } finally {
+            try {
+                ins.close();
+            } catch (IOException ignore) {
+            }
+            if (out != null) {
+                try {
+                    out.close();
+                } catch (IOException ignore) {
+                }
+            }
+        }
+    
+        // somewhat dirty hack to have the derby.log file in a sensible
+        // location, but don't overwrite anything already set
+        if (System.getProperty("derby.stream.error.file") == null) {
+            String derbyLog = homeDir + "/derby.log";
+            System.setProperty("derby.stream.error.file", derbyLog);
+        }
+    
+        final File f = new File(homeDir);
+        RepositoryWrapper repository = new RepositoryWrapper(JcrUtils.getRepository(f.toURI().toString()));
+        Session adminSession = repository.loginAdministrative(null);
+        adminSessions.put(repository, adminSession);
+        return repository;
+    }
+
+    /**
+     * Stop a repository.
+     */
+    public static void stopRepository(SlingRepository repository) throws NamingException {
+        Session adminSession = adminSessions.remove(repository);
+        if ( adminSession != null ) {
+            adminSession.logout();
+        }
+    }
+
+    public static Repository createOakRepository() {
+        return createOakRepository(new MemoryNodeStore());
+    }
+    
+    public static Repository createOakRepository(NodeStore nodeStore) {
+        DefaultWhiteboard whiteboard = new DefaultWhiteboard();
+        final Oak oak = new Oak(nodeStore)
+        .with(new InitialContent())
+//        .with(new ExtraSlingContent())
+
+        .with(JcrConflictHandler.createJcrConflictHandler())
+        .with(new EditorHook(new VersionEditorProvider()))
+
+        .with(new OpenSecurityProvider())
+
+//        .with(new ValidatorProvider() {
+//
+//            @Override
+//            public Validator getRootValidator(
+//                    NodeState before, NodeState after, CommitInfo info) {
+//                HashSet<String> set = newHashSet(after
+//                                .getChildNode(JCR_SYSTEM)
+//                                .getChildNode(REP_NAMESPACES)
+//                                .getChildNode(REP_NSDATA)
+//                                .getStrings(REP_PREFIXES));
+//                set.add("sling");
+//                return new NameValidator(set);
+//            }
+//        })
+        .with(new NamespaceEditorProvider())
+        .with(new TypeEditorProvider())
+//        .with(new RegistrationEditorProvider())
+        .with(new ConflictValidatorProvider())
+
+        // index stuff
+//        .with(indexProvider)
+//        .with(indexEditorProvider)
+        .with("default")//getDefaultWorkspace())
+//        .withAsyncIndexing()
+        .with(whiteboard)
+        ;
+        
+//        if (commitRateLimiter != null) {
+//            oak.with(commitRateLimiter);
+//        }
+
+        final ContentRepository contentRepository = oak.createContentRepository();
+        return new RepositoryImpl(contentRepository, whiteboard, new OpenSecurityProvider(), 1000, null);
+    }
+
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
new file mode 100644
index 0000000..d011d91
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/impl/TestOakSyncTokenConsistencyService.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.commons.providers.spi.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.lang.reflect.Method;
+import java.util.UUID;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.commons.SimpleValueFactory;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.util.GenericDescriptors;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.commons.providers.ViewStateManager;
+import org.apache.sling.discovery.commons.providers.impl.Listener;
+import org.apache.sling.discovery.commons.providers.impl.SimpleTopologyView;
+import org.apache.sling.discovery.commons.providers.impl.TestHelper;
+import org.apache.sling.discovery.commons.providers.impl.ViewStateManagerFactory;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class TestOakSyncTokenConsistencyService {
+
+    ResourceResolverFactory factory1;
+    ResourceResolverFactory factory2;
+    private SlingRepository repository1;
+    private SlingRepository repository2;
+    private MemoryNodeStore memoryNS;
+    
+    @Before
+    public void setup() throws Exception {
+        MockFactory.resetRepo();
+        memoryNS = new MemoryNodeStore();
+        repository1 = RepositoryHelper.newOakRepository(memoryNS);
+//        repository1 = MultipleRepositoriesSupport.newRepository("target/repo1");
+        RepositoryHelper.initSlingNodeTypes(repository1);
+        repository2 = RepositoryHelper.newOakRepository(memoryNS);
+//        repository2 = MultipleRepositoriesSupport.newRepository("target/repo2");
+//        MultipleRepositoriesSupport.initSlingNodeTypes(repository2);
+        factory1 = MockFactory.mockResourceResolverFactory(repository1);
+        factory2 = MockFactory.mockResourceResolverFactory(repository2);
+    }
+    
+    @After
+    public void tearDown() throws Exception {
+        if (repository1!=null) {
+            RepositoryHelper.stopRepository(repository1);
+            repository1 = null;
+        }
+        if (repository2!=null) {
+            RepositoryHelper.stopRepository(repository2);
+            repository2 = null;
+        }
+    }
+    
+    @Test
+    public void testOneNode() throws Exception {
+        String slingId1 = UUID.randomUUID().toString();
+        SimpleTopologyView one = TestHelper.newView(true, slingId1, slingId1, slingId1);
+        Lock lock = new ReentrantLock();
+        OakSyncTokenConsistencyService cs = new OakSyncTokenConsistencyService(factory1, slingId1, -1, -1);
+        ViewStateManager vsm = ViewStateManagerFactory.newViewStateManager(lock, cs);
+        Listener l = new Listener();
+        assertEquals(0, l.countEvents());
+        vsm.bind(l);
+        cs.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        vsm.handleActivated();
+        cs.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        vsm.handleNewView(one);
+        cs.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        cs.triggerBackgroundCheck();
+        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(1).activeIds(1));
+        cs.triggerBackgroundCheck();
+        assertEquals(1, l.countEvents());
+    }
+    
+    @Test
+    public void testTwoNodesOneLeaving() throws Exception {
+        String slingId1 = UUID.randomUUID().toString();
+        String slingId2 = UUID.randomUUID().toString();
+        SimpleTopologyView two1 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
+        Lock lock1 = new ReentrantLock();
+        OakSyncTokenConsistencyService cs1 = new OakSyncTokenConsistencyService(factory1, slingId1, -1, -1);
+        ViewStateManager vsm1 = ViewStateManagerFactory.newViewStateManager(lock1, cs1);
+        Listener l = new Listener();
+        vsm1.bind(l);
+        vsm1.handleActivated();
+        vsm1.handleNewView(two1);
+        cs1.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(1).activeIds(1).deactivatingIds(2));
+        cs1.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(2).activeIds(1));
+        cs1.triggerBackgroundCheck();
+        Lock lock2 = new ReentrantLock();
+        OakSyncTokenConsistencyService cs2 = new OakSyncTokenConsistencyService(factory2, slingId2, -1, -1);
+        ViewStateManager vsm2 = ViewStateManagerFactory.newViewStateManager(lock2, cs2);
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        setDiscoveryLiteDescriptor(factory2, new DiscoLite().me(2).seq(3).activeIds(1, 2));
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(3).activeIds(1, 2));
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
+        assertEquals(0, l.countEvents());
+        vsm2.handleActivated();
+        SimpleTopologyView two2 = TestHelper.newView(two1.getLocalClusterSyncTokenId(), two1.getLocalInstance().getClusterView().getId(), true, slingId1, slingId1, slingId1, slingId2);
+        vsm2.handleNewView(two2);
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
+        assertEquals(1, l.countEvents());
+        SimpleTopologyView oneLeaving = two1.clone();
+        oneLeaving.removeInstance(slingId2);
+        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(1).activeIds(1).deactivatingIds(2));
+        vsm1.handleNewView(oneLeaving);
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
+        assertEquals(2, l.countEvents());
+        setDiscoveryLiteDescriptor(factory1, new DiscoLite().me(1).seq(2).activeIds(1).inactiveIds(2));
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
+        RepositoryHelper.dumpRepo(factory1);
+        assertEquals(3, l.countEvents());
+    }
+    
+    private void setDiscoveryLiteDescriptor(ResourceResolverFactory factory, DiscoLite builder) throws JSONException, Exception {
+        setDescriptor(factory, OakSyncTokenConsistencyService.OAK_DISCOVERYLITE_CLUSTERVIEW, builder.asJson());
+    }
+    
+    private void setDescriptor(ResourceResolverFactory factory, String key,
+            String value) throws Exception {
+        ResourceResolver resourceResolver = factory.getAdministrativeResourceResolver(null);
+        try{
+            Session session = resourceResolver.adaptTo(Session.class);
+            if (session == null) {
+                return;
+            }
+            Repository repo = session.getRepository();
+            
+            //<hack>
+//            Method setDescriptorMethod = repo.getClass().
+//                    getDeclaredMethod("setDescriptor", String.class, String.class);
+//            if (setDescriptorMethod!=null) {
+//                setDescriptorMethod.setAccessible(true);
+//                setDescriptorMethod.invoke(repo, key, value);
+//            } else {
+//                fail("could not get 'setDescriptor' method");
+//            }
+            Method getDescriptorsMethod = repo.getClass().getDeclaredMethod("getDescriptors");
+            if (getDescriptorsMethod==null) {
+                fail("could not get 'getDescriptors' method");
+            } else {
+                getDescriptorsMethod.setAccessible(true);
+                GenericDescriptors descriptors = (GenericDescriptors) getDescriptorsMethod.invoke(repo);
+                SimpleValueFactory valueFactory = new SimpleValueFactory();
+                descriptors.put(key, valueFactory.createValue(value), true, true);
+            }
+            //</hack>
+            
+            //<verify-hack>
+            assertEquals(value, repo.getDescriptor(key));
+            //</verify-hack>
+        } finally {
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+}
diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties
new file mode 100644
index 0000000..7db291c
--- /dev/null
+++ b/src/test/resources/log4j.properties
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+log4j.rootLogger=INFO, stdout
+
+log4j.logger.org.apache.jackrabbit.core.TransientRepository=WARN
+#log4j.logger.org.apache.sling.discovery.impl=DEBUG
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+#log4j.appender.stdout.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} *%-5p* [%t] %c{1}: %m (%F, line %L)\n
+log4j.appender.stdout.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} *%-5p* [%t] %c{1}: %m\n

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 17/38: SLING-5173 and SLING-4603 related : more syncToken log.info - plus always doing the syncToken thingy, independent of whether any instance left or joined the cluster as otherwise this thing wont work

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit dcfc44f90adfe2bd0004821e6eabf073871faacb
Author: Stefan Egli <st...@apache.org>
AuthorDate: Wed Oct 21 11:39:26 2015 +0000

    SLING-5173 and SLING-4603 related : more syncToken log.info - plus always doing the syncToken thingy, independent of whether any instance left or joined the cluster as otherwise this thing wont work
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1709801 13f79535-47bb-0310-9956-ffa450edef68
---
 .../providers/base/MinEventDelayHandler.java       |  6 +--
 .../providers/base/ViewStateManagerImpl.java       | 49 ++++++++--------------
 2 files changed, 20 insertions(+), 35 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
index d3a9b6a..82c18eb 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/MinEventDelayHandler.java
@@ -128,17 +128,17 @@ class MinEventDelayHandler {
                     // check if the new topology is already ready
                     TopologyView t = discoveryService.getTopology();
                     if (!(t instanceof BaseTopologyView)) {
-                        logger.error("asyncDelay.run: topology not of type BaseTopologyView: "+t);
+                        logger.error("asyncDelay.run: done delaying. topology not of type BaseTopologyView: "+t);
                         // cannot continue in this case
                         return;
                     }
                     BaseTopologyView topology = (BaseTopologyView) t;
                     
                     if (topology.isCurrent()) {
-                        logger.debug("asyncDelay.run: got new view: ", topology);
+                        logger.info("asyncDelay.run: done delaying. got new view: ", topology.toShortString());
                         viewStateManager.handleNewViewNonDelayed(topology);
                     } else {
-                        logger.info("asyncDelay.run: new view (still/again) not current, delaying again");
+                        logger.info("asyncDelay.run: done delaying. new view (still/again) not current, delaying again");
                         triggerAsyncDelaying(topology);
                         // we're actually not interested in the result here
                         // if the async part failed, then we have to rely
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index b5fa9fa..f2c3525 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -33,8 +33,6 @@ import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
 import org.apache.sling.discovery.TopologyEventListener;
-import org.apache.sling.discovery.commons.InstancesDiff;
-import org.apache.sling.discovery.commons.InstancesDiff.InstanceCollection;
 import org.apache.sling.discovery.commons.providers.BaseTopologyView;
 import org.apache.sling.discovery.commons.providers.EventHelper;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
@@ -484,37 +482,23 @@ public class ViewStateManagerImpl implements ViewStateManager {
             
             final boolean invokeConsistencyService;
             if (consistencyService==null) {
-                logger.debug("handleNewViewNonDelayed: no consistencyService set - continuing directly.");
+                logger.info("handleNewViewNonDelayed: no consistencyService set - continuing directly.");
                 invokeConsistencyService = false;
-            } else if (previousView==null) {
-                // when there was no previous view, we cannot determine if
-                // any instance left
-                // so for safety reason: always invoke the consistencyservice
-                logger.debug("handleNewViewNonDelayed: no previousView set - invoking consistencyService");
-                invokeConsistencyService = true;
             } else {
-                final InstancesDiff diff = new InstancesDiff(previousView, newView);
-                InstanceCollection removed = diff.removed();
-//                Collection<InstanceDescription> c = removed.get();
-//                Iterator<InstanceDescription> it = c.iterator();
-//                while(it.hasNext()) {
-//                    logger.info("handleNewViewNonDelayed: removed: "+it.next());
-//                }
-                InstanceCollection inClusterView = removed.
-                        isInClusterView(newView.getLocalInstance().getClusterView());
-//                c = removed.get();
-//                it = c.iterator();
-//                while(it.hasNext()) {
-//                    logger.info("handleNewViewNonDelayed: inClusterView: "+it.next());
-//                }
-                final boolean anyInstanceLeftLocalCluster = inClusterView.
-                        get().size()>0;
-                if (anyInstanceLeftLocalCluster) {
-                    logger.debug("handleNewViewNonDelayed: anyInstanceLeftLocalCluster=true, hence invoking consistencyService next");
-                } else {
-                    logger.debug("handleNewViewNonDelayed: anyInstanceLeftLocalCluster=false - continuing directly.");
-                }
-                invokeConsistencyService = anyInstanceLeftLocalCluster;
+                // there used to be a distinction between:
+                // * if no previousView is set, then we should invoke the consistencyService
+                // * if one was set, then we only invoke it if any instance left the cluster
+                // this algorithm would not work though, as the newly joining instance
+                // would always have (previousView==null) - thus would always do the syncToken
+                // thingy - while the existing instances would think: ah, no instance left,
+                // so it is not so urgent to do the syncToken.
+                // at which point the joining instance would wait forever for a syncToken 
+                // to arrive.
+                //
+                // which is a long way of saying: if the consistencyService is configured,
+                // then we always use it, hence:
+                logger.info("handleNewViewNonDelayed: consistencyService set - invoking consistencyService");
+                invokeConsistencyService = true;
             }
                         
             if (invokeConsistencyService) {
@@ -522,7 +506,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                 // then:
                 // run the set consistencyService
                 final int lastModCnt = modCnt;
-                logger.debug("handleNewViewNonDelayed: invoking consistencyService (modCnt={})", modCnt);
+                logger.info("handleNewViewNonDelayed: invoking consistencyService (modCnt={})", modCnt);
                 consistencyService.sync(newView,
                         new Runnable() {
                     
@@ -551,6 +535,7 @@ public class ViewStateManagerImpl implements ViewStateManager {
                 // or using it is not applicable at this stage - so continue
                 // with sending the TOPOLOGY_CHANGED (or TOPOLOGY_INIT if there
                 // are any newly bound topology listeners) directly
+                logger.info("handleNewViewNonDelayed: not invoking consistencyService, considering consistent now");
                 doHandleConsistent(newView);
             }
             logger.debug("handleNewViewNonDelayed: end");

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 32/38: SLING-5094 / SLING-5173 / SLING-4603 related : ensure that before invoking the ConsistencyService.sync no async events are still in the queue. This is achieved by enqueueing an async event too that once it gets triggered ensures that no async events are left. This mechanism ensures that before the syncToken is written, all TopologyEventListeners have received a TOPOLOGY_CHANGING - and only that guarantees that the syncToken mechanism carries a high guarantee.

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 5a5f5ee4585d1aec441657cad7acbde6f66bab57
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 11:28:24 2015 +0000

    SLING-5094 / SLING-5173 / SLING-4603 related : ensure that before invoking the ConsistencyService.sync no async events are still in the queue. This is achieved by enqueueing an async event too that once it gets triggered ensures that no async events are left. This mechanism ensures that before the syncToken is written, all TopologyEventListeners have received a TOPOLOGY_CHANGING - and only that guarantees that the syncToken mechanism carries a high guarantee.
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710175 13f79535-47bb-0310-9956-ffa450edef68
---
 .../commons/providers/ViewStateManager.java        |  5 +-
 .../commons/providers/base/AsyncEventSender.java   | 33 ++++-----
 .../{AsyncEvent.java => AsyncTopologyEvent.java}   | 24 ++++++-
 .../providers/base/ViewStateManagerImpl.java       | 80 +++++++++++++++++-----
 .../commons/providers/base/TestHelper.java         |  2 +
 .../providers/base/TestViewStateManager.java       | 21 ++++--
 .../base/TestOakSyncTokenConsistencyService.java   |  5 +-
 7 files changed, 127 insertions(+), 43 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
index 8c71603..16a70c5 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/ViewStateManager.java
@@ -94,8 +94,9 @@ public interface ViewStateManager {
      * <p>
      * @param timeout time in millis to wait for at max - 0 to not wait at all - -1 
      * to wait indefinitely
-     * @return true if no more async events exist, false if the timeout hit early 
+     * @return 0 if no more async events exist, >0 the number of queued or in-flight (being sent)
+     * events if the timeout hit early
      */
-    boolean waitForAsyncEvents(long timeout);
+    int waitForAsyncEvents(long timeout);
 
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java
index 014f522..89b5b13 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEventSender.java
@@ -55,7 +55,12 @@ final class AsyncEventSender implements Runnable {
     
     /** Enqueues a particular event for asynchronous sending to a particular listener **/
     void enqueue(TopologyEventListener listener, TopologyEvent event) {
-        final AsyncEvent asyncEvent = new AsyncEvent(listener, event);
+        final AsyncTopologyEvent asyncEvent = new AsyncTopologyEvent(listener, event);
+        enqueue(asyncEvent);
+    }
+
+    /** Enqueues an AsyncEvent for later in-order execution **/
+    void enqueue(final AsyncEvent asyncEvent) {
         synchronized(eventQ) {
             eventQ.add(asyncEvent);
             if (logger.isDebugEnabled()) {
@@ -110,7 +115,7 @@ final class AsyncEventSender implements Runnable {
                         isSending = asyncEvent!=null;
                     }
                     if (asyncEvent!=null) {
-                        sendTopologyEvent(asyncEvent);
+                        asyncEvent.trigger();
                     }
                 } catch(Throwable th) {
                     // Even though we should never catch Error or RuntimeException
@@ -141,25 +146,21 @@ final class AsyncEventSender implements Runnable {
         }
     }
 
-    /** Actual sending of the asynchronous event - catches RuntimeExceptions a listener can send. (Error is caught outside) **/
-    private void sendTopologyEvent(AsyncEvent asyncEvent) {
-        logger.trace("sendTopologyEvent: start");
-        final TopologyEventListener listener = asyncEvent.listener;
-        final TopologyEvent event = asyncEvent.event;
-        try{
-            logger.debug("sendTopologyEvent: sending to listener: {}, event: {}", listener, event);
-            listener.handleTopologyEvent(event);
-        } catch(final Exception e) {
-            logger.warn("sendTopologyEvent: handler threw exception. handler: "+listener+", exception: "+e, e);
-        }
-        logger.trace("sendTopologyEvent: start: listener: {}, event: {}", listener, event);
-    }
-
     /** for testing only: checks whether there are any events being queued or sent **/
     boolean hasInFlightEvent() {
         synchronized(eventQ) {
             return isSending || !eventQ.isEmpty();
         }
     }
+
+    public int getInFlightEventCnt() {
+        synchronized(eventQ) {
+            int cnt = eventQ.size();
+            if (isSending) {
+                cnt++;
+            }
+            return cnt;
+        }
+    }
     
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncTopologyEvent.java
similarity index 61%
rename from src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
rename to src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncTopologyEvent.java
index 1a73908..4d2a443 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncEvent.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/AsyncTopologyEvent.java
@@ -20,12 +20,17 @@ package org.apache.sling.discovery.commons.providers.base;
 
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /** SLING-4755 : encapsulates an event that yet has to be sent (asynchronously) for a particular listener **/
-final class AsyncEvent {
+final class AsyncTopologyEvent implements AsyncEvent {
+    
+    static final Logger logger = LoggerFactory.getLogger(AsyncTopologyEvent.class);
+
     final TopologyEventListener listener;
     final TopologyEvent event;
-    AsyncEvent(TopologyEventListener listener, TopologyEvent event) {
+    AsyncTopologyEvent(TopologyEventListener listener, TopologyEvent event) {
         if (listener==null) {
             throw new IllegalArgumentException("listener must not be null");
         }
@@ -37,6 +42,19 @@ final class AsyncEvent {
     }
     @Override
     public String toString() {
-        return "an AsyncEvent[event="+event+", listener="+listener+"]";
+        return "an AsyncTopologyEvent[event="+event+", listener="+listener+"]";
     }
+
+    /** Actual sending of the asynchronous event - catches RuntimeExceptions a listener can send. (Error is caught outside) **/
+    public void trigger() {
+        logger.trace("trigger: start");
+        try{
+            logger.debug("trigger: sending to listener: {}, event: {}", listener, event);
+            listener.handleTopologyEvent(event);
+        } catch(final Exception e) {
+            logger.warn("trigger: handler threw exception. handler: "+listener+", exception: "+e, e);
+        }
+        logger.trace("trigger: start: listener: {}, event: {}", listener, event);
+    }
+
 }
\ No newline at end of file
diff --git a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
index 9fa980b..61b39f6 100644
--- a/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
+++ b/src/main/java/org/apache/sling/discovery/commons/providers/base/ViewStateManagerImpl.java
@@ -512,26 +512,56 @@ public class ViewStateManagerImpl implements ViewStateManager {
                 // then:
                 // run the set consistencyService
                 final int lastModCnt = modCnt;
-                logger.info("handleNewViewNonDelayed: invoking consistencyService (modCnt={})", modCnt);
-                consistencyService.sync(newView,
-                        new Runnable() {
+                logger.info("handleNewViewNonDelayed: invoking waitForAsyncEvents, then consistencyService (modCnt={})", modCnt);
+                asyncEventSender.enqueue(new AsyncEvent() {
                     
-                    public void run() {
-                        logger.trace("consistencyService.callback.run: start. acquiring lock...");
+                    @Override
+                    public String toString() {
+                        return "the waitForAsyncEvents-flush-token-"+hashCode();
+                    }
+                    
+                    @Override
+                    public void trigger() {
+                        // when this event is triggered we're guaranteed to have 
+                        // no more async events - cos the async events are handled
+                        // in a queue and this AsyncEvent was put at the end of the
+                        // queue at enqueue time. So now e can go ahead.
+                        // the plus using such a token event is that others when
+                        // calling waitForAsyncEvent() will get blocked while this
+                        // 'token async event' is handled. Which is what we explicitly want.
                         lock.lock();
                         try{
-                            logger.debug("consistencyService.callback.run: lock aquired. (modCnt should be {}, is {})", lastModCnt, modCnt);
                             if (modCnt!=lastModCnt) {
-                                logger.info("consistencyService.callback.run: modCnt changed (from {} to {}) - ignoring",
+                                logger.info("handleNewViewNonDelayed/waitForAsyncEvents.run: modCnt changed (from {} to {}) - ignoring",
                                         lastModCnt, modCnt);
                                 return;
                             }
-                            logger.info("consistencyService.callback.run: invoking doHandleConsistent.");
-                            // else:
-                            doHandleConsistent(newView);
+                            logger.info("handleNewViewNonDelayed/waitForAsyncEvents.run: done, now invoking consistencyService (modCnt={})", modCnt);
+                            consistencyService.sync(newView,
+                                    new Runnable() {
+                                
+                                public void run() {
+                                    logger.trace("consistencyService.callback.run: start. acquiring lock...");
+                                    lock.lock();
+                                    try{
+                                        logger.debug("consistencyService.callback.run: lock aquired. (modCnt should be {}, is {})", lastModCnt, modCnt);
+                                        if (modCnt!=lastModCnt) {
+                                            logger.info("consistencyService.callback.run: modCnt changed (from {} to {}) - ignoring",
+                                                    lastModCnt, modCnt);
+                                            return;
+                                        }
+                                        logger.info("consistencyService.callback.run: invoking doHandleConsistent.");
+                                        // else:
+                                        doHandleConsistent(newView);
+                                    } finally {
+                                        lock.unlock();
+                                        logger.trace("consistencyService.callback.run: end.");
+                                    }
+                                }
+                                
+                            });
                         } finally {
                             lock.unlock();
-                            logger.trace("consistencyService.callback.run: end.");
                         }
                     }
                     
@@ -628,22 +658,38 @@ public class ViewStateManagerImpl implements ViewStateManager {
     }
 
     @Override
-    public boolean waitForAsyncEvents(long timeout) {
+    public int waitForAsyncEvents(long timeout) {
         long end = System.currentTimeMillis() + timeout;
-        while(asyncEventSender.hasInFlightEvent() || 
-                (minEventDelayHandler!=null && minEventDelayHandler.isDelaying())) {
+        while(true) {
+            int inFlightEventCnt = getInFlightAsyncEventCnt();
+            if (inFlightEventCnt==0) {
+                // no in-flight events - return 0
+                return 0;
+            }
             if (timeout==0) {
-                return false;
+                // timeout is set to 'no-wait', but we have in-flight events,
+                // return the actual cnt
+                return inFlightEventCnt;
             }
-            if (timeout<0 || System.currentTimeMillis()<end) {
+            if (timeout<0 /*infinite waiting*/ || System.currentTimeMillis()<end) {
                 try {
                     Thread.sleep(50);
                 } catch (InterruptedException e) {
                     // ignore
                 }
+            } else {
+                // timeout hit
+                return inFlightEventCnt;
             }
         }
-        return true;
+    }
+    
+    private int getInFlightAsyncEventCnt() {
+        int cnt = asyncEventSender.getInFlightEventCnt();
+        if (minEventDelayHandler!=null && minEventDelayHandler.isDelaying()) {
+            cnt++;
+        }
+        return cnt;
     }
     
 }
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
index 23b6821..72ab0d2 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestHelper.java
@@ -19,6 +19,7 @@
 package org.apache.sling.discovery.commons.providers.base;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.fail;
 
@@ -134,6 +135,7 @@ public class TestHelper {
                 Thread.sleep(delayInMillis);
                 logger.debug("randomEventLoop: waiting "+delayInMillis+"ms done.");
             }
+            assertEquals(0, mgr.waitForAsyncEvents(500));
             if (!shouldCallChanging) {
                 // in that case I should still get a CHANGING - by contract
                 logger.debug("randomEventLoop: asserting CHANGING, CHANGED events were sent");
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
index 13d586d..e80268c 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/base/TestViewStateManager.java
@@ -63,7 +63,9 @@ public class TestViewStateManager {
             try {
                 lock.unlock();
                 try{
+                    logger.info("ConsistencyServiceWithSemaphore.sync: acquiring lock ...");
                     semaphore.acquire();
+                    logger.info("ConsistencyServiceWithSemaphore.sync: lock acquired.");
                 } finally {
                     lock.lock();
                 }
@@ -102,6 +104,10 @@ public class TestViewStateManager {
     
     @After
     public void teardown() throws Exception {
+        if (mgr != null) {
+            // release any async event sender ..
+            mgr.handleDeactivated();
+        }
         mgr = null;
         defaultRandom= null;
     }
@@ -240,7 +246,7 @@ public class TestViewStateManager {
         mgr.handleChanging();
         final BaseTopologyView view = new DummyTopologyView().addInstance();
         mgr.handleNewView(view);
-        assertTrue(mgr.waitForAsyncEvents(1000));
+        assertEquals(0, mgr.waitForAsyncEvents(1000));
         TestHelper.assertNoEvents(listener);
         synchronized(syncCallbacks) {
             assertEquals(1, syncCallbacks.size());
@@ -249,14 +255,14 @@ public class TestViewStateManager {
         String id2 = UUID.randomUUID().toString();
         final BaseTopologyView view2 = TestHelper.newView(true, id1, id1, id1, id2); 
         mgr.handleNewView(view2);
-        assertTrue(mgr.waitForAsyncEvents(1000));
+        assertEquals(0, mgr.waitForAsyncEvents(1000));
         TestHelper.assertNoEvents(listener);
         synchronized(syncCallbacks) {
             assertEquals(1, syncCallbacks.size());
             syncCallbacks.get(0).run();
             syncCallbacks.clear();
         }
-        assertTrue(mgr.waitForAsyncEvents(1000));
+        assertEquals(0, mgr.waitForAsyncEvents(1000));
         assertEvents(listener, EventHelper.newInitEvent(view2));
     }
     
@@ -609,10 +615,12 @@ public class TestViewStateManager {
         });
         Thread.sleep(1000);
         TestHelper.assertNoEvents(listener);
+        assertEquals("should have one thread now waiting", 1, serviceSemaphore.getQueueLength());
         serviceSemaphore.release(1); // release the first one only
         Thread.sleep(1000);
         assertEvents(listener, EventHelper.newInitEvent(view1));
         mgr.handleChanging();
+        assertEquals(0, mgr.waitForAsyncEvents(500));
         assertEvents(listener, EventHelper.newChangingEvent(view1));
         async(new Runnable() {
 
@@ -625,6 +633,7 @@ public class TestViewStateManager {
         Thread.sleep(1000);
         logger.debug("run: asserting no events");
         TestHelper.assertNoEvents(listener);
+        assertEquals("should have one thread now waiting", 1, serviceSemaphore.getQueueLength());
         assertFalse("should not be locked", lock.isLocked());
 
         logger.debug("run: issuing a second event");
@@ -649,12 +658,16 @@ public class TestViewStateManager {
         });
         logger.debug("run: waiting 1sec");
         Thread.sleep(1000);
+        int remainingAsyncEvents = mgr.waitForAsyncEvents(2000);
+        logger.info("run: result of waitForAsyncEvent is: "+remainingAsyncEvents);
+        assertEquals("should have one thread now waiting", 1, serviceSemaphore.getQueueLength());
         assertEquals("should be acquiring (by thread2)", 1, testSemaphore.getQueueLength());
         // releasing the testSemaphore
         testSemaphore.release();
         logger.debug("run: waiting 1sec");
         Thread.sleep(1000);
-        assertEquals("should have both threads now waiting", 2, serviceSemaphore.getQueueLength());
+        assertEquals("should have two async events now in the queue or being sent", 2, mgr.waitForAsyncEvents(500));
+        assertEquals("but should only have 1 thread actually sitting on the semaphore waiting", 1, serviceSemaphore.getQueueLength());
         logger.debug("run: releasing consistencyService");
         serviceSemaphore.release(1); // release the first one only
         logger.debug("run: waiting 1sec");
diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
index a774d76..357c8a5 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
@@ -136,7 +136,7 @@ public class TestOakSyncTokenConsistencyService {
         DescriptorHelper.setDiscoveryLiteDescriptor(factory1, new DiscoveryLiteDescriptorBuilder().me(1).seq(1).activeIds(1).setFinal(true));
         assertTrue(idMapService1.waitForInit(2000));
         cs.triggerBackgroundCheck();
-        assertTrue(vsm.waitForAsyncEvents(1000));
+        assertEquals(0, vsm.waitForAsyncEvents(1000));
         assertEquals(1, l.countEvents());
     }
     
@@ -180,7 +180,10 @@ public class TestOakSyncTokenConsistencyService {
         DummyTopologyView two2 = TestHelper.newView(two1.getLocalClusterSyncTokenId(), two1.getLocalInstance().getClusterView().getId(), true, slingId1, slingId1, slingId1, slingId2);
         vsm2.handleNewView(two2);
         cs1.triggerBackgroundCheck();
+        cs1.triggerBackgroundCheck();
+        cs2.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();
+        assertEquals(0, vsm1.waitForAsyncEvents(500));
         assertEquals(1, l.countEvents());
         DummyTopologyView oneLeaving = two1.clone();
         oneLeaving.removeInstance(slingId2);

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 05/38: Update svn:ignore

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 576bc0d8fce8e86c2a71d1b542aceb60e0b1aeb8
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Fri May 29 09:08:22 2015 +0000

    Update svn:ignore
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1682399 13f79535-47bb-0310-9956-ffa450edef68

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 26/38: SLING-5173 : introducing a more explicit chain concept for ConsistencyServices than the previous hidden/implicit one: ConsistencyServiceChain

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit 6dd354eaeb557f3f53ea1e372d41e2196dfbcf7f
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 15:33:50 2015 +0000

    SLING-5173 : introducing a more explicit chain concept for ConsistencyServices than the previous hidden/implicit one: ConsistencyServiceChain
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710039 13f79535-47bb-0310-9956-ffa450edef68
---
 .../providers/spi/base/TestOakSyncTokenConsistencyService.java    | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
index 174a2e5..a774d76 100644
--- a/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
+++ b/src/test/java/org/apache/sling/discovery/commons/providers/spi/base/TestOakSyncTokenConsistencyService.java
@@ -34,7 +34,7 @@ import org.apache.sling.discovery.commons.providers.base.TestHelper;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
 import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
 import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
-import org.apache.sling.discovery.commons.providers.spi.base.OakSyncTokenConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogConsistencyService;
 import org.apache.sling.jcr.api.SlingRepository;
 import org.junit.After;
 import org.junit.Before;
@@ -119,7 +119,7 @@ public class TestOakSyncTokenConsistencyService {
     public void testOneNode() throws Exception {
         DummyTopologyView one = TestHelper.newView(true, slingId1, slingId1, slingId1);
         Lock lock = new ReentrantLock();
-        OakSyncTokenConsistencyService cs = OakSyncTokenConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
+        OakBacklogConsistencyService cs = OakBacklogConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
         ViewStateManager vsm = ViewStateManagerFactory.newViewStateManager(lock, cs);
         DummyListener l = new DummyListener();
         assertEquals(0, l.countEvents());
@@ -145,7 +145,7 @@ public class TestOakSyncTokenConsistencyService {
         String slingId2 = UUID.randomUUID().toString();
         DummyTopologyView two1 = TestHelper.newView(true, slingId1, slingId1, slingId1, slingId2);
         Lock lock1 = new ReentrantLock();
-        OakSyncTokenConsistencyService cs1 = OakSyncTokenConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
+        OakBacklogConsistencyService cs1 = OakBacklogConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService1, new DummySlingSettingsService(slingId1), factory1);
         ViewStateManager vsm1 = ViewStateManagerFactory.newViewStateManager(lock1, cs1);
         DummyListener l = new DummyListener();
         vsm1.bind(l);
@@ -161,7 +161,7 @@ public class TestOakSyncTokenConsistencyService {
         Lock lock2 = new ReentrantLock();
         IdMapService idMapService2 = IdMapService.testConstructor(
                 new SimpleCommonsConfig(), new DummySlingSettingsService(slingId2), factory2);
-        OakSyncTokenConsistencyService cs2 = OakSyncTokenConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService2, new DummySlingSettingsService(slingId2), factory2);
+        OakBacklogConsistencyService cs2 = OakBacklogConsistencyService.testConstructorAndActivate(new SimpleCommonsConfig(), idMapService2, new DummySlingSettingsService(slingId2), factory2);
         ViewStateManager vsm2 = ViewStateManagerFactory.newViewStateManager(lock2, cs2);
         cs1.triggerBackgroundCheck();
         cs2.triggerBackgroundCheck();

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.

[sling-org-apache-sling-discovery-commons] 37/38: [maven-release-plugin] prepare release org.apache.sling.discovery.commons-1.0.0

Posted by ro...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.discovery.commons-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-commons.git

commit fdd21762a4dc99b10ffe2165019f62db28512dce
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 16:10:33 2015 +0000

    [maven-release-plugin] prepare release org.apache.sling.discovery.commons-1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons@1710642 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index 438bd24..d32437c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,20 +24,20 @@
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
         <version>25</version>
-        <relativePath/>
+        <relativePath />
     </parent>
 
     <artifactId>org.apache.sling.discovery.commons</artifactId>
     <packaging>bundle</packaging>
-    <version>1.0.0-SNAPSHOT</version>
+    <version>1.0.0</version>
 
     <name>Apache Sling Discovery Commons</name>
     <description>Common services related to Sling Discovery</description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/commons</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/commons</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.discovery.commons-1.0.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.discovery.commons-1.0.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.discovery.commons-1.0.0</url>
     </scm>
 
     <build>

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.