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:27:35 UTC

[sling-org-apache-sling-discovery-oak] annotated tag org.apache.sling.discovery.oak-1.0.0 created (now 064e5b1)

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

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


      at 064e5b1  (tag)
 tagging 13b1524ee920a56f57d15bea4a7323694995efa9 (commit)
      by Stefan Egli
      on Mon Oct 26 16:36:03 2015 +0000

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

This annotated tag includes the following new commits:

     new d1c3dce  SLING-4603 : added discovery.oak to the project - this is based on discovery.base, which handles topology connectors and contains a few other base classes that were factored out of discovery.impl for exactly this reuse
     new f889c65  SLING-4603 : some minor wording change
     new c03ca86  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 f030cb5  SLING-4603 : introducing configuration for syncToken enabling/disabling - plus using the ConsistencyServiceChain - plus more details when PROPERTIES_CHANGED
     new f32fadf  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 e993509  SLING-5188 : similar to moving discovery.impl's topology webconsole, do the same for discovery.oak
     new e2707dd  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 38813a3  SLING-5191 / SLING-4603 : rename ConsistencyService to ClusterSyncService - plus making timeout/interval values for the same configurable in discovery.oak
     new f845fb8  SLING-4603 : method renamed : handlePotentialTopologyChange, which is slightly confusing anyway, renamed to checkForTopologyChange
     new bf3ac64  SLING-4603 : switch to discovery.base/.commons 1.0.0
     new d22f2a2  [maven-release-plugin] prepare release org.apache.sling.discovery.oak-1.0.0
     new 13b1524  [maven-release-plugin] copy for tag org.apache.sling.discovery.oak-1.0.0

The 12 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-oak] 05/12: 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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit f32fadf5b356742ffc24ee58544f5e0930aa9e51
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/oak@1710047 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/discovery/oak/OakDiscoveryService.java     | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
index 1a5c8d3..0b27f95 100644
--- a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
+++ b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
@@ -61,6 +61,7 @@ import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
 import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.ConsistencyHistory;
 import org.apache.sling.discovery.commons.providers.spi.base.ConsistencyServiceChain;
 import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
 import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogConsistencyService;
@@ -211,6 +212,10 @@ public class OakDiscoveryService extends BaseDiscoveryService {
 
         ConsistencyService consistencyService;
         if (config.getSyncTokenEnabled()) {
+            //TODO: ConsistencyHistory is implemented a little bit hacky ..
+            ConsistencyHistory consistencyHistory = new ConsistencyHistory();
+            oakBacklogConsistencyService.setConsistencyHistory(consistencyHistory);
+            syncTokenConsistencyService.setConsistencyHistory(consistencyHistory);
             consistencyService = new ConsistencyServiceChain(oakBacklogConsistencyService, syncTokenConsistencyService);
         } else {
             consistencyService = oakBacklogConsistencyService;

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

[sling-org-apache-sling-discovery-oak] 07/12: 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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit e2707dd58c8e0516e7b7e66f23e796a996bf6c38
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/oak@1710175 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java  | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
index 7affca5..d5610fc 100644
--- a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
+++ b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
@@ -104,12 +104,12 @@ public class OakDiscoveryServiceTest {
         DescriptorHelper.setDiscoveryLiteDescriptor(builder.getResourceResolverFactory(), 
                 discoBuilder);
         discoveryService.handlePotentialTopologyChange();
-        assertTrue(discoveryService.getViewStateManager().waitForAsyncEvents(2000));
+        assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
         assertEquals(1, listener.countEvents());
         discoveryService.unbindTopologyEventListener(listener);
         assertEquals(1, listener.countEvents());
         discoveryService.bindTopologyEventListener(listener);
-        assertTrue(discoveryService.getViewStateManager().waitForAsyncEvents(2000));
+        assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
         assertEquals(2, listener.countEvents()); // should now have gotten an INIT too
     }
     

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

[sling-org-apache-sling-discovery-oak] 10/12: SLING-4603 : switch to discovery.base/.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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit bf3ac64d26245d38fcd553dca8f621ffcb99a37a
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 16:22:55 2015 +0000

    SLING-4603 : switch to discovery.base/.commons 1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak@1710649 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/pom.xml b/pom.xml
index 5ee0709..ead770f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -129,7 +129,7 @@
 		<dependency>
 			<groupId>org.apache.sling</groupId>
 			<artifactId>org.apache.sling.discovery.commons</artifactId>
-			<version>1.0.0-SNAPSHOT</version>
+			<version>1.0.0</version>
             <scope>provided</scope>
 		</dependency>
         <!-- besides including discovery.commons' normal jar above, 
@@ -138,14 +138,14 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.discovery.commons</artifactId>
-            <version>1.0.0-SNAPSHOT</version>
+            <version>1.0.0</version>
             <scope>test</scope>
             <type>test-jar</type>
         </dependency>
 		<dependency>
 			<groupId>org.apache.sling</groupId>
 			<artifactId>org.apache.sling.discovery.base</artifactId>
-			<version>1.0.0-SNAPSHOT</version>
+			<version>1.0.0</version>
             <scope>provided</scope>
 		</dependency>
         <!-- besides including discovery.base' normal jar above, 
@@ -154,7 +154,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.discovery.base</artifactId>
-            <version>1.0.0-SNAPSHOT</version>
+            <version>1.0.0</version>
             <scope>test</scope>
             <type>test-jar</type>
         </dependency>

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

[sling-org-apache-sling-discovery-oak] 02/12: SLING-4603 : some minor wording change

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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit f889c656f6c01fac1e76d0518526f3a9f7bf104c
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 08:03:56 2015 +0000

    SLING-4603 : some minor wording change
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak@1709956 13f79535-47bb-0310-9956-ffa450edef68
---
 .../org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java  | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
index 2c635a4..23676f9 100644
--- a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
@@ -279,14 +279,14 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         pw.println("<br/>");
 
         ResourceResolver resourceResolver = null;
-        pw.println("<p class=\"statline ui-state-highlight\">Discovery-Lite Descriptor History</p>");
+        pw.println("<p class=\"statline ui-state-highlight\">Oak Discovery-Lite Descriptor History</p>");
         pw.println("<pre>");
         for (String discoLiteHistoryEntry : discoveryLiteHistory) {
             pw.println(discoLiteHistoryEntry);
         }
         pw.println("</pre>");
         pw.println("<br/>");
-        pw.println("<p class=\"statline ui-state-highlight\">Current Discovery-Lite Descriptor Value</p>");
+        pw.println("<p class=\"statline ui-state-highlight\">Current Oak Discovery-Lite Descriptor</p>");
         pw.println("<pre>");
         try{
             resourceResolver = getResourceResolver();
@@ -924,14 +924,14 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         }
 
         ResourceResolver resourceResolver = null;
-        pw.println("Discovery-Lite Descriptor History");
+        pw.println("Oak Discovery-Lite Descriptor History");
         pw.println("---------------------------------------");
         for (String discoLiteHistoryEntry : discoveryLiteHistory) {
             pw.println(discoLiteHistoryEntry);
         }
         pw.println();
         pw.println();
-        pw.println("Current Discovery-Lite Descriptor Value");
+        pw.println("Current Oak Discovery-Lite Descriptor");
         pw.println("---------------------------------------");
         try{
             resourceResolver = getResourceResolver();

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

[sling-org-apache-sling-discovery-oak] 11/12: [maven-release-plugin] prepare release org.apache.sling.discovery.oak-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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit d22f2a22375b6571424e96249b568efffc1a5ca5
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 16:35:49 2015 +0000

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

diff --git a/pom.xml b/pom.xml
index ead770f..ef48de1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,15 +29,15 @@
 
     <artifactId>org.apache.sling.discovery.oak</artifactId>
     <packaging>bundle</packaging>
-    <version>1.0.0-SNAPSHOT</version>
+    <version>1.0.0</version>
 
     <name>Apache Sling Oak-Based Discovery Service</name>
     <description>Implementation of Apache Sling Discovery based on Jackrabbit Oak using its discovery-lite descriptor for in-cluster view detection and a TopologyView through HTTP POST heartbeats announcing sub-topologies to each other.</description>
 
     <scm>
-        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak</connection>
-        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak</developerConnection>
-        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/oak</url>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/tags/org.apache.sling.discovery.oak-1.0.0</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.discovery.oak-1.0.0</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/tags/org.apache.sling.discovery.oak-1.0.0</url>
     </scm>
 
     <build>

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

[sling-org-apache-sling-discovery-oak] 03/12: 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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit c03ca86d0ecff0fe465aaa66367acca4f11bd435
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/oak@1710003 13f79535-47bb-0310-9956-ffa450edef68
---
 .../discovery/oak/TopologyWebConsolePlugin.java    | 119 ++++++++++-----------
 .../oak/cluster/OakClusterViewService.java         |  12 ++-
 2 files changed, 68 insertions(+), 63 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
index 23676f9..acc75aa 100644
--- a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
@@ -55,6 +55,8 @@ import org.apache.sling.discovery.InstanceDescription;
 import org.apache.sling.discovery.InstanceFilter;
 import org.apache.sling.discovery.TopologyEvent;
 import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.TopologyView;
 import org.apache.sling.discovery.base.commons.ClusterViewService;
 import org.apache.sling.discovery.base.connectors.announcement.Announcement;
 import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
@@ -62,8 +64,7 @@ import org.apache.sling.discovery.base.connectors.announcement.CachedAnnouncemen
 import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
 import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorClientInformation;
 import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
-import org.apache.sling.discovery.TopologyEventListener;
-import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.commons.providers.spi.base.OakSyncTokenConsistencyService;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -110,10 +111,19 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
     @Reference
     protected ResourceResolverFactory resourceResolverFactory;
 
+    @Reference
+    private OakSyncTokenConsistencyService consistencyService;
+
     private TopologyView currentView;
     
     private List<String> discoveryLiteHistory = new LinkedList<String>();
 
+    /** 
+     * keeps hold of the last DiscoveryLiteDescriptor that was added
+     * to the discoveryLiteHistory - in order to de-duplicate as we go
+     */
+    private DiscoveryLiteDescriptor lastDiscoveryLiteDescriptor = null;
+    
     @Override
     public String getLabel() {
         return LABEL;
@@ -277,32 +287,6 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         listIncomingTopologyConnectors(pw);
         listOutgoingTopologyConnectors(pw);
         pw.println("<br/>");
-
-        ResourceResolver resourceResolver = null;
-        pw.println("<p class=\"statline ui-state-highlight\">Oak Discovery-Lite Descriptor History</p>");
-        pw.println("<pre>");
-        for (String discoLiteHistoryEntry : discoveryLiteHistory) {
-            pw.println(discoLiteHistoryEntry);
-        }
-        pw.println("</pre>");
-        pw.println("<br/>");
-        pw.println("<p class=\"statline ui-state-highlight\">Current Oak Discovery-Lite Descriptor</p>");
-        pw.println("<pre>");
-        try{
-            resourceResolver = getResourceResolver();
-            DiscoveryLiteDescriptor descriptor = DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
-            final String logEntry = getCurrentDateFormatted() + ": " + descriptor.getDescriptorStr();
-            pw.println(logEntry);
-        } catch(Exception e) {
-            logger.error("renderOverview: Exception: "+e, e);
-            pw.println("Got exception trying to get repository descriptor: "+e);
-        } finally {
-            if (resourceResolver != null) {
-                resourceResolver.close();
-            }
-        }
-        pw.println("</pre>");
-        pw.println("<br/>");
         
         pw.println("<p class=\"statline ui-state-highlight\">Topology Change History</p>");
         pw.println("<pre>");
@@ -322,6 +306,23 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         }
         pw.println("</pre>");
         pw.println("</br>");
+
+        pw.println("<p class=\"statline ui-state-highlight\">Oak Discovery-Lite Descriptor History</p>");
+        updateDiscoveryLiteHistory();
+        pw.println("<pre>");
+        for (String discoLiteHistoryEntry : discoveryLiteHistory) {
+            pw.println(discoLiteHistoryEntry);
+        }
+        pw.println("</pre>");
+        pw.println("<br/>");
+
+        pw.println("<p class=\"statline ui-state-highlight\">ConsistencyService History</p>");
+        pw.println("<pre>");
+        for (String syncHistoryEntry : consistencyService.getSyncHistory()) {
+            pw.println(syncHistoryEntry);
+        }
+        pw.println("</pre>");
+        pw.println("<br/>");
     }
 
     /**
@@ -600,7 +601,7 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
                             + newInstanceDescription
                             + " in oldview.. event="
                             + event);
-                    addEventLog(event.getType(), event.getType().toString());
+                    addEventLog(event.getType(), event.getType().toString()+" (new instance "+newInstanceDescription.getSlingId()+" not found in old view)");
                     return;
                 }
 
@@ -619,7 +620,7 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
                 }
             }
 
-            addEventLog(event.getType(), sb.toString());
+            addEventLog(event.getType(), "details: "+sb.toString());
         } else if (event.getType() == Type.TOPOLOGY_INIT) {
             this.currentView = event.getNewView();
             StringBuilder details = new StringBuilder();
@@ -692,7 +693,7 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
                                 + details);
             }
         }
-        addDiscoveryLiteHistoryEntry();
+        updateDiscoveryLiteHistory();
     }
 
     /**
@@ -735,17 +736,22 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
     }
 
     /**
-     * add a discoveryLite descriptor entry to the history, truncating if necessary
+     * if there's any change add a discoveryLite descriptor entry to the history, truncating if necessary
      */
-    private synchronized void addDiscoveryLiteHistoryEntry() {
+    private synchronized void updateDiscoveryLiteHistory() {
         ResourceResolver resourceResolver = null;
         try{
             resourceResolver = getResourceResolver();
             DiscoveryLiteDescriptor descriptor = 
                     DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
 
+            if (lastDiscoveryLiteDescriptor!=null && 
+                    descriptor.getDescriptorStr().equals(lastDiscoveryLiteDescriptor.getDescriptorStr())) {
+                // de-duplication - then there's nothing to update
+                return;
+            }
             final String logEntry = getCurrentDateFormatted() + ": " + descriptor.getDescriptorStr();
-            
+            lastDiscoveryLiteDescriptor = descriptor;
             discoveryLiteHistory.add(logEntry);
             while (discoveryLiteHistory.size() > 12) {
                 discoveryLiteHistory.remove(0);
@@ -923,34 +929,6 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
             pw.println();
         }
 
-        ResourceResolver resourceResolver = null;
-        pw.println("Oak Discovery-Lite Descriptor History");
-        pw.println("---------------------------------------");
-        for (String discoLiteHistoryEntry : discoveryLiteHistory) {
-            pw.println(discoLiteHistoryEntry);
-        }
-        pw.println();
-        pw.println();
-        pw.println("Current Oak Discovery-Lite Descriptor");
-        pw.println("---------------------------------------");
-        try{
-            resourceResolver = getResourceResolver();
-            DiscoveryLiteDescriptor descriptor = DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
-            final String logEntry = getCurrentDateFormatted() + ": " + descriptor.getDescriptorStr();
-            pw.println(logEntry);
-            pw.println();
-            pw.println();
-        } catch(Exception e) {
-            logger.error("renderOverview: Exception: "+e, e);
-            pw.println("Got exception trying to get repository descriptor: "+e);
-            pw.println();
-            pw.println();
-        } finally {
-            if (resourceResolver != null) {
-                resourceResolver.close();
-            }
-        }
-
         if ( topologyLog.size() > 0 ) {
             pw.println("Topology Change History");
             pw.println("---------------------------------------");
@@ -969,6 +947,23 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
             }
             pw.println();
         }
+
+        pw.println("Oak Discovery-Lite Descriptor History");
+        pw.println("---------------------------------------");
+        updateDiscoveryLiteHistory();
+        for (String discoLiteHistoryEntry : discoveryLiteHistory) {
+            pw.println(discoLiteHistoryEntry);
+        }
+        pw.println();
+        pw.println();
+
+        pw.println("ConsistencyService History");
+        pw.println("---------------------------------------");
+        for (String syncHistoryEntry : consistencyService.getSyncHistory()) {
+            pw.println(syncHistoryEntry);
+        }
+        pw.println();
+        pw.println();
     }
 
     private String getCurrentDateFormatted() {
diff --git a/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java b/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java
index ce55cc2..4f770cf 100644
--- a/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java
+++ b/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java
@@ -68,6 +68,9 @@ public class OakClusterViewService implements ClusterViewService {
     @Reference
     private IdMapService idMapService;
     
+    /** the last sequence number read from the oak discovery-lite descriptor **/
+    private long lastSeqNum = -1;
+    
     public static OakClusterViewService testConstructor(SlingSettingsService settingsService,
             ResourceResolverFactory resourceResolverFactory,
             IdMapService idMapService,
@@ -98,6 +101,11 @@ public class OakClusterViewService implements ClusterViewService {
             resourceResolver = getResourceResolver();
             DiscoveryLiteDescriptor descriptor = 
                     DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+            if (lastSeqNum!=descriptor.getSeqNum()) {
+                logger.info("getLocalClusterView: sequence number change detected - clearing idmap cache");
+                idMapService.clearCache();
+                lastSeqNum = descriptor.getSeqNum();
+            }
             return asClusterView(descriptor, resourceResolver);
         } catch (UndefinedClusterViewException e) {
             logger.info("getLocalClusterView: undefined clusterView: "+e.getReason()+" - "+e.getMessage());
@@ -122,7 +130,7 @@ public class OakClusterViewService implements ClusterViewService {
         }
         logger.trace("asClusterView: start");
         String clusterViewId = descriptor.getViewId();
-        String localClusterSyncTokenId = descriptor.getViewId()+"_"+descriptor.getSeqNum();
+        String localClusterSyncTokenId = /*descriptor.getViewId()+"_"+*/String.valueOf(descriptor.getSeqNum());
         if (!descriptor.isFinal()) {
             throw new UndefinedClusterViewException(Reason.NO_ESTABLISHED_VIEW, "descriptor is not yet final: "+descriptor);
         }
@@ -146,6 +154,7 @@ public class OakClusterViewService implements ClusterViewService {
         for (Integer id : activeIdsList) {
             String slingId = idMapService.toSlingId(id, resourceResolver);
             if (slingId == null) {
+                idMapService.clearCache();
                 throw new UndefinedClusterViewException(Reason.NO_ESTABLISHED_VIEW,
                         "no slingId mapped for clusterNodeId="+id);
             }
@@ -169,6 +178,7 @@ public class OakClusterViewService implements ClusterViewService {
             boolean isOwn = id==me;
             String slingId = idMapService.toSlingId(id, resourceResolver);
             if (slingId==null) {
+                idMapService.clearCache();
                 logger.info("asClusterView: cannot resolve oak-clusterNodeId {} to a slingId", id);
                 throw new Exception("Cannot resolve oak-clusterNodeId "+id+" to a slingId");
             }

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

[sling-org-apache-sling-discovery-oak] 12/12: [maven-release-plugin] copy for tag org.apache.sling.discovery.oak-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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit 13b1524ee920a56f57d15bea4a7323694995efa9
Author: Stefan Egli <st...@apache.org>
AuthorDate: Mon Oct 26 16:36:03 2015 +0000

    [maven-release-plugin] copy for tag org.apache.sling.discovery.oak-1.0.0
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/tags/org.apache.sling.discovery.oak-1.0.0@1710652 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-oak] 08/12: 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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit 38813a3f65f689823cc908db604b1165437c2abe
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/oak@1710183 13f79535-47bb-0310-9956-ffa450edef68
---
 .../org/apache/sling/discovery/oak/Config.java     | 31 ++++++++++++++++----
 .../sling/discovery/oak/OakDiscoveryService.java   | 34 +++++++++++-----------
 .../discovery/oak/TopologyWebConsolePlugin.java    | 12 ++++----
 .../discovery/oak/OakDiscoveryServiceTest.java     |  4 +--
 .../oak/its/setup/OakVirtualInstanceBuilder.java   | 32 ++++++++++----------
 5 files changed, 66 insertions(+), 47 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/oak/Config.java b/src/main/java/org/apache/sling/discovery/oak/Config.java
index 9b8cd11..4bb4095 100644
--- a/src/main/java/org/apache/sling/discovery/oak/Config.java
+++ b/src/main/java/org/apache/sling/discovery/oak/Config.java
@@ -73,6 +73,16 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
     public static final String DISCOVERY_LITE_CHECK_INTERVAL_KEY = "discoveryLiteCheckInterval";
     protected long discoveryLiteCheckInterval = DEFAULT_DISCOVERY_LITE_CHECK_INTERVAL;
     
+    public static final long DEFAULT_CLUSTER_SYNC_SERVICE_TIMEOUT = 120;
+    @Property(longValue=DEFAULT_CLUSTER_SYNC_SERVICE_TIMEOUT)
+    public static final String CLUSTER_SYNC_SERVICE_TIMEOUT_KEY = "clusterSyncServiceTimeout";
+    protected long clusterSyncServiceTimeout = DEFAULT_CLUSTER_SYNC_SERVICE_TIMEOUT;
+
+    public static final long DEFAULT_CLUSTER_SYNC_SERVICE_INTERVAL = 2;
+    @Property(longValue=DEFAULT_CLUSTER_SYNC_SERVICE_INTERVAL)
+    public static final String CLUSTER_SYNC_SERVICE_INTERVAL_KEY = "clusterSyncServiceInterval";
+    protected long clusterSyncServiceInterval = DEFAULT_CLUSTER_SYNC_SERVICE_INTERVAL;
+
     /**
      * If set to true a syncToken will be used on top of waiting for
      * deactivating instances to be fully processed.
@@ -234,6 +244,17 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
         logger.debug("configure: discoveryLiteCheckInterval='{}'",
                 this.discoveryLiteCheckInterval);
                 
+        this.clusterSyncServiceTimeout = PropertiesUtil.toLong(
+                properties.get(CLUSTER_SYNC_SERVICE_TIMEOUT_KEY),
+                DEFAULT_CLUSTER_SYNC_SERVICE_TIMEOUT);
+        logger.debug("configure: clusterSyncServiceTimeout='{}'",
+                this.clusterSyncServiceTimeout);
+
+        this.clusterSyncServiceInterval = PropertiesUtil.toLong(
+                properties.get(CLUSTER_SYNC_SERVICE_INTERVAL_KEY),
+                DEFAULT_CLUSTER_SYNC_SERVICE_TIMEOUT);
+        logger.debug("configure: clusterSyncServiceInterval='{}'",
+                this.clusterSyncServiceInterval);
 
         this.minEventDelay = PropertiesUtil.toInteger(
                 properties.get(MIN_EVENT_DELAY_KEY),
@@ -473,15 +494,13 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
     }
     
     @Override
-    public long getBgTimeoutMillis() {
-        // TODO: currently hard coded
-        return -1;
+    public long getClusterSyncServiceTimeoutMillis() {
+        return clusterSyncServiceTimeout * 1000;
     }
 
     @Override
-    public long getBgIntervalMillis() {
-        // TODO: currently hard coded
-        return 1000;
+    public long getClusterSyncServiceIntervalMillis() {
+        return clusterSyncServiceInterval * 1000;
     }
     
     public boolean getSyncTokenEnabled() {
diff --git a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
index 0b27f95..a75a2ef 100644
--- a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
+++ b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
@@ -60,12 +60,12 @@ import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
-import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
-import org.apache.sling.discovery.commons.providers.spi.base.ConsistencyHistory;
-import org.apache.sling.discovery.commons.providers.spi.base.ConsistencyServiceChain;
+import org.apache.sling.discovery.commons.providers.spi.ClusterSyncService;
+import org.apache.sling.discovery.commons.providers.spi.base.ClusterSyncHistory;
+import org.apache.sling.discovery.commons.providers.spi.base.ClusterSyncServiceChain;
 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.SyncTokenConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogClusterSyncService;
+import org.apache.sling.discovery.commons.providers.spi.base.SyncTokenService;
 import org.apache.sling.discovery.commons.providers.util.PropertyNameHelper;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.discovery.oak.pinger.OakViewChecker;
@@ -133,10 +133,10 @@ public class OakDiscoveryService extends BaseDiscoveryService {
     private IdMapService idMapService;
     
     @Reference
-    private OakBacklogConsistencyService oakBacklogConsistencyService;
+    private OakBacklogClusterSyncService oakBacklogClusterSyncService;
 
     @Reference
-    private SyncTokenConsistencyService syncTokenConsistencyService;
+    private SyncTokenService syncTokenService;
 
     /** the slingId of the local instance **/
     private String slingId;
@@ -157,8 +157,8 @@ public class OakDiscoveryService extends BaseDiscoveryService {
             OakViewChecker connectorPinger,
             Scheduler scheduler,
             IdMapService idMapService,
-            OakBacklogConsistencyService oakBacklogConsistencyService,
-            SyncTokenConsistencyService syncTokenConsistencyService,
+            OakBacklogClusterSyncService oakBacklogClusterSyncService,
+            SyncTokenService syncTokenService,
             ResourceResolverFactory factory) {
         OakDiscoveryService discoService = new OakDiscoveryService();
         discoService.settingsService = settingsService;
@@ -169,8 +169,8 @@ public class OakDiscoveryService extends BaseDiscoveryService {
         discoService.oakViewChecker = connectorPinger;
         discoService.scheduler = scheduler;
         discoService.idMapService = idMapService;
-        discoService.oakBacklogConsistencyService = oakBacklogConsistencyService;
-        discoService.syncTokenConsistencyService = syncTokenConsistencyService;
+        discoService.oakBacklogClusterSyncService = oakBacklogClusterSyncService;
+        discoService.syncTokenService = syncTokenService;
         discoService.resourceResolverFactory = factory;
         return discoService;
     }
@@ -210,15 +210,15 @@ public class OakDiscoveryService extends BaseDiscoveryService {
 
         slingId = settingsService.getSlingId();
 
-        ConsistencyService consistencyService;
+        ClusterSyncService consistencyService;
         if (config.getSyncTokenEnabled()) {
             //TODO: ConsistencyHistory is implemented a little bit hacky ..
-            ConsistencyHistory consistencyHistory = new ConsistencyHistory();
-            oakBacklogConsistencyService.setConsistencyHistory(consistencyHistory);
-            syncTokenConsistencyService.setConsistencyHistory(consistencyHistory);
-            consistencyService = new ConsistencyServiceChain(oakBacklogConsistencyService, syncTokenConsistencyService);
+            ClusterSyncHistory consistencyHistory = new ClusterSyncHistory();
+            oakBacklogClusterSyncService.setConsistencyHistory(consistencyHistory);
+            syncTokenService.setConsistencyHistory(consistencyHistory);
+            consistencyService = new ClusterSyncServiceChain(oakBacklogClusterSyncService, syncTokenService);
         } else {
-            consistencyService = oakBacklogConsistencyService;
+            consistencyService = oakBacklogClusterSyncService;
             
         }
         viewStateManager = ViewStateManagerFactory.newViewStateManager(viewStateManagerLock, consistencyService);
diff --git a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
index f4d4658..c823411 100644
--- a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
@@ -65,7 +65,7 @@ import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
 import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorClientInformation;
 import org.apache.sling.discovery.commons.InstancesDiff;
 import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
-import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogClusterSyncService;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -114,7 +114,7 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
     protected ResourceResolverFactory resourceResolverFactory;
 
     @Reference
-    private OakBacklogConsistencyService consistencyService;
+    private OakBacklogClusterSyncService clusterSyncService;
 
     private TopologyView currentView;
     
@@ -318,9 +318,9 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         pw.println("</pre>");
         pw.println("<br/>");
 
-        pw.println("<p class=\"statline ui-state-highlight\">ConsistencyService History</p>");
+        pw.println("<p class=\"statline ui-state-highlight\">ClusterSyncService History</p>");
         pw.println("<pre>");
-        for (String syncHistoryEntry : consistencyService.getSyncHistory()) {
+        for (String syncHistoryEntry : clusterSyncService.getSyncHistory()) {
             pw.println(syncHistoryEntry);
         }
         pw.println("</pre>");
@@ -983,9 +983,9 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         pw.println();
         pw.println();
 
-        pw.println("ConsistencyService History");
+        pw.println("ClusterSyncService History");
         pw.println("---------------------------------------");
-        for (String syncHistoryEntry : consistencyService.getSyncHistory()) {
+        for (String syncHistoryEntry : clusterSyncService.getSyncHistory()) {
             pw.println(syncHistoryEntry);
         }
         pw.println();
diff --git a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
index d5610fc..5ca93bd 100644
--- a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
+++ b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
@@ -56,12 +56,12 @@ public class OakDiscoveryServiceTest {
         }
 
         @Override
-        public long getBgTimeoutMillis() {
+        public long getClusterSyncServiceTimeoutMillis() {
             return bgTimeoutMillis;
         }
 
         @Override
-        public long getBgIntervalMillis() {
+        public long getClusterSyncServiceIntervalMillis() {
             return bgIntervalMillis;
         }
 
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
index b25d2ff..acf20e3 100644
--- a/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
+++ b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
@@ -33,9 +33,9 @@ import org.apache.sling.discovery.base.its.setup.VirtualInstance;
 import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
 import org.apache.sling.discovery.base.its.setup.mock.MockFactory;
 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.discovery.commons.providers.spi.base.RepositoryTestHelper;
-import org.apache.sling.discovery.commons.providers.spi.base.SyncTokenConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.SyncTokenService;
 import org.apache.sling.discovery.oak.OakDiscoveryService;
 import org.apache.sling.discovery.oak.cluster.OakClusterViewService;
 import org.apache.sling.discovery.oak.pinger.OakViewChecker;
@@ -54,8 +54,8 @@ public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
     private IdMapService idMapService;
     private OakViewChecker oakViewChecker;
     private SimulatedLeaseCollection leaseCollection;
-    private OakBacklogConsistencyService consistencyService;
-    private SyncTokenConsistencyService syncTokenConsistencyService;
+    private OakBacklogClusterSyncService consistencyService;
+    private SyncTokenService syncTokenService;
     
     @Override
     public VirtualInstanceBuilder createNewRepository() throws Exception {
@@ -189,26 +189,26 @@ public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
         return OakViewChecker.testConstructor(getSlingSettingsService(), getResourceResolverFactory(), getConnectorRegistry(), getAnnouncementRegistry(), getScheduler(), getConfig());
     }
 
-    private OakBacklogConsistencyService getOakBacklogConsistencyService() throws Exception {
+    private OakBacklogClusterSyncService getOakBacklogClusterSyncService() throws Exception {
         if (consistencyService == null) {
-            consistencyService = createOakBacklogConsistencyService();
+            consistencyService = createOakBacklogClusterSyncService();
         }
         return consistencyService;
     }
     
-    private OakBacklogConsistencyService createOakBacklogConsistencyService() {
-        return OakBacklogConsistencyService.testConstructorAndActivate(getConfig(), getIdMapService(), getSlingSettingsService(), getResourceResolverFactory());
+    private OakBacklogClusterSyncService createOakBacklogClusterSyncService() {
+        return OakBacklogClusterSyncService.testConstructorAndActivate(getConfig(), getIdMapService(), getSlingSettingsService(), getResourceResolverFactory());
     }
 
-    private SyncTokenConsistencyService getSyncTokenConsistencyService() throws Exception {
-        if (syncTokenConsistencyService == null) {
-            syncTokenConsistencyService = createSyncTokenConsistencyService();
+    private SyncTokenService getSyncTokenService() throws Exception {
+        if (syncTokenService == null) {
+            syncTokenService = createSyncTokenService();
         }
-        return syncTokenConsistencyService;
+        return syncTokenService;
     }
     
-    private SyncTokenConsistencyService createSyncTokenConsistencyService() {
-        return SyncTokenConsistencyService.testConstructorAndActivate(getConfig(), getResourceResolverFactory(), getSlingSettingsService());
+    private SyncTokenService createSyncTokenService() {
+        return SyncTokenService.testConstructorAndActivate(getConfig(), getResourceResolverFactory(), getSlingSettingsService());
     }
 
     @Override
@@ -222,8 +222,8 @@ public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
                 getOakViewChecker(), 
                 getScheduler(), 
                 getIdMapService(), 
-                getOakBacklogConsistencyService(),
-                getSyncTokenConsistencyService(),
+                getOakBacklogClusterSyncService(),
+                getSyncTokenService(),
                 getResourceResolverFactory());
     }
 

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

[sling-org-apache-sling-discovery-oak] 09/12: SLING-4603 : method renamed : handlePotentialTopologyChange, which is slightly confusing anyway, renamed to checkForTopologyChange

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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

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

    SLING-4603 : method renamed : handlePotentialTopologyChange, which is slightly confusing anyway, renamed to checkForTopologyChange
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak@1710561 13f79535-47bb-0310-9956-ffa450edef68
---
 .../org/apache/sling/discovery/oak/OakDiscoveryService.java  | 12 ++++++------
 .../apache/sling/discovery/oak/pinger/OakViewChecker.java    |  4 ++--
 .../apache/sling/discovery/oak/OakDiscoveryServiceTest.java  |  2 +-
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
index a75a2ef..49c307b 100644
--- a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
+++ b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
@@ -384,7 +384,7 @@ public class OakDiscoveryService extends BaseDiscoveryService {
         this.providerInfos.add(info);
         Collections.sort(this.providerInfos);
         this.doUpdateProperties();
-        handlePotentialTopologyChange();
+        checkForTopologyChange();
     }
 
     /**
@@ -423,7 +423,7 @@ public class OakDiscoveryService extends BaseDiscoveryService {
         final ProviderInfo info = new ProviderInfo(propertyProvider, props);
         if ( this.providerInfos.remove(info) && update ) {
             this.doUpdateProperties();
-            this.handlePotentialTopologyChange();
+            this.checkForTopologyChange();
         }
     }
 
@@ -527,7 +527,7 @@ public class OakDiscoveryService extends BaseDiscoveryService {
             logger.debug("updateProperties: calling doUpdateProperties.");
             doUpdateProperties();
             logger.debug("updateProperties: calling handlePotentialTopologyChange.");
-            handlePotentialTopologyChange();
+            checkForTopologyChange();
             logger.debug("updateProperties: done.");
         }
     }
@@ -610,13 +610,13 @@ public class OakDiscoveryService extends BaseDiscoveryService {
     }
 
     /**
-     * Handle the fact that the topology has likely changed
+     * Check the current topology for any potential change
      */
-    public void handlePotentialTopologyChange() {
+    public void checkForTopologyChange() {
         viewStateManagerLock.lock();
         try{
             if (!activated) {
-                logger.debug("handlePotentialTopologyChange: not yet activated, ignoring");
+                logger.debug("checkForTopologyChange: not yet activated, ignoring");
                 return;
             }
             BaseTopologyView t = (BaseTopologyView) getTopology();
diff --git a/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java b/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java
index cf50d26..1cd73c3 100644
--- a/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java
+++ b/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java
@@ -219,7 +219,7 @@ public class OakViewChecker extends BaseViewChecker {
             // all that we can therefore do here is assume something
             // might have changed and let discoveryService/viewStateManager
             // filter out the 99.99% of unchanged cases.
-            discoveryService.handlePotentialTopologyChange();
+            discoveryService.checkForTopologyChange();
         }
         logger.debug("discoveryLiteCheck: end. [for slingId="+slingId+"]");
     }
@@ -310,7 +310,7 @@ public class OakViewChecker extends BaseViewChecker {
         // all that we can therefore do here is assume something
         // might have changed and let discoveryService/viewStateManager
         // filter out the 99.99% of unchanged cases.
-        discoveryService.handlePotentialTopologyChange();
+        discoveryService.checkForTopologyChange();
     }
 
     protected void updateProperties() {
diff --git a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
index 5ca93bd..f1b965a 100644
--- a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
+++ b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
@@ -103,7 +103,7 @@ public class OakDiscoveryServiceTest {
         discoBuilder.setFinal(true);
         DescriptorHelper.setDiscoveryLiteDescriptor(builder.getResourceResolverFactory(), 
                 discoBuilder);
-        discoveryService.handlePotentialTopologyChange();
+        discoveryService.checkForTopologyChange();
         assertEquals(0, discoveryService.getViewStateManager().waitForAsyncEvents(2000));
         assertEquals(1, listener.countEvents());
         discoveryService.unbindTopologyEventListener(listener);

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

[sling-org-apache-sling-discovery-oak] 06/12: SLING-5188 : similar to moving discovery.impl's topology webconsole, do the same for 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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit e993509c9ee3210e57538b5e81f294a848538cc2
Author: Stefan Egli <st...@apache.org>
AuthorDate: Fri Oct 23 11:21:34 2015 +0000

    SLING-5188 : similar to moving discovery.impl's topology webconsole, do the same for discovery.oak
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak@1710173 13f79535-47bb-0310-9956-ffa450edef68
---
 .../java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java   | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
index 97d8fa6..f4d4658 100644
--- a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
@@ -64,7 +64,6 @@ import org.apache.sling.discovery.base.connectors.announcement.CachedAnnouncemen
 import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
 import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorClientInformation;
 import org.apache.sling.discovery.commons.InstancesDiff;
-import org.apache.sling.discovery.commons.InstancesDiff.InstanceCollection;
 import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
 import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogConsistencyService;
 import org.osgi.framework.BundleContext;
@@ -82,6 +81,7 @@ import org.slf4j.LoggerFactory;
             value="Apache Sling Web Console Plugin to display Background servlets and ExecutionEngine status"),
     @Property(name=WebConsoleConstants.PLUGIN_LABEL, value=TopologyWebConsolePlugin.LABEL),
     @Property(name=WebConsoleConstants.PLUGIN_TITLE, value=TopologyWebConsolePlugin.TITLE),
+    @Property(name="felix.webconsole.category", value="Sling"),
     @Property(name="felix.webconsole.configprinter.modes", value={"zip"})
 })
 @SuppressWarnings("serial")

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

[sling-org-apache-sling-discovery-oak] 04/12: SLING-4603 : introducing configuration for syncToken enabling/disabling - plus using the ConsistencyServiceChain - plus more details when PROPERTIES_CHANGED

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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

commit f030cb5310da31f788938e75e3583300e8605c2e
Author: Stefan Egli <st...@apache.org>
AuthorDate: Thu Oct 22 15:38:13 2015 +0000

    SLING-4603 : introducing configuration for syncToken enabling/disabling - plus using the ConsistencyServiceChain - plus more details when PROPERTIES_CHANGED
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak@1710041 13f79535-47bb-0310-9956-ffa450edef68
---
 .../org/apache/sling/discovery/oak/Config.java     | 20 ++++++++++++
 .../sling/discovery/oak/OakDiscoveryService.java   | 24 +++++++++++---
 .../discovery/oak/TopologyWebConsolePlugin.java    | 38 ++++++++++++++++++----
 .../oak/its/setup/OakVirtualInstanceBuilder.java   | 28 ++++++++++++----
 4 files changed, 92 insertions(+), 18 deletions(-)

diff --git a/src/main/java/org/apache/sling/discovery/oak/Config.java b/src/main/java/org/apache/sling/discovery/oak/Config.java
index d2eaa4a..9b8cd11 100644
--- a/src/main/java/org/apache/sling/discovery/oak/Config.java
+++ b/src/main/java/org/apache/sling/discovery/oak/Config.java
@@ -73,6 +73,15 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
     public static final String DISCOVERY_LITE_CHECK_INTERVAL_KEY = "discoveryLiteCheckInterval";
     protected long discoveryLiteCheckInterval = DEFAULT_DISCOVERY_LITE_CHECK_INTERVAL;
     
+    /**
+     * If set to true a syncToken will be used on top of waiting for
+     * deactivating instances to be fully processed.
+     * If set to false, only deactivating instances will be waited for
+     * to be fully processed.
+     */
+    @Property(boolValue=true)
+    private static final String SYNC_TOKEN_ENABLED = "enableSyncToken";
+
     /** Configure the time (in seconds) which must be passed at minimum between sending TOPOLOGY_CHANGING/_CHANGED (avoid flooding). */
     public static final int DEFAULT_MIN_EVENT_DELAY = 3;
     @Property(intValue=DEFAULT_MIN_EVENT_DELAY)
@@ -194,6 +203,12 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
     /** the maximum backoff factor to be used for stable connectors **/
     private int backoffStableFactor = DEFAULT_BACKOFF_STABLE_FACTOR;
     
+    /**
+     * Whether, on top of waiting for deactivating instances,
+     * a syncToken should also be used
+     */
+    private boolean syncTokenEnabled;
+    
     @Activate
     protected void activate(final Map<String, Object> properties) {
 		logger.debug("activate: config activated.");
@@ -295,6 +310,7 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
         
         hmacEnabled = PropertiesUtil.toBoolean(properties.get(HMAC_ENABLED), true);
         encryptionEnabled = PropertiesUtil.toBoolean(properties.get(ENCRYPTION_ENABLED), false);
+        syncTokenEnabled = PropertiesUtil.toBoolean(properties.get(SYNC_TOKEN_ENABLED), true);
         sharedKey = PropertiesUtil.toString(properties.get(SHARED_KEY), null);
         keyInterval = PropertiesUtil.toLong(SHARED_KEY_INTERVAL, DEFAULT_SHARED_KEY_INTERVAL);
         
@@ -467,4 +483,8 @@ public class Config implements BaseConfig, DiscoveryLiteConfig {
         // TODO: currently hard coded
         return 1000;
     }
+    
+    public boolean getSyncTokenEnabled() {
+        return syncTokenEnabled;
+    }
 }
diff --git a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
index 084fe50..1a5c8d3 100644
--- a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
+++ b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
@@ -60,8 +60,11 @@ import org.apache.sling.discovery.commons.providers.DefaultClusterView;
 import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
 import org.apache.sling.discovery.commons.providers.ViewStateManager;
 import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
+import org.apache.sling.discovery.commons.providers.spi.ConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.ConsistencyServiceChain;
 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.discovery.commons.providers.spi.base.SyncTokenConsistencyService;
 import org.apache.sling.discovery.commons.providers.util.PropertyNameHelper;
 import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
 import org.apache.sling.discovery.oak.pinger.OakViewChecker;
@@ -129,7 +132,10 @@ public class OakDiscoveryService extends BaseDiscoveryService {
     private IdMapService idMapService;
     
     @Reference
-    private OakSyncTokenConsistencyService consistencyService;
+    private OakBacklogConsistencyService oakBacklogConsistencyService;
+
+    @Reference
+    private SyncTokenConsistencyService syncTokenConsistencyService;
 
     /** the slingId of the local instance **/
     private String slingId;
@@ -150,7 +156,8 @@ public class OakDiscoveryService extends BaseDiscoveryService {
             OakViewChecker connectorPinger,
             Scheduler scheduler,
             IdMapService idMapService,
-            OakSyncTokenConsistencyService consistencyService,
+            OakBacklogConsistencyService oakBacklogConsistencyService,
+            SyncTokenConsistencyService syncTokenConsistencyService,
             ResourceResolverFactory factory) {
         OakDiscoveryService discoService = new OakDiscoveryService();
         discoService.settingsService = settingsService;
@@ -161,7 +168,8 @@ public class OakDiscoveryService extends BaseDiscoveryService {
         discoService.oakViewChecker = connectorPinger;
         discoService.scheduler = scheduler;
         discoService.idMapService = idMapService;
-        discoService.consistencyService = consistencyService;
+        discoService.oakBacklogConsistencyService = oakBacklogConsistencyService;
+        discoService.syncTokenConsistencyService = syncTokenConsistencyService;
         discoService.resourceResolverFactory = factory;
         return discoService;
     }
@@ -201,7 +209,13 @@ public class OakDiscoveryService extends BaseDiscoveryService {
 
         slingId = settingsService.getSlingId();
 
-        //TODO: this should fail as bind can now run into viewStateManager==null
+        ConsistencyService consistencyService;
+        if (config.getSyncTokenEnabled()) {
+            consistencyService = new ConsistencyServiceChain(oakBacklogConsistencyService, syncTokenConsistencyService);
+        } else {
+            consistencyService = oakBacklogConsistencyService;
+            
+        }
         viewStateManager = ViewStateManagerFactory.newViewStateManager(viewStateManagerLock, consistencyService);
 
         if (config.getMinEventDelay()>0) {
diff --git a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
index acc75aa..97d8fa6 100644
--- a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
+++ b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
@@ -63,8 +63,10 @@ import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegis
 import org.apache.sling.discovery.base.connectors.announcement.CachedAnnouncement;
 import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
 import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorClientInformation;
+import org.apache.sling.discovery.commons.InstancesDiff;
+import org.apache.sling.discovery.commons.InstancesDiff.InstanceCollection;
 import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
-import org.apache.sling.discovery.commons.providers.spi.base.OakSyncTokenConsistencyService;
+import org.apache.sling.discovery.commons.providers.spi.base.OakBacklogConsistencyService;
 import org.osgi.framework.BundleContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -112,7 +114,7 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
     protected ResourceResolverFactory resourceResolverFactory;
 
     @Reference
-    private OakSyncTokenConsistencyService consistencyService;
+    private OakBacklogConsistencyService consistencyService;
 
     private TopologyView currentView;
     
@@ -588,9 +590,33 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
         if (event.getType() == Type.PROPERTIES_CHANGED) {
             this.currentView = event.getNewView();
 
+            StringBuilder sb = new StringBuilder();
+            InstancesDiff instanceDiff = new InstancesDiff(event.getOldView(), event.getNewView());
+            
+            // there shouldn't be any instances added, but for paranoia reason:
+            Collection<InstanceDescription> added = instanceDiff.added().get();
+            if (!added.isEmpty()) {
+                sb.append("instances were added:");
+                for (InstanceDescription instance : added) {
+                    sb.append(" ");
+                    sb.append(instance.getSlingId());
+                }
+                sb.append(".");
+            }
+            
+            // there shouldn't be any instances removed as well, but again for paranoia reason:
+            Collection<InstanceDescription> removed = instanceDiff.removed().get();
+            if (!removed.isEmpty()) {
+                sb.append("instances were removed:");
+                for (InstanceDescription instance : added) {
+                    sb.append(" ");
+                    sb.append(instance.getSlingId());
+                }
+                sb.append(".");
+            }
+            
             Set<InstanceDescription> newInstances = event.getNewView()
                     .getInstances();
-            StringBuilder sb = new StringBuilder();
             for (Iterator<InstanceDescription> it = newInstances.iterator(); it
                     .hasNext();) {
                 final InstanceDescription newInstanceDescription = it.next();
@@ -601,8 +627,8 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
                             + newInstanceDescription
                             + " in oldview.. event="
                             + event);
-                    addEventLog(event.getType(), event.getType().toString()+" (new instance "+newInstanceDescription.getSlingId()+" not found in old view)");
-                    return;
+                    sb.append("did not find instance in old view: " + newInstanceDescription.getSlingId() + ".");
+                    continue;
                 }
 
                 Map<String, String> oldProps = oldInstanceDescription
@@ -616,7 +642,7 @@ public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implement
                     }
                     sb.append("on instance "
                             + newInstanceDescription.getSlingId() + (newInstanceDescription.isLeader() ? " [isLeader]" : "") 
-                            + ": " + diff);
+                            + ": " + diff + ". ");
                 }
             }
 
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
index 2080c0e..b25d2ff 100644
--- a/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
+++ b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
@@ -33,8 +33,9 @@ import org.apache.sling.discovery.base.its.setup.VirtualInstance;
 import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
 import org.apache.sling.discovery.base.its.setup.mock.MockFactory;
 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.discovery.commons.providers.spi.base.RepositoryTestHelper;
+import org.apache.sling.discovery.commons.providers.spi.base.SyncTokenConsistencyService;
 import org.apache.sling.discovery.oak.OakDiscoveryService;
 import org.apache.sling.discovery.oak.cluster.OakClusterViewService;
 import org.apache.sling.discovery.oak.pinger.OakViewChecker;
@@ -53,7 +54,8 @@ public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
     private IdMapService idMapService;
     private OakViewChecker oakViewChecker;
     private SimulatedLeaseCollection leaseCollection;
-    private OakSyncTokenConsistencyService consistencyService;
+    private OakBacklogConsistencyService consistencyService;
+    private SyncTokenConsistencyService syncTokenConsistencyService;
     
     @Override
     public VirtualInstanceBuilder createNewRepository() throws Exception {
@@ -187,15 +189,26 @@ public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
         return OakViewChecker.testConstructor(getSlingSettingsService(), getResourceResolverFactory(), getConnectorRegistry(), getAnnouncementRegistry(), getScheduler(), getConfig());
     }
 
-    private OakSyncTokenConsistencyService getConsistencyService() throws Exception {
+    private OakBacklogConsistencyService getOakBacklogConsistencyService() throws Exception {
         if (consistencyService == null) {
-            consistencyService = createConsistencyService();
+            consistencyService = createOakBacklogConsistencyService();
         }
         return consistencyService;
     }
     
-    private OakSyncTokenConsistencyService createConsistencyService() {
-        return OakSyncTokenConsistencyService.testConstructorAndActivate(getConfig(), getIdMapService(), getSlingSettingsService(), getResourceResolverFactory());
+    private OakBacklogConsistencyService createOakBacklogConsistencyService() {
+        return OakBacklogConsistencyService.testConstructorAndActivate(getConfig(), getIdMapService(), getSlingSettingsService(), getResourceResolverFactory());
+    }
+
+    private SyncTokenConsistencyService getSyncTokenConsistencyService() throws Exception {
+        if (syncTokenConsistencyService == null) {
+            syncTokenConsistencyService = createSyncTokenConsistencyService();
+        }
+        return syncTokenConsistencyService;
+    }
+    
+    private SyncTokenConsistencyService createSyncTokenConsistencyService() {
+        return SyncTokenConsistencyService.testConstructorAndActivate(getConfig(), getResourceResolverFactory(), getSlingSettingsService());
     }
 
     @Override
@@ -209,7 +222,8 @@ public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
                 getOakViewChecker(), 
                 getScheduler(), 
                 getIdMapService(), 
-                getConsistencyService(), 
+                getOakBacklogConsistencyService(),
+                getSyncTokenConsistencyService(),
                 getResourceResolverFactory());
     }
 

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

[sling-org-apache-sling-discovery-oak] 01/12: SLING-4603 : added discovery.oak to the project - this is based on discovery.base, which handles topology connectors and contains a few other base classes that were factored out of discovery.impl for exactly this reuse

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.oak-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-oak.git

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

    SLING-4603 : added discovery.oak to the project - this is based on discovery.base, which handles topology connectors and contains a few other base classes that were factored out of discovery.impl for exactly this reuse
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak@1709867 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |  323 ++++++
 .../org/apache/sling/discovery/oak/Config.java     |  470 +++++++++
 .../sling/discovery/oak/OakDiscoveryService.java   |  642 ++++++++++++
 .../discovery/oak/TopologyWebConsolePlugin.java    | 1048 ++++++++++++++++++++
 .../oak/cluster/OakClusterViewService.java         |  227 +++++
 .../sling/discovery/oak/pinger/OakViewChecker.java |  322 ++++++
 .../OSGI-INF/metatype/metatype.properties          |  121 +++
 .../discovery/oak/OakDiscoveryServiceTest.java     |  116 +++
 .../discovery/oak/its/OakClusterLoadTest.java      |   32 +
 .../sling/discovery/oak/its/OakClusterTest.java    |   32 +
 .../discovery/oak/its/OakSingleInstanceTest.java   |   32 +
 .../discovery/oak/its/OakTopologyEventTest.java    |   43 +
 .../discovery/oak/its/setup/OakTestConfig.java     |   80 ++
 .../oak/its/setup/OakVirtualInstanceBuilder.java   |  259 +++++
 .../discovery/oak/its/setup/SimulatedLease.java    |   59 ++
 .../oak/its/setup/SimulatedLeaseCollection.java    |  102 ++
 src/test/resources/log4j.properties                |   26 +
 17 files changed, 3934 insertions(+)

diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5ee0709
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,323 @@
+<?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>25</version>
+        <relativePath />
+    </parent>
+
+    <artifactId>org.apache.sling.discovery.oak</artifactId>
+    <packaging>bundle</packaging>
+    <version>1.0.0-SNAPSHOT</version>
+
+    <name>Apache Sling Oak-Based Discovery Service</name>
+    <description>Implementation of Apache Sling Discovery based on Jackrabbit Oak using its discovery-lite descriptor for in-cluster view detection and a TopologyView through HTTP POST heartbeats announcing sub-topologies to each other.</description>
+
+    <scm>
+        <connection>scm:svn:http://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak</connection>
+        <developerConnection>scm:svn:https://svn.apache.org/repos/asf/sling/trunk/bundles/extensions/discovery/oak</developerConnection>
+        <url>http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/discovery/oak</url>
+    </scm>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                 <artifactId>maven-surefire-plugin</artifactId>
+	  	 <configuration>
+       		 	<redirectTestOutputToFile>false</redirectTestOutputToFile>
+       		 	<argLine>-Xmx2048m</argLine>
+        	 </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <configuration>
+                  <instructions>
+		            <Embed-Dependency>
+		              commons-net;inline=org/apache/commons/net/util/SubnetUtils*
+		            </Embed-Dependency>
+		          </instructions>
+		        </configuration>
+            </plugin>
+        </plugins>
+    </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>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+	        <version>1.6.1</version>
+        </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.sling</groupId>
+			<artifactId>org.apache.sling.jcr.api</artifactId>
+			<version>2.1.0</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.jackrabbit</groupId>
+			<artifactId>jackrabbit-api</artifactId>
+			<version>2.2.4</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.commons.osgi</artifactId>
+			<version>2.1.0</version>
+			<scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.settings</artifactId>
+			<version>1.2.2</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.discovery.commons</artifactId>
+			<version>1.0.0-SNAPSHOT</version>
+            <scope>provided</scope>
+		</dependency>
+        <!-- besides including discovery.commons' normal jar above, 
+              for testing a few test helper classes are also reused.
+              in order to achieve that, also adding a test/test-jar dependency: --> 
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.discovery.commons</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.discovery.base</artifactId>
+			<version>1.0.0-SNAPSHOT</version>
+            <scope>provided</scope>
+		</dependency>
+        <!-- besides including discovery.base' normal jar above, 
+              for testing a few test helper classes are also reused.
+              in order to achieve that, also adding a test/test-jar dependency: --> 
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.discovery.base</artifactId>
+            <version>1.0.0-SNAPSHOT</version>
+            <scope>test</scope>
+            <type>test-jar</type>
+        </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.scheduler</artifactId>
+        	<version>2.3.4</version>
+            <scope>provided</scope>
+        </dependency>
+		<dependency>
+			<groupId>org.apache.felix</groupId>
+			<artifactId>org.apache.felix.webconsole</artifactId>
+			<version>3.0.0</version>
+            <scope>provided</scope>
+		</dependency>
+	    <dependency>
+	      <groupId>org.eclipse.jetty</groupId>
+	      <artifactId>jetty-servlet</artifactId>
+	      <version>8.1.2.v20120308</version>
+	      <scope>test</scope>
+	    </dependency>
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.httpcomponents</groupId>
+			<artifactId>httpclient-osgi</artifactId>
+			<version>4.3.5</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>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.launchpad.api</artifactId>
+		    <version>1.1.0</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-io</groupId>
+            <artifactId>commons-io</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>commons-net</groupId>
+            <artifactId>commons-net</artifactId>
+            <version>3.3</version>
+            <scope>provided</scope>
+        </dependency>
+      <!-- Testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit-addons</groupId>
+            <artifactId>junit-addons</artifactId>
+            <version>1.4</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.jmock</groupId>
+            <artifactId>jmock-junit4</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-all</artifactId>
+            <version>1.9.5</version>
+            <scope>test</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>
+        
+        <!-- using log4j under slf4j to allow fine-grained logging config (see src/test/resources/log4j.properties) -->
+        <dependency>
+        	<groupId>org.slf4j</groupId>
+        	<artifactId>slf4j-log4j12</artifactId>
+        	<version>1.7.5</version>
+        	<scope>test</scope>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.sling</groupId>
+        	<artifactId>org.apache.sling.jcr.resource</artifactId>
+        	<version>2.3.8</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.sling</groupId>
+        	<artifactId>org.apache.sling.commons.threads</artifactId>
+        	<version>3.1.0</version>
+        	<type>bundle</type>
+        	<scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.tools</artifactId>
+            <version>1.0.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+          <groupId>org.apache.sling</groupId>
+          <artifactId>org.apache.sling.testing.sling-mock</artifactId>
+          <version>1.2.0</version>
+        </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>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-core</artifactId>
+            <version>1.3.7</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.jackrabbit</groupId>
+            <artifactId>oak-jcr</artifactId>
+            <version>1.3.7</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/src/main/java/org/apache/sling/discovery/oak/Config.java b/src/main/java/org/apache/sling/discovery/oak/Config.java
new file mode 100644
index 0000000..d2eaa4a
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/oak/Config.java
@@ -0,0 +1,470 @@
+/*
+ * 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.oak;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.apache.sling.discovery.base.connectors.BaseConfig;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Configuration object used as a central config point for the discovery service
+ * implementation
+ * <p>
+ * The properties are described below under.
+ */
+@Component(metatype = true, label="%config.name", description="%config.description")
+@Service(value = { Config.class, BaseConfig.class, DiscoveryLiteConfig.class })
+public class Config implements BaseConfig, DiscoveryLiteConfig {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** resource used to keep instance information such as last heartbeat, properties, incoming announcements **/
+    private static final String CLUSTERINSTANCES_RESOURCE = "clusterInstances";
+
+    /** resource used to store the sync tokens as part of a topology change **/
+    private static final String SYNC_TOKEN_RESOURCE = "syncTokens";
+
+    /** resource used to store the clusterNodeIds to slingIds map **/
+    private static final String ID_MAP_RESOURCE = "idMap";
+
+    /** Configure the timeout (in seconds) after which an instance is considered dead/crashed. */
+    public static final long DEFAULT_TOPOLOGY_CONNECTOR_TIMEOUT = 120;
+    @Property(longValue=DEFAULT_TOPOLOGY_CONNECTOR_TIMEOUT)
+    public static final String TOPOLOGY_CONNECTOR_TIMEOUT_KEY = "connectorPingTimeout";
+    protected long connectorPingTimeout = DEFAULT_TOPOLOGY_CONNECTOR_TIMEOUT;
+
+    /** Configure the interval (in seconds) according to which the heartbeats are exchanged in the topology. */
+    public static final long DEFAULT_TOPOLOGY_CONNECTOR_INTERVAL = 30;
+    @Property(longValue=DEFAULT_TOPOLOGY_CONNECTOR_INTERVAL)
+    public static final String TOPOLOGY_CONNECTOR_INTERVAL_KEY = "connectorPingInterval";
+    protected long connectorPingInterval = DEFAULT_TOPOLOGY_CONNECTOR_INTERVAL;
+    
+    /** Configure the interval (in seconds) according to which the heartbeats are exchanged in the topology. */
+    public static final long DEFAULT_DISCOVERY_LITE_CHECK_INTERVAL = 2;
+    @Property(longValue=DEFAULT_DISCOVERY_LITE_CHECK_INTERVAL)
+    public static final String DISCOVERY_LITE_CHECK_INTERVAL_KEY = "discoveryLiteCheckInterval";
+    protected long discoveryLiteCheckInterval = DEFAULT_DISCOVERY_LITE_CHECK_INTERVAL;
+    
+    /** Configure the time (in seconds) which must be passed at minimum between sending TOPOLOGY_CHANGING/_CHANGED (avoid flooding). */
+    public static final int DEFAULT_MIN_EVENT_DELAY = 3;
+    @Property(intValue=DEFAULT_MIN_EVENT_DELAY)
+    public static final String MIN_EVENT_DELAY_KEY = "minEventDelay";
+    protected int minEventDelay = DEFAULT_MIN_EVENT_DELAY;
+
+    /** Configure the socket connect timeout for topology connectors. */
+    public static final int DEFAULT_SOCKET_CONNECT_TIMEOUT = 10;
+    @Property(intValue=DEFAULT_SOCKET_CONNECT_TIMEOUT)
+    public static final String SOCKET_CONNECT_TIMEOUT_KEY = "socketConnectTimeout";
+    private int socketConnectTimeout = DEFAULT_SOCKET_CONNECT_TIMEOUT;
+
+    /** Configure the socket read timeout (SO_TIMEOUT) for topology connectors. */
+    public static final int DEFAULT_SO_TIMEOUT = 10;
+    @Property(intValue=DEFAULT_SO_TIMEOUT)
+    public static final String SO_TIMEOUT_KEY = "soTimeout";
+    private int soTimeout = DEFAULT_SO_TIMEOUT;
+
+    /** URLs where to join a topology, eg http://localhost:4502/libs/sling/topology/connector */
+    @Property(cardinality=1024)
+    public static final String TOPOLOGY_CONNECTOR_URLS_KEY = "topologyConnectorUrls";
+    private URL[] topologyConnectorUrls = {null};
+
+    /** list of ips and/or hostnames which are allowed to connect to /libs/sling/topology/connector */
+    private static final String[] DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST = {"localhost","127.0.0.1"};
+    @Property(value={"localhost","127.0.0.1"})
+    public static final String TOPOLOGY_CONNECTOR_WHITELIST_KEY = "topologyConnectorWhitelist";
+    protected String[] topologyConnectorWhitelist = DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST;
+
+    /** Path of resource where to keep discovery information, e.g /var/discovery/oak/ */
+    private static final String DEFAULT_DISCOVERY_RESOURCE_PATH = "/var/discovery/oak/";
+    @Property(value=DEFAULT_DISCOVERY_RESOURCE_PATH, propertyPrivate=true)
+    public static final String DISCOVERY_RESOURCE_PATH_KEY = "discoveryResourcePath";
+    protected String discoveryResourcePath = DEFAULT_DISCOVERY_RESOURCE_PATH;
+
+    /**
+     * If set to true, local-loops of topology connectors are automatically stopped when detected so.
+     */
+    @Property(boolValue=false)
+    private static final String AUTO_STOP_LOCAL_LOOP_ENABLED = "autoStopLocalLoopEnabled";
+
+    /**
+     * If set to true, request body will be gzipped - only works if counter-part accepts gzip-requests!
+     */
+    @Property(boolValue=false)
+    private static final String GZIP_CONNECTOR_REQUESTS_ENABLED = "gzipConnectorRequestsEnabled";
+
+    /**
+     * If set to true, hmac is enabled and the white list is disabled.
+     */
+    @Property(boolValue=false)
+    private static final String HMAC_ENABLED = "hmacEnabled";
+
+    /**
+     * If set to true, and the whitelist is disabled, messages will be encrypted.
+     */
+    @Property(boolValue=false)
+    private static final String ENCRYPTION_ENABLED = "enableEncryption";
+
+    /**
+     * The value fo the shared key, shared amongst all instances in the same cluster.
+     */
+    @Property
+    private static final String SHARED_KEY = "sharedKey";
+
+    /**
+     * The default lifetime of a HMAC shared key in ms. (4h)
+     */
+    private static final long DEFAULT_SHARED_KEY_INTERVAL = 3600*1000*4;
+
+    @Property(longValue=DEFAULT_SHARED_KEY_INTERVAL)
+    private static final String SHARED_KEY_INTERVAL = "hmacSharedKeyTTL";
+    
+    /**
+     * The property for defining the backoff factor for standby (loop) connectors
+     */
+    @Property
+    private static final String BACKOFF_STANDBY_FACTOR = "backoffStandbyFactor";
+    private static final int DEFAULT_BACKOFF_STANDBY_FACTOR = 5;
+    
+    /**
+     * The property for defining the maximum backoff factor for stable connectors
+     */
+    @Property
+    private static final String BACKOFF_STABLE_FACTOR = "backoffStableFactor";
+    private static final int DEFAULT_BACKOFF_STABLE_FACTOR = 5;
+
+    /** True when auto-stop of a local-loop is enabled. Default is false. **/
+    private boolean autoStopLocalLoopEnabled;
+    
+    /**
+     * True when the hmac is enabled and signing is disabled.
+     */
+    private boolean hmacEnabled;
+
+    /**
+     * the shared key.
+     */
+    private String sharedKey;
+
+    /**
+     * The key interval.
+     */
+    private long keyInterval;
+
+    /**
+     * true when encryption is enabled.
+     */
+    private boolean encryptionEnabled;
+    
+    /**
+     * true when topology connector requests should be gzipped
+     */
+    private boolean gzipConnectorRequestsEnabled;
+
+    /** the backoff factor to be used for standby (loop) connectors **/
+    private int backoffStandbyFactor = DEFAULT_BACKOFF_STANDBY_FACTOR;
+    
+    /** the maximum backoff factor to be used for stable connectors **/
+    private int backoffStableFactor = DEFAULT_BACKOFF_STABLE_FACTOR;
+    
+    @Activate
+    protected void activate(final Map<String, Object> properties) {
+		logger.debug("activate: config activated.");
+        configure(properties);
+    }
+
+    protected void configure(final Map<String, Object> properties) {
+        this.connectorPingTimeout = PropertiesUtil.toLong(
+                properties.get(TOPOLOGY_CONNECTOR_TIMEOUT_KEY),
+                DEFAULT_TOPOLOGY_CONNECTOR_TIMEOUT);
+        logger.debug("configure: connectorPingTimeout='{}'", 
+                this.connectorPingTimeout);
+
+        this.connectorPingInterval = PropertiesUtil.toLong(
+                properties.get(TOPOLOGY_CONNECTOR_INTERVAL_KEY),
+                DEFAULT_TOPOLOGY_CONNECTOR_INTERVAL);
+        logger.debug("configure: connectorPingInterval='{}'",
+                this.connectorPingInterval);
+        
+        this.discoveryLiteCheckInterval = PropertiesUtil.toLong(
+                properties.get(DISCOVERY_LITE_CHECK_INTERVAL_KEY),
+                DEFAULT_DISCOVERY_LITE_CHECK_INTERVAL);
+        logger.debug("configure: discoveryLiteCheckInterval='{}'",
+                this.discoveryLiteCheckInterval);
+                
+
+        this.minEventDelay = PropertiesUtil.toInteger(
+                properties.get(MIN_EVENT_DELAY_KEY),
+                DEFAULT_MIN_EVENT_DELAY);
+        logger.debug("configure: minEventDelay='{}'",
+                this.minEventDelay);
+        
+        this.socketConnectTimeout = PropertiesUtil.toInteger(
+                properties.get(SOCKET_CONNECT_TIMEOUT_KEY),
+                DEFAULT_SOCKET_CONNECT_TIMEOUT);
+        logger.debug("configure: socketConnectTimeout='{}'",
+                this.socketConnectTimeout);
+        
+        this.soTimeout = PropertiesUtil.toInteger(
+                properties.get(SO_TIMEOUT_KEY),
+                DEFAULT_SO_TIMEOUT);
+        logger.debug("configure: soTimeout='{}'",
+                this.soTimeout);
+        
+        
+        String[] topologyConnectorUrlsStr = PropertiesUtil.toStringArray(
+                properties.get(TOPOLOGY_CONNECTOR_URLS_KEY), null);
+        if (topologyConnectorUrlsStr!=null && topologyConnectorUrlsStr.length > 0) {
+            List<URL> urls = new LinkedList<URL>();
+            for (int i = 0; i < topologyConnectorUrlsStr.length; i++) {
+                String anUrlStr = topologyConnectorUrlsStr[i];
+                try {
+                	if (anUrlStr!=null && anUrlStr.length()>0) {
+	                    URL url = new URL(anUrlStr);
+	                    logger.debug("configure: a topologyConnectorbUrl='{}'",
+	                            url);
+	                    urls.add(url);
+                	}
+                } catch (MalformedURLException e) {
+                    logger.error("configure: could not set a topologyConnectorUrl: " + e,
+                            e);
+                }
+            }
+            if (urls.size()>0) {
+                this.topologyConnectorUrls = urls.toArray(new URL[urls.size()]);
+                logger.debug("configure: number of topologyConnectorUrls='{}''",
+                        urls.size());
+            } else {
+                this.topologyConnectorUrls = null;
+                logger.debug("configure: no (valid) topologyConnectorUrls configured");
+            }
+        } else {
+            this.topologyConnectorUrls = null;
+            logger.debug("configure: no (valid) topologyConnectorUrls configured");
+        }
+        this.topologyConnectorWhitelist = PropertiesUtil.toStringArray(
+                properties.get(TOPOLOGY_CONNECTOR_WHITELIST_KEY),
+                DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST);
+        logger.debug("configure: topologyConnectorWhitelist='{}'",
+                this.topologyConnectorWhitelist);
+
+        this.discoveryResourcePath = PropertiesUtil.toString(
+                properties.get(DISCOVERY_RESOURCE_PATH_KEY),
+                "");
+        while(this.discoveryResourcePath.endsWith("/")) {
+            this.discoveryResourcePath = this.discoveryResourcePath.substring(0,
+                    this.discoveryResourcePath.length()-1);
+        }
+        this.discoveryResourcePath = this.discoveryResourcePath + "/";
+        if (this.discoveryResourcePath==null || this.discoveryResourcePath.length()<=1) {
+            // if the path is empty, or /, then use the default
+            this.discoveryResourcePath = DEFAULT_DISCOVERY_RESOURCE_PATH;
+        }
+        logger.debug("configure: discoveryResourcePath='{}'",
+                this.discoveryResourcePath);
+
+        autoStopLocalLoopEnabled = PropertiesUtil.toBoolean(properties.get(AUTO_STOP_LOCAL_LOOP_ENABLED), false);
+        gzipConnectorRequestsEnabled = PropertiesUtil.toBoolean(properties.get(GZIP_CONNECTOR_REQUESTS_ENABLED), false);
+        
+        hmacEnabled = PropertiesUtil.toBoolean(properties.get(HMAC_ENABLED), true);
+        encryptionEnabled = PropertiesUtil.toBoolean(properties.get(ENCRYPTION_ENABLED), false);
+        sharedKey = PropertiesUtil.toString(properties.get(SHARED_KEY), null);
+        keyInterval = PropertiesUtil.toLong(SHARED_KEY_INTERVAL, DEFAULT_SHARED_KEY_INTERVAL);
+        
+        backoffStandbyFactor = PropertiesUtil.toInteger(properties.get(BACKOFF_STANDBY_FACTOR), 
+                DEFAULT_BACKOFF_STANDBY_FACTOR);
+        backoffStableFactor = PropertiesUtil.toInteger(properties.get(BACKOFF_STABLE_FACTOR), 
+                DEFAULT_BACKOFF_STABLE_FACTOR);
+    }
+
+    /**
+     * Returns the socket connect() timeout used by the topology connector, 0 disables the timeout
+     * @return the socket connect() timeout used by the topology connector, 0 disables the timeout
+     */
+    public int getSocketConnectTimeout() {
+        return socketConnectTimeout;
+    }
+
+    /**
+     * Returns the socket read timeout (SO_TIMEOUT) used by the topology connector, 0 disables the timeout
+     * @return the socket read timeout (SO_TIMEOUT) used by the topology connector, 0 disables the timeout
+     */
+    public int getSoTimeout() {
+        return soTimeout;
+    }
+    
+    /**
+     * Returns the minimum time (in seconds) between sending TOPOLOGY_CHANGING/_CHANGED events - to avoid flooding
+     * @return the minimum time (in seconds) between sending TOPOLOGY_CHANGING/_CHANGED events - to avoid flooding
+     */
+    public int getMinEventDelay() {
+        return minEventDelay;
+    }
+
+    /**
+     * Returns the URLs to which to open a topology connector - or null/empty if no topology connector
+     * is configured (default is null)
+     * @return the URLs to which to open a topology connector - or null/empty if no topology connector
+     * is configured
+     */
+    public URL[] getTopologyConnectorURLs() {
+        return topologyConnectorUrls;
+    }
+
+    /**
+     * Returns a comma separated list of hostnames and/or ip addresses which are allowed as
+     * remote hosts to open connections to the topology connector servlet
+     * @return a comma separated list of hostnames and/or ip addresses which are allowed as
+     * remote hosts to open connections to the topology connector servlet
+     */
+    public String[] getTopologyConnectorWhitelist() {
+        return topologyConnectorWhitelist;
+    }
+    
+    protected String getDiscoveryResourcePath() {
+        return discoveryResourcePath;
+    }
+
+    /**
+     * Returns the resource path where cluster instance informations are stored.
+     * @return the resource path where cluster instance informations are stored
+     */
+    public String getClusterInstancesPath() {
+        return getDiscoveryResourcePath() + CLUSTERINSTANCES_RESOURCE;
+    }
+
+    @Override
+    public String getSyncTokenPath() {
+        return getDiscoveryResourcePath() + SYNC_TOKEN_RESOURCE;
+    }
+
+    @Override
+    public String getIdMapPath() {
+        return getDiscoveryResourcePath() + ID_MAP_RESOURCE;
+    }
+
+    /**
+     * @return true if hmac is enabled.
+     */
+    public boolean isHmacEnabled() {
+        return hmacEnabled;
+    }
+
+    /**
+     * @return the shared key
+     */
+    public String getSharedKey() {
+        return sharedKey;
+    }
+
+    /**
+     * @return the interval of the shared key for hmac.
+     */
+    public long getKeyInterval() {
+        return keyInterval;
+    }
+
+    /**
+     * @return true if encryption is enabled.
+     */
+    public boolean isEncryptionEnabled() {
+        return encryptionEnabled;
+    }
+    
+    /**
+     * @return true if requests on the topology connector should be gzipped
+     * (which only works if the server accepts that.. ie discovery.impl 1.0.4+)
+     */
+    public boolean isGzipConnectorRequestsEnabled() {
+        return gzipConnectorRequestsEnabled;
+    }
+    
+    /**
+     * @return true if the auto-stopping of local-loop topology connectors is enabled.
+     */
+    public boolean isAutoStopLocalLoopEnabled() {
+        return autoStopLocalLoopEnabled;
+    }
+
+    /**
+     * Returns the backoff factor to be used for standby (loop) connectors
+     * @return the backoff factor to be used for standby (loop) connectors
+     */
+    public int getBackoffStandbyFactor() {
+        return backoffStandbyFactor;
+    }
+
+    /**
+     * Returns the (maximum) backoff factor to be used for stable connectors
+     * @return the (maximum) backoff factor to be used for stable connectors
+     */
+    public int getBackoffStableFactor() {
+        return backoffStableFactor;
+    }
+
+    /**
+     * Returns the backoff interval for standby (loop) connectors in seconds
+     * @return the backoff interval for standby (loop) connectors in seconds
+     */
+    public long getBackoffStandbyInterval() {
+        final int factor = getBackoffStandbyFactor();
+        if (factor<=1) {
+            return -1;
+        } else {
+            return factor * getConnectorPingInterval();
+        }
+    }
+
+    @Override
+    public long getConnectorPingInterval() {
+        return connectorPingInterval;
+    }
+    
+    @Override
+    public long getConnectorPingTimeout() {
+        return connectorPingTimeout;
+    }
+    
+    public long getDiscoveryLiteCheckInterval() {
+        return discoveryLiteCheckInterval;
+    }
+    
+    @Override
+    public long getBgTimeoutMillis() {
+        // TODO: currently hard coded
+        return -1;
+    }
+
+    @Override
+    public long getBgIntervalMillis() {
+        // TODO: currently hard coded
+        return 1000;
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
new file mode 100644
index 0000000..084fe50
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/oak/OakDiscoveryService.java
@@ -0,0 +1,642 @@
+/*
+ * 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.oak;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
+
+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.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+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.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.PropertyProvider;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.base.commons.BaseDiscoveryService;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.commons.DefaultTopologyView;
+import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
+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.ViewStateManager;
+import org.apache.sling.discovery.commons.providers.base.ViewStateManagerFactory;
+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.util.PropertyNameHelper;
+import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
+import org.apache.sling.discovery.oak.pinger.OakViewChecker;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This implementation of the cross-cluster service uses the view manager
+ * implementation for detecting changes in a cluster and only supports one
+ * cluster (of which this instance is part of).
+ */
+@Component(immediate = true)
+@Service(value = { DiscoveryService.class, OakDiscoveryService.class })
+public class OakDiscoveryService extends BaseDiscoveryService {
+
+    private final static Logger logger = LoggerFactory.getLogger(OakDiscoveryService.class);
+
+    @Reference
+    private SlingSettingsService settingsService;
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, referenceInterface = TopologyEventListener.class)
+    private TopologyEventListener[] eventListeners = new TopologyEventListener[0];
+    
+    /**
+     * All property providers.
+     */
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, referenceInterface = PropertyProvider.class, updated = "updatedPropertyProvider")
+    private List<ProviderInfo> providerInfos = new ArrayList<ProviderInfo>();
+
+    /** lock object used for synching bind/unbind and topology event sending **/
+    private final Object lock = new Object();
+
+    /**
+     * whether or not this service is activated - necessary to avoid sending
+     * events to discovery awares before activate is done
+     **/
+    private boolean activated = false;
+
+    @Reference
+    private ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    private Scheduler scheduler;
+
+    @Reference
+    private OakViewChecker oakViewChecker;
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private ConnectorRegistry connectorRegistry;
+
+    @Reference
+    private ClusterViewService clusterViewService;
+
+    @Reference
+    private Config config;
+
+    @Reference
+    private IdMapService idMapService;
+    
+    @Reference
+    private OakSyncTokenConsistencyService consistencyService;
+
+    /** the slingId of the local instance **/
+    private String slingId;
+
+    private ServiceRegistration mbeanRegistration;
+
+    private ViewStateManager viewStateManager;
+
+    private final ReentrantLock viewStateManagerLock = new ReentrantLock(); 
+    
+    private final List<TopologyEventListener> pendingListeners = new LinkedList<TopologyEventListener>();
+    
+    public static OakDiscoveryService testConstructor(SlingSettingsService settingsService,
+            AnnouncementRegistry announcementRegistry,
+            ConnectorRegistry connectorRegistry,
+            ClusterViewService clusterViewService,
+            Config config,
+            OakViewChecker connectorPinger,
+            Scheduler scheduler,
+            IdMapService idMapService,
+            OakSyncTokenConsistencyService consistencyService,
+            ResourceResolverFactory factory) {
+        OakDiscoveryService discoService = new OakDiscoveryService();
+        discoService.settingsService = settingsService;
+        discoService.announcementRegistry = announcementRegistry;
+        discoService.connectorRegistry = connectorRegistry;
+        discoService.clusterViewService = clusterViewService;
+        discoService.config = config;
+        discoService.oakViewChecker = connectorPinger;
+        discoService.scheduler = scheduler;
+        discoService.idMapService = idMapService;
+        discoService.consistencyService = consistencyService;
+        discoService.resourceResolverFactory = factory;
+        return discoService;
+    }
+
+    protected void handleIsolatedFromTopology() {
+        if (oakViewChecker!=null) {
+            // SLING-5030 part 2: when we detect being isolated we should
+            // step at the end of the leader-election queue and 
+            // that can be achieved by resetting the leaderElectionId
+            // (which will in turn take effect on the next round of
+            // voting, or also double-checked when the local instance votes)
+            //
+            //TODO:
+            // Note that when the local instance doesn't notice
+            // an 'ISOLATED_FROM_TOPOLOGY' case, then the leaderElectionId
+            // will not be reset. Which means that it then could potentially
+            // regain leadership.
+            if (oakViewChecker.resetLeaderElectionId()) {
+                logger.info("getTopology: reset leaderElectionId to force this instance to the end of the instance order (thus incl not to remain leader)");
+            }
+        }
+    }
+    
+    /**
+     * Activate this service
+     */
+    @Activate
+    protected void activate(final BundleContext bundleContext) {
+        logger.debug("OakDiscoveryService activating...");
+
+        if (settingsService == null) {
+            throw new IllegalStateException("settingsService not found");
+        }
+        if (oakViewChecker == null) {
+            throw new IllegalStateException("heartbeatHandler not found");
+        }
+
+        slingId = settingsService.getSlingId();
+
+        //TODO: this should fail as bind can now run into viewStateManager==null
+        viewStateManager = ViewStateManagerFactory.newViewStateManager(viewStateManagerLock, consistencyService);
+
+        if (config.getMinEventDelay()>0) {
+            viewStateManager.installMinEventDelayHandler(this, scheduler, config.getMinEventDelay());
+        }
+
+        final String isolatedClusterId = UUID.randomUUID().toString();
+        {
+            // create a pre-voting/isolated topologyView which would be used
+            // until the first voting has finished.
+            // this way for the single-instance case the clusterId can
+            // remain the same between a getTopology() that is invoked before
+            // the first TOPOLOGY_INIT and afterwards
+            DefaultClusterView isolatedCluster = new DefaultClusterView(isolatedClusterId);
+            Map<String, String> emptyProperties = new HashMap<String, String>();
+            DefaultInstanceDescription isolatedInstance = 
+                    new DefaultInstanceDescription(isolatedCluster, true, true, slingId, emptyProperties);
+            Collection<InstanceDescription> col = new ArrayList<InstanceDescription>();
+            col.add(isolatedInstance);
+            final DefaultTopologyView topology = new DefaultTopologyView();
+            topology.addInstances(col);
+            topology.setNotCurrent();
+            setOldView(topology);
+        }
+        setOldView((DefaultTopologyView) getTopology());
+        getOldView().setNotCurrent();
+
+        // make sure the first heartbeat is issued as soon as possible - which
+        // is right after this service starts. since the two (discoveryservice
+        // and heartbeatHandler need to know each other, the discoveryservice
+        // is passed on to the heartbeatHandler in this initialize call).
+        oakViewChecker.initialize(this);
+
+        viewStateManagerLock.lock();
+        try{
+            viewStateManager.handleActivated();
+
+            doUpdateProperties();
+
+            DefaultTopologyView newView = (DefaultTopologyView) getTopology();
+            if (newView.isCurrent()) {
+                viewStateManager.handleNewView(newView);
+            } else {
+                // SLING-3750: just issue a log.info about the delaying
+                logger.info("activate: this instance is in isolated mode and must yet finish voting before it can send out TOPOLOGY_INIT.");
+            }
+            activated = true;
+            setOldView(newView);
+            
+            // in case bind got called before activate we now have pending listeners,
+            // bind them to the viewstatemanager too
+            for (TopologyEventListener listener : pendingListeners) {
+                viewStateManager.bind(listener);
+            }
+            pendingListeners.clear();
+        } finally {
+            if (viewStateManagerLock!=null) {
+                viewStateManagerLock.unlock();
+            }
+        }
+
+        URL[] topologyConnectorURLs = config.getTopologyConnectorURLs();
+        if (topologyConnectorURLs != null) {
+            for (int i = 0; i < topologyConnectorURLs.length; i++) {
+                final URL aURL = topologyConnectorURLs[i];
+                if (aURL!=null) {
+                	try{
+                		logger.info("activate: registering outgoing topology connector to "+aURL);
+                		connectorRegistry.registerOutgoingConnector(clusterViewService, aURL);
+                	} catch (final Exception e) {
+                		logger.info("activate: could not register url: "+aURL+" due to: "+e, e);
+                	}
+                }
+            }
+        }
+        
+        logger.debug("OakDiscoveryService activated.");
+    }
+
+    /**
+     * Deactivate this service
+     */
+    @Deactivate
+    protected void deactivate() {
+        logger.debug("OakDiscoveryService deactivated.");
+        viewStateManagerLock.lock();
+        try{
+            viewStateManager.handleDeactivated();
+            
+            activated = false;
+        } finally {
+            if (viewStateManagerLock!=null) {
+                viewStateManagerLock.unlock();
+            }
+        }
+        try{
+            if ( this.mbeanRegistration != null ) {
+                this.mbeanRegistration.unregister();
+                this.mbeanRegistration = null;
+            }
+        } catch(Exception e) {
+            logger.error("deactivate: Error on unregister: "+e, e);
+        }
+    }
+
+    /**
+     * bind a topology event listener
+     */
+    protected void bindTopologyEventListener(final TopologyEventListener eventListener) {
+        viewStateManagerLock.lock();
+        try{
+            if (!activated) {
+                pendingListeners.add(eventListener);
+            } else {
+                viewStateManager.bind(eventListener);
+            }
+        } finally {
+            if (viewStateManagerLock!=null) {
+                viewStateManagerLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Unbind a topology event listener
+     */
+    protected void unbindTopologyEventListener(final TopologyEventListener eventListener) {
+        viewStateManagerLock.lock();
+        try{
+            if (!activated) {
+                pendingListeners.remove(eventListener);
+            } else {
+                viewStateManager.unbind(eventListener);
+            }
+        } finally {
+            if (viewStateManagerLock!=null) {
+                viewStateManagerLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Bind a new property provider.
+     */
+    protected void bindPropertyProvider(final PropertyProvider propertyProvider,
+                                        final Map<String, Object> props) {
+        logger.debug("bindPropertyProvider: Binding PropertyProvider {}",
+                propertyProvider);
+
+        synchronized (lock) {
+            this.bindPropertyProviderInteral(propertyProvider, props);
+        }
+    }
+
+    /**
+     * Bind a new property provider.
+     */
+    private void bindPropertyProviderInteral(final PropertyProvider propertyProvider,
+            final Map<String, Object> props) {
+        final ProviderInfo info = new ProviderInfo(propertyProvider, props);
+        this.providerInfos.add(info);
+        Collections.sort(this.providerInfos);
+        this.doUpdateProperties();
+        handlePotentialTopologyChange();
+    }
+
+    /**
+     * Update a property provider.
+     */
+    protected void updatedPropertyProvider(final PropertyProvider propertyProvider,
+                                           final Map<String, Object> props) {
+        logger.debug("bindPropertyProvider: Updating PropertyProvider {}",
+                propertyProvider);
+
+        synchronized (lock) {
+           this.unbindPropertyProviderInternal(propertyProvider, props, false);
+           this.bindPropertyProviderInteral(propertyProvider, props);
+        }
+    }
+
+    /**
+     * Unbind a property provider
+     */
+    protected void unbindPropertyProvider(final PropertyProvider propertyProvider,
+                                          final Map<String, Object> props) {
+        logger.debug("unbindPropertyProvider: Releasing PropertyProvider {}",
+                propertyProvider);
+        synchronized (lock) {
+            this.unbindPropertyProviderInternal(propertyProvider, props, true);
+        }
+    }
+
+    /**
+     * Unbind a property provider
+     */
+    private void unbindPropertyProviderInternal(
+            final PropertyProvider propertyProvider,
+            final Map<String, Object> props, final boolean update) {
+
+        final ProviderInfo info = new ProviderInfo(propertyProvider, props);
+        if ( this.providerInfos.remove(info) && update ) {
+            this.doUpdateProperties();
+            this.handlePotentialTopologyChange();
+        }
+    }
+
+    /**
+     * Update the properties by inquiring the PropertyProvider's current values.
+     * <p>
+     * This method is invoked regularly by the heartbeatHandler.
+     * The properties are stored in the repository under Config.getClusterInstancesPath()
+     * and announced in the topology.
+     * <p>
+     * @see Config#getClusterInstancesPath()
+     */
+    private void doUpdateProperties() {
+        if (resourceResolverFactory == null) {
+            // cannot update the properties then..
+            logger.debug("doUpdateProperties: too early to update the properties. resourceResolverFactory not yet set.");
+            return;
+        } else {
+            logger.debug("doUpdateProperties: updating properties now..");
+        }
+
+        final Map<String, String> newProps = new HashMap<String, String>();
+        for (final ProviderInfo info : this.providerInfos) {
+            info.refreshProperties();
+            newProps.putAll(info.properties);
+        }
+
+        ResourceResolver resourceResolver = null;
+        try {
+            resourceResolver = resourceResolverFactory
+                    .getAdministrativeResourceResolver(null);
+
+            Resource myInstance = ResourceHelper
+                    .getOrCreateResource(
+                            resourceResolver,
+                            config.getClusterInstancesPath()
+                                    + "/" + slingId + "/properties");
+            // SLING-2879 - revert/refresh resourceResolver here to work
+            // around a potential issue with jackrabbit in a clustered environment
+            resourceResolver.revert();
+            resourceResolver.refresh();
+
+            final ModifiableValueMap myInstanceMap = myInstance.adaptTo(ModifiableValueMap.class);
+            final Set<String> keys = new HashSet<String>(myInstanceMap.keySet());
+            for(final String key : keys) {
+                if (newProps.containsKey(key)) {
+                    // perfect
+                    continue;
+                } else if (key.indexOf(":")!=-1) {
+                    // ignore
+                    continue;
+                } else {
+                    // remove
+                	myInstanceMap.remove(key);
+                }
+            }
+
+            boolean anyChanges = false;
+            for(final Entry<String, String> entry : newProps.entrySet()) {
+            	Object existingValue = myInstanceMap.get(entry.getKey());
+            	if (entry.getValue().equals(existingValue)) {
+            	    // SLING-3389: dont rewrite the properties if nothing changed!
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("doUpdateProperties: unchanged: {}={}", entry.getKey(), entry.getValue());
+                    }
+            	    continue;
+            	}
+            	if (logger.isDebugEnabled()) {
+            	    logger.debug("doUpdateProperties: changed: {}={}", entry.getKey(), entry.getValue());
+            	}
+            	anyChanges = true;
+                myInstanceMap.put(entry.getKey(), entry.getValue());
+            }
+
+            if (anyChanges) {
+                resourceResolver.commit();
+            }
+        } catch (LoginException e) {
+            logger.error(
+                    "handleEvent: could not log in administratively: " + e, e);
+            throw new RuntimeException("Could not log in to repository (" + e
+                    + ")", e);
+        } catch (PersistenceException e) {
+            logger.error("handleEvent: got a PersistenceException: " + e, e);
+            throw new RuntimeException(
+                    "Exception while talking to repository (" + e + ")", e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+
+        logger.debug("doUpdateProperties: updating properties done.");
+    }
+
+    /**
+     * Update the properties and sent a topology event if applicable
+     */
+    public void updateProperties() {
+        synchronized (lock) {
+            logger.debug("updateProperties: calling doUpdateProperties.");
+            doUpdateProperties();
+            logger.debug("updateProperties: calling handlePotentialTopologyChange.");
+            handlePotentialTopologyChange();
+            logger.debug("updateProperties: done.");
+        }
+    }
+
+    /**
+     * Internal class caching some provider infos like service id and ranking.
+     */
+    private final static class ProviderInfo implements Comparable<ProviderInfo> {
+
+        public final PropertyProvider provider;
+        public final Object propertyProperties;
+        public final int ranking;
+        public final long serviceId;
+        public final Map<String, String> properties = new HashMap<String, String>();
+
+        public ProviderInfo(final PropertyProvider provider,
+                final Map<String, Object> serviceProps) {
+            this.provider = provider;
+            this.propertyProperties = serviceProps.get(PropertyProvider.PROPERTY_PROPERTIES);
+            final Object sr = serviceProps.get(Constants.SERVICE_RANKING);
+            if (sr == null || !(sr instanceof Integer)) {
+                this.ranking = 0;
+            } else {
+                this.ranking = (Integer) sr;
+            }
+            this.serviceId = (Long) serviceProps.get(Constants.SERVICE_ID);
+            refreshProperties();
+        }
+
+        public void refreshProperties() {
+            properties.clear();
+            if (this.propertyProperties instanceof String) {
+                final String val = provider.getProperty((String) this.propertyProperties);
+                if (val != null) {
+                	putPropertyIfValid((String) this.propertyProperties, val);
+                }
+            } else if (this.propertyProperties instanceof String[]) {
+                for (final String name : (String[]) this.propertyProperties) {
+                    final String val = provider.getProperty(name);
+                    if (val != null) {
+                        putPropertyIfValid(name, val);
+                    }
+                }
+            }
+        }
+
+        /** SLING-2883 : put property only if valid **/
+		private void putPropertyIfValid(final String name, final String val) {
+			if (PropertyNameHelper.isValidPropertyName(name)) {
+				this.properties.put(name, val);
+			}
+		}
+
+		/**
+         * @see java.lang.Comparable#compareTo(java.lang.Object)
+         */
+        public int compareTo(final ProviderInfo o) {
+            // Sort by rank in ascending order.
+            if (this.ranking < o.ranking) {
+                return -1; // lower rank
+            } else if (this.ranking > o.ranking) {
+                return 1; // higher rank
+            }
+            // If ranks are equal, then sort by service id in descending order.
+            return (this.serviceId < o.serviceId) ? 1 : -1;
+        }
+
+        @Override
+        public boolean equals(final Object obj) {
+            if (obj instanceof ProviderInfo) {
+                return ((ProviderInfo) obj).serviceId == this.serviceId;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return provider.hashCode();
+        }
+    }
+
+    /**
+     * Handle the fact that the topology has likely changed
+     */
+    public void handlePotentialTopologyChange() {
+        viewStateManagerLock.lock();
+        try{
+            if (!activated) {
+                logger.debug("handlePotentialTopologyChange: not yet activated, ignoring");
+                return;
+            }
+            BaseTopologyView t = (BaseTopologyView) getTopology();
+            if (t.isCurrent()) {
+                // if we have a valid view, let the viewStateManager do the
+                // comparison and sending of an event, if necessary
+                viewStateManager.handleNewView(t);
+            } else {
+                // if we don't have a view, then we might have to send
+                // a CHANGING event, let that be decided by the viewStateManager as well
+                viewStateManager.handleChanging();
+            }
+        } finally {
+            if (viewStateManagerLock!=null) {
+                viewStateManagerLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Handle the fact that the topology has started to change - inform the listeners asap
+     */
+    public void handleTopologyChanging() {
+        logger.debug("handleTopologyChanging: invoking viewStateManager.handlechanging");
+        viewStateManager.handleChanging();
+    }
+
+    protected ClusterViewService getClusterViewService() {
+        return clusterViewService;
+    }
+    
+    protected AnnouncementRegistry getAnnouncementRegistry() {
+        return announcementRegistry;
+    }
+
+    /** for testing only 
+     * @return */
+    public ViewStateManager getViewStateManager() {
+        return viewStateManager;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
new file mode 100644
index 0000000..2c635a4
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/oak/TopologyWebConsolePlugin.java
@@ -0,0 +1,1048 @@
+/*
+ * 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.oak;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+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.Properties;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+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.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.connectors.announcement.Announcement;
+import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.base.connectors.announcement.CachedAnnouncement;
+import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
+import org.apache.sling.discovery.base.connectors.ping.TopologyConnectorClientInformation;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
+import org.apache.sling.discovery.TopologyEventListener;
+import org.apache.sling.discovery.TopologyView;
+import org.osgi.framework.BundleContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple webconsole which gives an overview of the topology visible by the
+ * discovery service
+ */
+@Component
+@Service(value = { TopologyEventListener.class, Servlet.class })
+@Properties({
+    @Property(name=org.osgi.framework.Constants.SERVICE_DESCRIPTION,
+            value="Apache Sling Web Console Plugin to display Background servlets and ExecutionEngine status"),
+    @Property(name=WebConsoleConstants.PLUGIN_LABEL, value=TopologyWebConsolePlugin.LABEL),
+    @Property(name=WebConsoleConstants.PLUGIN_TITLE, value=TopologyWebConsolePlugin.TITLE),
+    @Property(name="felix.webconsole.configprinter.modes", value={"zip"})
+})
+@SuppressWarnings("serial")
+public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implements TopologyEventListener {
+
+    public static final String LABEL = "topology";
+    public static final String TITLE = "Topology Management";
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** the truncated log of topology events, filtered by property change types. shown in webconsole **/
+    private final List<String> propertyChangeLog = new LinkedList<String>();
+
+    /** the truncated log of topology events, shown in webconsole **/
+    private final List<String> topologyLog = new LinkedList<String>();
+
+    /** the date format used in the truncated log of topology events **/
+    private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
+
+    @Reference
+    private ClusterViewService clusterViewService;
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private ConnectorRegistry connectorRegistry;
+
+    @Reference
+    protected ResourceResolverFactory resourceResolverFactory;
+
+    private TopologyView currentView;
+    
+    private List<String> discoveryLiteHistory = new LinkedList<String>();
+
+    @Override
+    public String getLabel() {
+        return LABEL;
+    }
+
+    @Override
+    public String getTitle() {
+        return TITLE;
+    }
+
+    @Activate
+    @Override
+    public void activate(final BundleContext bundleContext) {
+        super.activate(bundleContext);
+    }
+
+    @Deactivate
+    @Override
+    public void deactivate() {
+        super.deactivate();
+    }
+
+    @Override
+    protected void renderContent(final HttpServletRequest req, final HttpServletResponse res)
+            throws ServletException, IOException {
+        Object rawRoot = req.getAttribute(WebConsoleConstants.ATTR_PLUGIN_ROOT);
+        if (!(rawRoot instanceof String)) {
+            throw new ServletException("Illegal attr: "
+                    + WebConsoleConstants.ATTR_PLUGIN_ROOT);
+        }
+
+        String root = rawRoot.toString();
+        String pathInfo = req.getRequestURI().substring(root.length());
+
+        final PrintWriter pw = res.getWriter();
+
+        if (pathInfo.equals("")) {
+            if ( this.currentView != null ) {
+                renderOverview(pw,  currentView);
+            } else {
+                pw.println("<p class=\"statline ui-state-highlight\">No view available</p>");
+                pw.println("<br/>");
+                pw.println("No TOPOLOGY_INIT received yet, therefore no view available yet.");
+            }
+        } else {
+            StringTokenizer st = new StringTokenizer(pathInfo, "/");
+            final String nodeId = st.nextToken();
+            renderProperties(pw, req.getContextPath(), nodeId);
+        }
+    }
+
+    /**
+     * Render the properties page of a particular instance
+     */
+    private void renderProperties(final PrintWriter pw, final String contextPath, final String nodeId) {
+    	if (logger.isDebugEnabled()) {
+    		logger.debug("renderProperties: nodeId=" + nodeId);
+    	}
+        final TopologyView tv = this.currentView;
+        @SuppressWarnings("unchecked")
+        Set<InstanceDescription> instances = ( tv == null ? (Set<InstanceDescription>)Collections.EMPTY_SET :
+
+                tv.findInstances(new InstanceFilter() {
+
+                    public boolean accept(InstanceDescription instance) {
+                        String slingId = instance.getSlingId();
+                    	if (logger.isDebugEnabled()) {
+	                        logger.debug("renderProperties/picks: slingId={}", slingId);
+                    	}
+                        return (slingId.equals(nodeId));
+                    }
+                }));
+
+        if (instances != null && instances.size() == 1) {
+            InstanceDescription instance = instances.iterator().next();
+            pw.println("Properties of " + instance.getSlingId() + ":<br/>");
+
+            pw.println("<table class=\"adapters nicetable ui-widget tablesorter\">");
+            pw.println("<thead>");
+            pw.println("<tr>");
+            pw.println("<th class=\"header ui-widget-header\">Key</th>");
+            pw.println("<th class=\"header ui-widget-header\">Value</th>");
+            pw.println("</tr>");
+            pw.println("</thead>");
+            pw.println("<tbody>");
+            boolean odd = true;
+            for (Iterator<Entry<String, String>> it = instance.getProperties()
+                    .entrySet().iterator(); it.hasNext();) {
+                Entry<String, String> entry = it.next();
+                String oddEven = odd ? "odd" : "even";
+                odd = !odd;
+                pw.println("<tr class=\"" + oddEven + " ui-state-default\">");
+
+                pw.println("<td>" + entry.getKey() + "</td>");
+                pw.println("<td>" + entry.getValue() + "</td>");
+
+                pw.println("</tr>");
+            }
+            pw.println("</tbody>");
+            pw.println("</table>");
+        }
+    }
+
+    protected ResourceResolver getResourceResolver() throws LoginException {
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+
+    /**
+     * Render the overview of the entire topology
+     */
+    private void renderOverview(final PrintWriter pw, final TopologyView topology) {
+        pw.println("<p class=\"statline ui-state-highlight\">Configuration</p>");
+        pw.println("<br/>");
+        pw.print("<a href=\"${appRoot}/configMgr/org.apache.sling.discovery.oak.Config\">Configure Discovery.Oak Service</a>");
+        pw.println("<br/>");
+        pw.println("<br/>");
+        final String changing;
+        if (!topology.isCurrent()) {
+        	changing = " <b><i>CHANGING!</i> (the view is no longer current!)</b>";
+        } else {
+        	changing = "";
+        }
+        pw.println("<p class=\"statline ui-state-highlight\">Topology"+changing+"</p>");
+        pw.println("<div class=\"ui-widget-header ui-corner-top buttonGroup\" style=\"height: 15px;\">");
+        pw.println("<span style=\"float: left; margin-left: 1em;\">Instances in the topology</span>");
+        pw.println("</div>");
+        pw.println("<table class=\"adapters nicetable ui-widget tablesorter\">");
+        pw.println("<thead>");
+        pw.println("<tr>");
+        pw.println("<th class=\"header ui-widget-header\">Sling id (click for properties)</th>");
+        pw.println("<th class=\"header ui-widget-header\">ClusterView id</th>");
+        pw.println("<th class=\"header ui-widget-header\">Local instance</th>");
+        pw.println("<th class=\"header ui-widget-header\">Leader instance</th>");
+        pw.println("<th class=\"header ui-widget-header\">In local cluster</th>");
+        pw.println("<th class=\"header ui-widget-header\">Announced by instance</th>");
+        pw.println("</tr>");
+        pw.println("</thead>");
+        pw.println("<tbody>");
+
+        Set<ClusterView> clusters = topology.getClusterViews();
+        ClusterView myCluster = topology.getLocalInstance().getClusterView();
+        boolean odd = true;
+        renderCluster(pw, myCluster, myCluster, odd, topology.isCurrent());
+
+        for (Iterator<ClusterView> it = clusters.iterator(); it.hasNext();) {
+            ClusterView clusterView = it.next();
+            if (clusterView.equals(myCluster)) {
+                // skip - I already rendered that
+                continue;
+            }
+            odd = !odd;
+            renderCluster(pw, clusterView, myCluster, odd, topology.isCurrent());
+        }
+
+        pw.println("</tbody>");
+        pw.println("</table>");
+
+        pw.println("<br/>");
+        pw.println("<br/>");
+        pw.println("<p class=\"statline ui-state-highlight\">Connectors</p>");
+        listIncomingTopologyConnectors(pw);
+        listOutgoingTopologyConnectors(pw);
+        pw.println("<br/>");
+
+        ResourceResolver resourceResolver = null;
+        pw.println("<p class=\"statline ui-state-highlight\">Discovery-Lite Descriptor History</p>");
+        pw.println("<pre>");
+        for (String discoLiteHistoryEntry : discoveryLiteHistory) {
+            pw.println(discoLiteHistoryEntry);
+        }
+        pw.println("</pre>");
+        pw.println("<br/>");
+        pw.println("<p class=\"statline ui-state-highlight\">Current Discovery-Lite Descriptor Value</p>");
+        pw.println("<pre>");
+        try{
+            resourceResolver = getResourceResolver();
+            DiscoveryLiteDescriptor descriptor = DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+            final String logEntry = getCurrentDateFormatted() + ": " + descriptor.getDescriptorStr();
+            pw.println(logEntry);
+        } catch(Exception e) {
+            logger.error("renderOverview: Exception: "+e, e);
+            pw.println("Got exception trying to get repository descriptor: "+e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+        pw.println("</pre>");
+        pw.println("<br/>");
+        
+        pw.println("<p class=\"statline ui-state-highlight\">Topology Change History</p>");
+        pw.println("<pre>");
+        for (Iterator<String> it = topologyLog
+                .iterator(); it.hasNext();) {
+            String aLogEntry = it.next();
+            pw.println(aLogEntry);
+        }
+        pw.println("</pre>");
+        pw.println("<br/>");
+        pw.println("<p class=\"statline ui-state-highlight\">Property Change History</p>");
+        pw.println("<pre>");
+        for (Iterator<String> it = propertyChangeLog
+                .iterator(); it.hasNext();) {
+            String aLogEntry = it.next();
+            pw.println(aLogEntry);
+        }
+        pw.println("</pre>");
+        pw.println("</br>");
+    }
+
+    /**
+     * Render a particular cluster (into table rows)
+     */
+    private void renderCluster(final PrintWriter pw, final ClusterView renderCluster, final ClusterView localCluster, final boolean odd, final boolean current) {
+        final Collection<Announcement> announcements = announcementRegistry.listAnnouncementsInSameCluster(localCluster);
+
+        for (Iterator<InstanceDescription> it = renderCluster.getInstances()
+                .iterator(); it.hasNext();) {
+            final InstanceDescription instanceDescription = it.next();
+            final boolean inLocalCluster = renderCluster == localCluster;
+            Announcement parentAnnouncement = null;
+            for (Iterator<Announcement> it2 = announcements.iterator(); it2
+                    .hasNext();) {
+                Announcement announcement = it2.next();
+                for (Iterator<InstanceDescription> it3 = announcement
+                        .listInstances().iterator(); it3.hasNext();) {
+                    InstanceDescription announcedInstance = it3.next();
+                    if (announcedInstance.getSlingId().equals(
+                            instanceDescription.getSlingId())) {
+                        parentAnnouncement = announcement;
+                        break;
+                    }
+                }
+            }
+
+            final String oddEven = odd ? "odd" : "even";
+
+            if (current && (inLocalCluster || (parentAnnouncement!=null))) {
+                pw.println("<tr class=\"" + oddEven + " ui-state-default\">");
+            } else {
+                pw.println("<tr class=\"" + oddEven + " ui-state-error\">");
+            }
+            final boolean isLocal = instanceDescription.isLocal();
+            final String slingId = instanceDescription.getSlingId();
+            pw.print("<td>");
+            if ( isLocal) {
+                pw.print("<b>");
+            }
+            pw.print("<a href=\"");
+            pw.print(this.getLabel());
+            pw.print('/');
+            pw.print(slingId);
+            pw.print("\">");
+            pw.print(slingId);
+            pw.print("</a>");
+            if ( isLocal) {
+                pw.print("</b>");
+            }
+            pw.println("</td>");
+            pw.println("<td>"
+                    + (instanceDescription.getClusterView() == null ? "null"
+                            : instanceDescription.getClusterView().getId())
+                    + "</td>");
+            pw.println("<td>" + (isLocal ? "<b>true</b>" : "false") + "</td>");
+            pw.println("<td>"
+                    + (instanceDescription.isLeader() ? "<b>true</b>" : "false")
+                    + "</td>");
+            if (inLocalCluster) {
+                pw.println("<td>local</td>");
+                pw.println("<td>n/a</td>");
+            } else {
+                pw.println("<td>remote</td>");
+                if (parentAnnouncement != null) {
+                    pw.println("<td>" + parentAnnouncement.getOwnerId()
+                            + "</td>");
+                } else {
+                    pw.println("<td><b>(changing)</b></td>");
+                }
+            }
+            pw.println("</tr>");
+        }
+
+    }
+
+    /**
+     * Render the outgoing topology connectors - including the header-div and table
+     */
+    private void listOutgoingTopologyConnectors(final PrintWriter pw) {
+        boolean odd = false;
+        pw.println("<div class=\"ui-widget-header ui-corner-top buttonGroup\" style=\"height: 15px;\">");
+        pw.println("<span style=\"float: left; margin-left: 1em;\">Outgoing topology connectors</span>");
+        pw.println("</div>");
+        pw.println("<table class=\"adapters nicetable ui-widget tablesorter\">");
+        pw.println("<thead>");
+        pw.println("<tr>");
+        pw.println("<th class=\"header ui-widget-header\">Connector url</th>");
+        pw.println("<th class=\"header ui-widget-header\">Connected to slingId</th>");
+        pw.println("<th class=\"header ui-widget-header\">Connector status</th>");
+        pw.println("<th class=\"header ui-widget-header\">Last heartbeat&nbsp;</th>");
+        pw.println("<th class=\"header ui-widget-header\">Next heartbeat&nbsp;</th>");
+        pw.println("<th class=\"header ui-widget-header\">Request encoding&nbsp;</th>");
+        pw.println("<th class=\"header ui-widget-header\">Response encoding&nbsp;</th>");
+        // pw.println("<th class=\"header ui-widget-header\">Fallback connector urls</th>");
+        pw.println("</tr>");
+        pw.println("</thead>");
+        pw.println("<tbody>");
+
+        Collection<TopologyConnectorClientInformation> outgoingConnections = connectorRegistry
+                .listOutgoingConnectors();
+        for (Iterator<TopologyConnectorClientInformation> it = outgoingConnections
+                .iterator(); it.hasNext();) {
+            TopologyConnectorClientInformation topologyConnectorClient = it
+                    .next();
+            final String oddEven = odd ? "odd" : "even";
+            odd = !odd;
+            final String remoteSlingId = topologyConnectorClient.getRemoteSlingId();
+            final boolean isConnected = topologyConnectorClient.isConnected() && remoteSlingId != null;
+            final boolean autoStopped = topologyConnectorClient.isAutoStopped();
+            final boolean representsLoop = topologyConnectorClient.representsLoop();
+            if (isConnected || autoStopped || representsLoop) {
+                pw.println("<tr class=\"" + oddEven + " ui-state-default\">");
+            } else {
+                pw.println("<tr class=\"" + oddEven + " ui-state-error\">");
+            }
+            pw.println("<td>"
+                    + topologyConnectorClient.getConnectorUrl().toString()
+                    + "</td>");
+            if (autoStopped) {
+            	pw.println("<td><b>auto-stopped</b></td>");
+            	pw.println("<td><b>auto-stopped due to local-loop</b></td>");
+            } else if (isConnected && !representsLoop) {
+                pw.println("<td>" + remoteSlingId + "</td>");
+                pw.println("<td>ok, in use</td>");
+            } else if (representsLoop) {
+                pw.println("<td>" + remoteSlingId + "</td>");
+                pw.println("<td>ok, unused (loop or duplicate): standby</td>");
+            } else {
+                final int statusCode = topologyConnectorClient.getStatusCode();
+                final String statusDetails = topologyConnectorClient.getStatusDetails();
+                final String tooltipText;
+                switch(statusCode) {
+                case HttpServletResponse.SC_UNAUTHORIZED:
+                    tooltipText = HttpServletResponse.SC_UNAUTHORIZED +
+                        ": possible setup issue of discovery.oak on target instance, or wrong URL";
+                    break;
+                case HttpServletResponse.SC_NOT_FOUND:
+                    tooltipText = HttpServletResponse.SC_NOT_FOUND +
+                        ": possible white list rejection by target instance";
+                    break;
+                case -1:
+                    tooltipText = "-1: check error log. possible connection refused.";
+                    break;
+                default:
+                    tooltipText = null;
+                }
+                final String tooltip = tooltipText==null ? "" : (" title=\""+tooltipText+"\"");
+                pw.println("<td><b>not connected</b></td>");
+                pw.println("<td"+tooltip+"><b>not ok (HTTP Status-Code: "+statusCode+", "+statusDetails+")</b></td>");
+            }
+            pw.println("<td>"+beautifiedTimeDiff(topologyConnectorClient.getLastPingSent())+"</td>");
+            pw.println("<td>"+beautifiedDueTime(topologyConnectorClient.getNextPingDue())+"</td>");
+            pw.println("<td>"+topologyConnectorClient.getLastRequestEncoding()+"</td>");
+            pw.println("<td>"+topologyConnectorClient.getLastResponseEncoding()+"</td>");
+            // //TODO fallback urls are not yet implemented!
+            // String fallbackConnectorUrls;
+            // List<String> urls = topologyConnectorClient
+            // .listFallbackConnectorUrls();
+            // if (urls == null || urls.size() == 0) {
+            // fallbackConnectorUrls = "n/a";
+            // } else {
+            // fallbackConnectorUrls = "";
+            // for (Iterator<String> it2 = urls.iterator(); it2.hasNext();) {
+            // String aFallbackConnectorUrl = it2.next();
+            // fallbackConnectorUrls = fallbackConnectorUrls
+            // + aFallbackConnectorUrl + "<br/>";
+            // }
+            // }
+            // pw.println("<td>" + fallbackConnectorUrls + "</td>");
+        }
+
+        pw.println("</tbody>");
+        pw.println("</table>");
+    }
+    
+    private String beautifiedDueTime(long secondsDue) {
+        if (secondsDue<-1) {
+            return "overdue";
+        } else if (secondsDue<=0) {
+            return "now-ish";
+        } else if (secondsDue==1) {
+            return "in 1 second";
+        } else {
+            int minsDue = (int) (secondsDue / 60);
+            if (minsDue<5) {
+                return "in "+secondsDue+" seconds";
+            } else {
+                return "in "+minsDue+" minutes";
+            }
+        }
+    }
+
+    private String beautifiedTimeDiff(long heartbeatTime) {
+        final long diff = System.currentTimeMillis() - heartbeatTime;
+        long seconds = (diff/1000);
+        if (heartbeatTime<=0) {
+            return "n/a";
+        } else if (seconds==0) {
+            return diff+" millis ago";
+        } else if (seconds==1) {
+            return "1 second ago";
+        } else if (seconds<300) {
+            // then print seconds
+            return seconds+" seconds ago";
+        } else {
+            // then print seconds
+            return (seconds/60)+" minute ago";
+        }
+    }
+
+    /**
+     * Render the incoming topology connectors - including the header-div and table
+     */
+    private void listIncomingTopologyConnectors(final PrintWriter pw) {
+        boolean odd = false;
+        pw.println("<div class=\"ui-widget-header ui-corner-top buttonGroup\" style=\"height: 15px;\">");
+        pw.println("<span style=\"float: left; margin-left: 1em;\">Incoming topology connectors</span>");
+        pw.println("</div>");
+        pw.println("<table class=\"adapters nicetable ui-widget tablesorter\">");
+        pw.println("<thead>");
+        pw.println("<tr>");
+        pw.println("<th class=\"header ui-widget-header\">Owner slingId</th>");
+        pw.println("<th class=\"header ui-widget-header\">Server info</th>");
+        pw.println("<th class=\"header ui-widget-header\">Last heartbeat</th>");
+        pw.println("<th class=\"header ui-widget-header\">Timeout</th>");
+        pw.println("</tr>");
+        pw.println("</thead>");
+        pw.println("<tbody>");
+
+        Collection<CachedAnnouncement> incomingConnections = announcementRegistry.listLocalIncomingAnnouncements();
+        for (Iterator<CachedAnnouncement> it = incomingConnections.iterator(); it
+                .hasNext();) {
+            CachedAnnouncement incomingCachedAnnouncement = it.next();
+            Announcement incomingAnnouncement = incomingCachedAnnouncement.getAnnouncement();
+            String oddEven = odd ? "odd" : "even";
+            odd = !odd;
+
+            pw.println("<tr class=\"" + oddEven + " ui-state-default\">");
+            pw.println("<td>" + incomingAnnouncement.getOwnerId() + "</td>");
+            if (incomingAnnouncement.getServerInfo() != null) {
+                pw.println("<td>" + incomingAnnouncement.getServerInfo()
+                        + "</td>");
+            } else {
+                pw.println("<td><i>n/a</i></td>");
+            }
+            pw.println("<td>"+beautifiedTimeDiff(incomingCachedAnnouncement.getLastPing())+"</td>");
+            pw.println("<td>"+beautifiedDueTime(incomingCachedAnnouncement.getSecondsUntilTimeout())+"</td>");
+
+            pw.println("</tr>");
+        }
+
+        pw.println("</tbody>");
+        pw.println("</table>");
+        pw.println("<br/>");
+        pw.println("<br/>");
+    }
+
+    /**
+     * keep a truncated history of the log events for information purpose (to be shown in the webconsole)
+     */
+    public void handleTopologyEvent(final TopologyEvent event) {
+        if (event.getType() == Type.PROPERTIES_CHANGED) {
+            this.currentView = event.getNewView();
+
+            Set<InstanceDescription> newInstances = event.getNewView()
+                    .getInstances();
+            StringBuilder sb = new StringBuilder();
+            for (Iterator<InstanceDescription> it = newInstances.iterator(); it
+                    .hasNext();) {
+                final InstanceDescription newInstanceDescription = it.next();
+                InstanceDescription oldInstanceDescription = findInstance(
+                        event.getOldView(), newInstanceDescription.getSlingId());
+                if (oldInstanceDescription == null) {
+                    logger.error("handleTopologyEvent: got a property changed but did not find instance "
+                            + newInstanceDescription
+                            + " in oldview.. event="
+                            + event);
+                    addEventLog(event.getType(), event.getType().toString());
+                    return;
+                }
+
+                Map<String, String> oldProps = oldInstanceDescription
+                        .getProperties();
+                Map<String, String> newProps = newInstanceDescription
+                        .getProperties();
+                StringBuilder diff = diff(oldProps, newProps);
+                if (diff.length() > 0) {
+                    if (sb.length() != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append("on instance "
+                            + newInstanceDescription.getSlingId() + (newInstanceDescription.isLeader() ? " [isLeader]" : "") 
+                            + ": " + diff);
+                }
+            }
+
+            addEventLog(event.getType(), sb.toString());
+        } else if (event.getType() == Type.TOPOLOGY_INIT) {
+            this.currentView = event.getNewView();
+            StringBuilder details = new StringBuilder();
+            for (Iterator<InstanceDescription> it = event.getNewView()
+                    .getInstances().iterator(); it.hasNext();) {
+                InstanceDescription newInstance = it.next();
+                if (details.length() != 0) {
+                    details.append(", ");
+                }
+                details.append(newInstance.getSlingId());
+                if (newInstance.isLeader()) {
+                    details.append(" [isLeader]");
+                }
+            }
+            addEventLog(event.getType(),
+                    "view: " + shortViewInfo(event.getNewView()) + ". "
+                            + details);
+        } else if (event.getType() == Type.TOPOLOGY_CHANGING) {
+            this.currentView = event.getOldView();
+            addEventLog(event.getType(),
+                    "old view: " + shortViewInfo(event.getOldView()));
+        } else {
+            this.currentView = event.getNewView();
+            if (event.getOldView() == null) {
+                addEventLog(event.getType(),
+                        "new view: " + shortViewInfo(event.getNewView()));
+            } else {
+                StringBuilder details = new StringBuilder();
+                for (Iterator<InstanceDescription> it = event.getNewView()
+                        .getInstances().iterator(); it.hasNext();) {
+                    InstanceDescription newInstance = it.next();
+                    if (findInstance(event.getOldView(),
+                            newInstance.getSlingId()) == null) {
+                        if (details.length() != 0) {
+                            details.append(", ");
+                        }
+                        details.append(newInstance.getSlingId() + " joined");
+                    }
+                }
+                for (Iterator<InstanceDescription> it = event.getOldView()
+                        .getInstances().iterator(); it.hasNext();) {
+                    InstanceDescription oldInstance = it.next();
+                    if (findInstance(event.getNewView(),
+                            oldInstance.getSlingId()) == null) {
+                        if (details.length() != 0) {
+                            details.append(", ");
+                        }
+                        details.append(oldInstance.getSlingId() + " left");
+                    }
+                }
+                final InstanceDescription li = event.getNewView().getLocalInstance();
+                if (li!=null) {
+                    ClusterView clusterView = li.getClusterView();
+                    if (clusterView!=null) {
+                        final InstanceDescription leader = clusterView.getLeader();
+                        if (leader!=null) {
+                            if (details.length() !=0) {
+                                details.append(", ");
+                            }
+                            details.append("[isLeader: "+leader.getSlingId()+"]");
+                        }
+                    }
+                }
+    
+                addEventLog(
+                        event.getType(),
+                        "old view: " + shortViewInfo(event.getOldView())
+                                + ", new view: "
+                                + shortViewInfo(event.getNewView()) + ". "
+                                + details);
+            }
+        }
+        addDiscoveryLiteHistoryEntry();
+    }
+
+    /**
+     * find a particular instance in the topology
+     */
+    private InstanceDescription findInstance(final TopologyView view,
+            final String slingId) {
+        Set<InstanceDescription> foundInstances = view
+                .findInstances(new InstanceFilter() {
+
+                    public boolean accept(InstanceDescription instance) {
+                        return instance.getSlingId().equals(slingId);
+                    }
+                });
+        if (foundInstances.size() == 1) {
+            return foundInstances.iterator().next();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * add a log entry and truncate the log entries if necessary
+     */
+    private synchronized void addEventLog(final Type type, final String info) {
+        final String logEntry = getCurrentDateFormatted() + ": " + type + ". " + info;
+
+        if (type == Type.PROPERTIES_CHANGED) {
+            propertyChangeLog.add(logEntry);
+            while (propertyChangeLog.size() > 12) {
+                propertyChangeLog.remove(0);
+            }
+        } else {
+            topologyLog.add(logEntry);
+            while (topologyLog.size() > 12) {
+                topologyLog.remove(0);
+            }
+        }
+
+    }
+
+    /**
+     * add a discoveryLite descriptor entry to the history, truncating if necessary
+     */
+    private synchronized void addDiscoveryLiteHistoryEntry() {
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            DiscoveryLiteDescriptor descriptor = 
+                    DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+
+            final String logEntry = getCurrentDateFormatted() + ": " + descriptor.getDescriptorStr();
+            
+            discoveryLiteHistory.add(logEntry);
+            while (discoveryLiteHistory.size() > 12) {
+                discoveryLiteHistory.remove(0);
+            }
+        } catch(Exception e) {
+            logger.error("addDiscoveryLiteHistoryEntry: Exception: "+e, e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+        
+    }
+
+    /**
+     * compile a short information string of the topology, including
+     * number of clusters and instances
+     */
+    private String shortViewInfo(final TopologyView view) {
+        int clusters = view.getClusterViews().size();
+        int instances = view.getInstances().size();
+        return ((clusters == 1) ? "1 cluster" : clusters + " clusters") + ", "
+                + ((instances == 1) ? "1 instance" : instances + " instances");
+    }
+
+    /**
+     * calculate the difference between two sets of properties
+     */
+    private StringBuilder diff(final Map<String, String> oldProps,
+            final Map<String, String> newProps) {
+        final Set<String> oldKeys = new HashSet<String>(oldProps.keySet());
+        final Set<String> newKeys = new HashSet<String>(newProps.keySet());
+
+        StringBuilder sb = new StringBuilder();
+
+        for (Iterator<String> it = oldKeys.iterator(); it.hasNext();) {
+            String oldKey = it.next();
+            if (newKeys.contains(oldKey)) {
+                if (oldProps.get(oldKey).equals(newProps.get(oldKey))) {
+                    // perfect
+                } else {
+                    sb.append("(" + oldKey + " changed from "
+                            + oldProps.get(oldKey) + " to "
+                            + newProps.get(oldKey) + ")");
+                }
+                newKeys.remove(oldKey);
+            } else {
+                sb.append("(" + oldKey + " was removed)");
+            }
+            it.remove();
+        }
+        for (Iterator<String> it = newKeys.iterator(); it.hasNext();) {
+            String newKey = it.next();
+            sb.append("(" + newKey + " was added)");
+        }
+
+        return sb;
+    }
+
+    public void printConfiguration( final PrintWriter pw ) {
+        final TopologyView topology = this.currentView;
+
+        pw.println(TITLE);
+        pw.println("---------------------------------------");
+        pw.println();
+        if ( topology == null ) {
+            pw.println("No topology available yet!");
+            return;
+        }
+        pw.print("Topology");
+        if (!topology.isCurrent()) {
+            pw.print(" CHANGING! (the view is no longer current!)");
+        }
+        pw.println();
+        pw.println();
+
+        final Set<ClusterView> clusters = topology.getClusterViews();
+        final ClusterView myCluster = topology.getLocalInstance().getClusterView();
+        printCluster(pw, myCluster, myCluster);
+
+        for (Iterator<ClusterView> it = clusters.iterator(); it.hasNext();) {
+            ClusterView clusterView = it.next();
+            if (clusterView.equals(myCluster)) {
+                // skip - I already rendered that
+                continue;
+            }
+            printCluster(pw, clusterView, myCluster);
+        }
+
+        pw.println();
+        pw.println();
+
+        final Collection<CachedAnnouncement> incomingConnections = announcementRegistry.listLocalIncomingAnnouncements();
+        if ( incomingConnections.size() > 0 ) {
+            pw.println("Incoming topology connectors");
+            pw.println("---------------------------------------");
+
+            for(final CachedAnnouncement incomingCachedAnnouncement : incomingConnections) {
+                Announcement incomingAnnouncement = incomingCachedAnnouncement.getAnnouncement();
+                pw.print("Owner Sling Id : ");
+                pw.print(incomingAnnouncement.getOwnerId());
+                pw.println();
+                if (incomingAnnouncement.getServerInfo() != null) {
+                    pw.print("Server Info : ");
+                    pw.print(incomingAnnouncement.getServerInfo());
+                    pw.println();
+                }
+                pw.println("Last heartbeat received : "+beautifiedTimeDiff(incomingCachedAnnouncement.getLastPing()));
+                pw.println("Timeout : "+beautifiedDueTime(incomingCachedAnnouncement.getSecondsUntilTimeout()));
+
+                pw.println();
+            }
+            pw.println();
+            pw.println();
+        }
+
+        final Collection<TopologyConnectorClientInformation> outgoingConnections = connectorRegistry.listOutgoingConnectors();
+        if ( outgoingConnections.size() > 0 ) {
+            pw.println("Outgoing topology connectors");
+            pw.println("---------------------------------------");
+
+            for(final TopologyConnectorClientInformation topologyConnectorClient : outgoingConnections) {
+                final String remoteSlingId = topologyConnectorClient.getRemoteSlingId();
+                final boolean autoStopped = topologyConnectorClient.isAutoStopped();
+                final boolean isConnected = topologyConnectorClient.isConnected() && remoteSlingId != null;
+                pw.print("Connector URL : ");
+                pw.print(topologyConnectorClient.getConnectorUrl());
+                pw.println();
+
+                if (autoStopped) {
+                    pw.println("Conncted to Sling Id : auto-stopped");
+                    pw.println("Connector status : auto-stopped due to local-loop");
+                } else if (isConnected && !topologyConnectorClient.representsLoop()) {
+                    pw.print("Connected to Sling Id : ");
+                    pw.println(remoteSlingId);
+                    pw.println("Connector status : ok, in use");
+                } else if (topologyConnectorClient.representsLoop()) {
+                    pw.print("Connected to Sling Id : ");
+                    pw.println(remoteSlingId);
+                    pw.println("Connector status : ok, unused (loop or duplicate): standby");
+                } else {
+                    final int statusCode = topologyConnectorClient.getStatusCode();
+                    final String statusDetails = topologyConnectorClient.getStatusDetails();
+                    final String tooltipText;
+                    switch(statusCode) {
+                        case HttpServletResponse.SC_UNAUTHORIZED:
+                            tooltipText = HttpServletResponse.SC_UNAUTHORIZED +
+                                ": possible setup issue of discovery.oak on target instance, or wrong URL";
+                            break;
+                        case HttpServletResponse.SC_NOT_FOUND:
+                            tooltipText = HttpServletResponse.SC_NOT_FOUND +
+                                ": possible white list rejection by target instance";
+                            break;
+                        case -1:
+                            tooltipText = "-1: check error log. possible connection refused.";
+                            break;
+                        default:
+                            tooltipText = null;
+                    }
+                    pw.println("Connected to Sling Id : not connected");
+                    pw.print("Connector status : not ok");
+                    if ( tooltipText != null ) {
+                        pw.print(" (");
+                        pw.print(tooltipText);
+                        pw.print(")");
+                    }
+                    pw.print(" (HTTP StatusCode: "+statusCode+", "+statusDetails+")");
+                    pw.println();
+                    pw.println("Last heartbeat sent : "+beautifiedTimeDiff(topologyConnectorClient.getLastPingSent()));
+                    pw.println("Next heartbeat due : "+beautifiedDueTime(topologyConnectorClient.getNextPingDue()));
+                }
+                pw.println();
+            }
+            pw.println();
+            pw.println();
+        }
+
+        ResourceResolver resourceResolver = null;
+        pw.println("Discovery-Lite Descriptor History");
+        pw.println("---------------------------------------");
+        for (String discoLiteHistoryEntry : discoveryLiteHistory) {
+            pw.println(discoLiteHistoryEntry);
+        }
+        pw.println();
+        pw.println();
+        pw.println("Current Discovery-Lite Descriptor Value");
+        pw.println("---------------------------------------");
+        try{
+            resourceResolver = getResourceResolver();
+            DiscoveryLiteDescriptor descriptor = DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+            final String logEntry = getCurrentDateFormatted() + ": " + descriptor.getDescriptorStr();
+            pw.println(logEntry);
+            pw.println();
+            pw.println();
+        } catch(Exception e) {
+            logger.error("renderOverview: Exception: "+e, e);
+            pw.println("Got exception trying to get repository descriptor: "+e);
+            pw.println();
+            pw.println();
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+
+        if ( topologyLog.size() > 0 ) {
+            pw.println("Topology Change History");
+            pw.println("---------------------------------------");
+            for(final String aLogEntry : topologyLog) {
+                pw.println(aLogEntry);
+            }
+            pw.println();
+            pw.println();
+        }
+
+        if ( propertyChangeLog.size() > 0 ) {
+            pw.println("Property Change History");
+            pw.println("---------------------------------------");
+            for(final String aLogEntry : propertyChangeLog) {
+                pw.println(aLogEntry);
+            }
+            pw.println();
+        }
+    }
+
+    private String getCurrentDateFormatted() {
+        return sdf.format(Calendar.getInstance().getTime());
+    }
+
+    /**
+     * Render a particular cluster
+     */
+    private void printCluster(final PrintWriter pw, final ClusterView renderCluster, final ClusterView localCluster) {
+        final Collection<Announcement> announcements = announcementRegistry.listAnnouncementsInSameCluster(localCluster);
+
+        for(final InstanceDescription instanceDescription : renderCluster.getInstances() ) {
+            final boolean inLocalCluster = renderCluster == localCluster;
+            Announcement parentAnnouncement = null;
+            for (Iterator<Announcement> it2 = announcements.iterator(); it2
+                    .hasNext();) {
+                Announcement announcement = it2.next();
+                for (Iterator<InstanceDescription> it3 = announcement
+                        .listInstances().iterator(); it3.hasNext();) {
+                    InstanceDescription announcedInstance = it3.next();
+                    if (announcedInstance.getSlingId().equals(
+                            instanceDescription.getSlingId())) {
+                        parentAnnouncement = announcement;
+                        break;
+                    }
+                }
+            }
+
+            final boolean isLocal = instanceDescription.isLocal();
+            final String slingId = instanceDescription.getSlingId();
+
+            pw.print("Sling ID : ");
+            pw.print(slingId);
+            pw.println();
+            pw.print("Cluster View ID : ");
+            pw.print(instanceDescription.getClusterView() == null ? "null"
+                            : instanceDescription.getClusterView().getId());
+            pw.println();
+            pw.print("Local instance : ");
+            pw.print(isLocal);
+            pw.println();
+            pw.print("Leader instance : ");
+            pw.print(instanceDescription.isLeader());
+            pw.println();
+            pw.print("In local cluster : ");
+            if (inLocalCluster) {
+                pw.print("local");
+            } else {
+                pw.print("remote");
+            }
+            pw.println();
+            pw.print("Announced by : ");
+            if (inLocalCluster) {
+                pw.print("n/a");
+            } else {
+                if (parentAnnouncement != null) {
+                    pw.print(parentAnnouncement.getOwnerId());
+                } else {
+                    pw.print("(changing)");
+                }
+            }
+            pw.println();
+
+            pw.println("Properties:");
+            for(final Map.Entry<String, String> entry : instanceDescription.getProperties().entrySet()) {
+                pw.print("- ");
+                pw.print(entry.getKey());
+                pw.print(" : ");
+                pw.print(entry.getValue());
+                pw.println();
+            }
+            pw.println();
+            pw.println();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java b/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java
new file mode 100644
index 0000000..ce55cc2
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/oak/cluster/OakClusterViewService.java
@@ -0,0 +1,227 @@
+/*
+ * 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.oak.cluster;
+
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+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.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.discovery.InstanceDescription;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.commons.UndefinedClusterViewException;
+import org.apache.sling.discovery.base.commons.UndefinedClusterViewException.Reason;
+import org.apache.sling.discovery.commons.providers.DefaultInstanceDescription;
+import org.apache.sling.discovery.commons.providers.spi.LocalClusterView;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptor;
+import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
+import org.apache.sling.discovery.oak.Config;
+import org.apache.sling.settings.SlingSettingsService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Oak-based implementation of the ClusterViewService interface.
+ */
+@Component
+@Service(value = ClusterViewService.class)
+public class OakClusterViewService implements ClusterViewService {
+    
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private SlingSettingsService settingsService;
+
+    @Reference
+    private ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    private Config config;
+    
+    @Reference
+    private IdMapService idMapService;
+    
+    public static OakClusterViewService testConstructor(SlingSettingsService settingsService,
+            ResourceResolverFactory resourceResolverFactory,
+            IdMapService idMapService,
+            Config config) {
+        OakClusterViewService service = new OakClusterViewService();
+        service.settingsService = settingsService;
+        service.resourceResolverFactory = resourceResolverFactory;
+        service.config = config;
+        service.idMapService = idMapService;
+        return service;
+    }
+    
+    public String getSlingId() {
+    	if (settingsService==null) {
+    		return null;
+    	}
+        return settingsService.getSlingId();
+    }
+
+    protected ResourceResolver getResourceResolver() throws LoginException {
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+
+    public LocalClusterView getLocalClusterView() throws UndefinedClusterViewException {
+        logger.trace("getLocalClusterView: start");
+        ResourceResolver resourceResolver = null;
+        try{
+            resourceResolver = getResourceResolver();
+            DiscoveryLiteDescriptor descriptor = 
+                    DiscoveryLiteDescriptor.getDescriptorFrom(resourceResolver);
+            return asClusterView(descriptor, resourceResolver);
+        } catch (UndefinedClusterViewException e) {
+            logger.info("getLocalClusterView: undefined clusterView: "+e.getReason()+" - "+e.getMessage());
+            throw e;
+        } catch (Exception e) {
+            logger.error("getLocalClusterView: repository exception: "+e, e);
+            throw new UndefinedClusterViewException(Reason.REPOSITORY_EXCEPTION, "Exception while processing descriptor: "+e);
+        } finally {
+            logger.trace("getLocalClusterView: end");
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    private LocalClusterView asClusterView(DiscoveryLiteDescriptor descriptor, ResourceResolver resourceResolver) throws Exception {
+        if (descriptor == null) {
+            throw new IllegalArgumentException("descriptor must not be null");
+        }
+        if (resourceResolver==null) {
+            throw new IllegalArgumentException("resourceResolver must not be null");
+        }
+        logger.trace("asClusterView: start");
+        String clusterViewId = descriptor.getViewId();
+        String localClusterSyncTokenId = descriptor.getViewId()+"_"+descriptor.getSeqNum();
+        if (!descriptor.isFinal()) {
+            throw new UndefinedClusterViewException(Reason.NO_ESTABLISHED_VIEW, "descriptor is not yet final: "+descriptor);
+        }
+        LocalClusterView cluster = new LocalClusterView(clusterViewId, localClusterSyncTokenId);
+        long me = descriptor.getMyId();
+        int[] activeIds = descriptor.getActiveIds();
+        if (activeIds==null || activeIds.length==0) {
+            throw new UndefinedClusterViewException(Reason.NO_ESTABLISHED_VIEW, "Descriptor contained no active ids: "+descriptor.getDescriptorStr());
+        }
+        // convert int[] to List<Integer>
+        //TODO: could use Guava's Ints class here..
+        List<Integer> activeIdsList = new LinkedList<Integer>();
+        for (Integer integer : activeIds) {
+            activeIdsList.add(integer);
+        }
+        
+        // step 1: sort activeIds by their leaderElectionId
+        //   serves two purposes: pos[0] is then leader
+        //   and the rest are properly sorted within the cluster
+        final Map<Integer, String> leaderElectionIds = new HashMap<Integer, String>();
+        for (Integer id : activeIdsList) {
+            String slingId = idMapService.toSlingId(id, resourceResolver);
+            if (slingId == null) {
+                throw new UndefinedClusterViewException(Reason.NO_ESTABLISHED_VIEW,
+                        "no slingId mapped for clusterNodeId="+id);
+            }
+            String leaderElectionId = getLeaderElectionId(resourceResolver,
+                    slingId);
+            leaderElectionIds.put(id, leaderElectionId);
+        }
+        
+        Collections.sort(activeIdsList, new Comparator<Integer>() {
+
+            @Override
+            public int compare(Integer arg0, Integer arg1) {
+                return leaderElectionIds.get(arg0)
+                        .compareTo(leaderElectionIds.get(arg1));
+            }
+        });
+        
+        for(int i=0; i<activeIdsList.size(); i++) {
+            int id = activeIdsList.get(i);
+            boolean isLeader = i==0; // thx to sorting above [0] is leader indeed
+            boolean isOwn = id==me;
+            String slingId = idMapService.toSlingId(id, resourceResolver);
+            if (slingId==null) {
+                logger.info("asClusterView: cannot resolve oak-clusterNodeId {} to a slingId", id);
+                throw new Exception("Cannot resolve oak-clusterNodeId "+id+" to a slingId");
+            }
+            Map<String, String> properties = readProperties(slingId, resourceResolver);
+            // create a new instance (adds itself to the cluster in the constructor)
+            new DefaultInstanceDescription(cluster, isLeader, isOwn, slingId, properties);
+        }
+        logger.trace("asClusterView: returning {}", cluster);
+        InstanceDescription local = cluster.getLocalInstance();
+        if (local != null) {
+            return cluster;
+        } else {
+            logger.info("getClusterView: the local instance ("+getSlingId()+") is currently not included in the existing established view! "
+                    + "This is normal at startup. At other times is pseudo-network-partitioning is an indicator for repository/network-delays or clocks-out-of-sync (SLING-3432). "
+                    + "(increasing the heartbeatTimeout can help as a workaround too) "
+                    + "The local instance will stay in TOPOLOGY_CHANGING or pre _INIT mode until a new vote was successful.");
+            throw new UndefinedClusterViewException(Reason.ISOLATED_FROM_TOPOLOGY, 
+                    "established view does not include local instance - isolated");
+        }
+    }
+
+    private String getLeaderElectionId(ResourceResolver resourceResolver, String slingId) {
+        if (slingId==null) {
+            throw new IllegalStateException("slingId must not be null");
+        }
+        final String myClusterNodePath = config.getClusterInstancesPath()+"/"+slingId;
+        ValueMap resourceMap = resourceResolver.getResource(myClusterNodePath)
+                .adaptTo(ValueMap.class);
+        String result = resourceMap.get("leaderElectionId", String.class);
+        return result;
+    }
+    
+    private Map<String, String> readProperties(String slingId, ResourceResolver resourceResolver) {
+        Resource res = resourceResolver.getResource(
+                        config.getClusterInstancesPath() + "/"
+                                + slingId);
+        final Map<String, String> props = new HashMap<String, String>();
+        if (res != null) {
+            final Resource propertiesChild = res.getChild("properties");
+            if (propertiesChild != null) {
+                final ValueMap properties = propertiesChild.adaptTo(ValueMap.class);
+                if (properties != null) {
+                    for (Iterator<String> it = properties.keySet().iterator(); it
+                            .hasNext();) {
+                        String key = it.next();
+                        if (!key.equals("jcr:primaryType")) {
+                            props.put(key, properties.get(key, String.class));
+                        }
+                    }
+                }
+            }
+        }
+        return props;
+    }
+
+}
diff --git a/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java b/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java
new file mode 100644
index 0000000..cf50d26
--- /dev/null
+++ b/src/main/java/org/apache/sling/discovery/oak/pinger/OakViewChecker.java
@@ -0,0 +1,322 @@
+/*
+ * 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.oak.pinger;
+
+import java.util.Calendar;
+import java.util.UUID;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.ReferencePolicy;
+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.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.base.commons.BaseViewChecker;
+import org.apache.sling.discovery.base.connectors.BaseConfig;
+import org.apache.sling.discovery.base.connectors.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.base.connectors.ping.ConnectorRegistry;
+import org.apache.sling.discovery.commons.providers.util.ResourceHelper;
+import org.apache.sling.discovery.oak.Config;
+import org.apache.sling.discovery.oak.OakDiscoveryService;
+import org.apache.sling.launchpad.api.StartupListener;
+import org.apache.sling.launchpad.api.StartupMode;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.service.http.HttpService;
+
+/**
+ * The OakViewChecker is taking care of checking the oak discovery-lite
+ * descriptor when checking the local cluster view and passing that
+ * on to the ViewStateManager which will then detect whether there was 
+ * any change or not. Unlike discovery.impl's HeartbeatHandler this one
+ * does not store any heartbeats in the repository anymore.
+ * <p>
+ * Remote heartbeats are POSTs to remote TopologyConnectorServlets using
+ * discovery.base
+ */
+@Component
+@Service(value = { OakViewChecker.class, StartupListener.class })
+@Reference(referenceInterface=HttpService.class,
+           cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE,
+           policy=ReferencePolicy.DYNAMIC)
+public class OakViewChecker extends BaseViewChecker {
+
+    @Reference
+    protected SlingSettingsService slingSettingsService;
+
+    @Reference
+    protected ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    protected ConnectorRegistry connectorRegistry;
+
+    @Reference
+    protected AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    protected Scheduler scheduler;
+
+    @Reference
+    private Config config;
+
+    private OakDiscoveryService discoveryService;
+
+    /** for testing only **/
+    public static OakViewChecker testConstructor(
+            SlingSettingsService slingSettingsService,
+            ResourceResolverFactory resourceResolverFactory,
+            ConnectorRegistry connectorRegistry,
+            AnnouncementRegistry announcementRegistry,
+            Scheduler scheduler,
+            Config config) {
+        OakViewChecker pinger = new OakViewChecker();
+        pinger.slingSettingsService = slingSettingsService;
+        pinger.resourceResolverFactory = resourceResolverFactory;
+        pinger.connectorRegistry = connectorRegistry;
+        pinger.announcementRegistry = announcementRegistry;
+        pinger.scheduler = scheduler;
+        pinger.config = config;
+        return pinger;
+    }
+
+    @Override
+    protected AnnouncementRegistry getAnnouncementRegistry() {
+        return announcementRegistry;
+    }
+    
+    @Override
+    protected BaseConfig getConnectorConfig() {
+        return config;
+    }
+    
+    @Override
+    protected ConnectorRegistry getConnectorRegistry() {
+        return connectorRegistry;
+    }
+    
+    @Override
+    protected ResourceResolverFactory getResourceResolverFactory() {
+        return resourceResolverFactory;
+    }
+    
+    @Override
+    protected Scheduler getScheduler() {
+        return scheduler;
+    }
+    
+    @Override
+    protected SlingSettingsService getSlingSettingsService() {
+        return slingSettingsService;
+    }
+    
+    @Override
+    protected void doActivate() {
+        // on activate the resetLeaderElectionId is set to true to ensure that
+        // the 'leaderElectionId' property is reset on next heartbeat issuance.
+        // the idea being that a node which leaves the cluster should not
+        // become leader on next join - and by resetting the leaderElectionId
+        // to the current time, this is ensured.
+        runtimeId = UUID.randomUUID().toString();
+
+        logger.info("doActivate: activated with runtimeId: {}, slingId: {}", runtimeId, slingId);
+        
+        resetLeaderElectionId();
+    }
+    
+    @Override
+    public void startupFinished(StartupMode mode) {
+        super.startupFinished(mode);
+        
+        synchronized(lock) {
+            if (activated) {
+                // only reset if activated
+                resetLeaderElectionId();
+            }
+        }
+
+    }
+    
+    /**
+     * The initialize method is called by the OakDiscoveryService.activate
+     * as we require the discoveryService (and the discoveryService has
+     * a reference on us - but we cant have circular references in osgi).
+     */
+    public void initialize(final OakDiscoveryService discoveryService) {
+        logger.info("initialize: initializing.");
+        synchronized(lock) {
+        	this.discoveryService = discoveryService;
+            issueHeartbeat();
+        }
+
+        // start the (less frequent) periodic job that does the
+        // connector pings and checks the connector/topology view
+        try {
+            final long interval = config.getConnectorPingInterval();
+            logger.info("initialize: starting periodic connectorPing job for "+slingId+" with interval "+interval+" sec.");
+            scheduler.addPeriodicJob(NAME+".connectorPinger", this,
+                    null, interval, false);
+        } catch (Exception e) {
+            logger.error("activate: Could not start heartbeat runner: " + e, e);
+        }
+        
+        // start the (more frequent) periodic job that checks
+        // the discoveryLite descriptor - that can be more frequent
+        // since it is only reading an oak repository descriptor
+        // which is designed to be read very frequently (it caches
+        // the value and only updates it on change, so reading is very cheap)
+        // and because doing this more frequently means that the
+        // reaction time is faster
+        try{
+            final long interval = config.getDiscoveryLiteCheckInterval();
+            logger.info("initialize: starting periodic discoveryLiteCheck job for "+slingId+" with interval "+interval+" sec.");
+            scheduler.addPeriodicJob(NAME+".discoveryLiteCheck", new Runnable() {
+
+                @Override
+                public void run() {
+                    discoveryLiteCheck();
+                }
+                
+            },
+                    null, interval, false);
+        } catch (Exception e) {
+            logger.error("activate: Could not start heartbeat runner: " + e, e);
+        }
+    }
+    
+    private void discoveryLiteCheck() {
+        logger.debug("discoveryLiteCheck: start. [for slingId="+slingId+"]");
+        synchronized(lock) {
+            if (!activated) {
+                // SLING:2895: avoid checks if not activated
+                logger.debug("discoveryLiteCheck: not activated yet");
+                return;
+            }
+
+            // check the view
+            // discovery.oak relies on oak's discovery-lite descriptor
+            // to be updated independently in case of cluster view change.
+            // all that we can therefore do here is assume something
+            // might have changed and let discoveryService/viewStateManager
+            // filter out the 99.99% of unchanged cases.
+            discoveryService.handlePotentialTopologyChange();
+        }
+        logger.debug("discoveryLiteCheck: end. [for slingId="+slingId+"]");
+    }
+
+    /** Get or create a ResourceResolver **/
+    private ResourceResolver getResourceResolver() throws LoginException {
+        if (resourceResolverFactory == null) {
+            logger.error("getResourceResolver: resourceResolverFactory is null!");
+            return null;
+        }
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+
+    /** Calcualte the local cluster instance path **/
+    private String getLocalClusterNodePath() {
+        return config.getClusterInstancesPath() + "/" + slingId;
+    }
+
+    /**
+     * Hook that will cause a reset of the leaderElectionId 
+     * on next invocation of issueClusterLocalHeartbeat.
+     * @return true if the leaderElectionId was reset - false if that was not
+     * necessary as that happened earlier already and it has not propagated
+     * yet to the ./clusterInstances in the meantime
+     */
+    public boolean resetLeaderElectionId() {
+        ResourceResolver resourceResolver = null;
+        try{
+            final String myClusterNodePath = getLocalClusterNodePath();
+            resourceResolver = getResourceResolver();
+            if (resourceResolver==null) {
+                logger.warn("resetLeaderElectionId: could not login, new leaderElectionId will be calculated upon next heartbeat only!");
+                return false;
+            }
+            String newLeaderElectionId = newLeaderElectionId();
+
+            final Resource resource = ResourceHelper.getOrCreateResource(
+                    resourceResolver, myClusterNodePath);
+            final ModifiableValueMap resourceMap = resource.adaptTo(ModifiableValueMap.class);
+
+            resourceMap.put(PROPERTY_ID_RUNTIME, runtimeId);
+            // SLING-4765 : store more infos to be able to be more verbose on duplicate slingId/ghost detection
+            final String slingHomePath = slingSettingsService==null ? "n/a" : slingSettingsService.getSlingHomePath();
+            resourceMap.put(PROPERTY_ID_SLING_HOME_PATH, slingHomePath);
+            final String endpointsAsString = getEndpointsAsString();
+            resourceMap.put(PROPERTY_ID_ENDPOINTS, endpointsAsString);
+
+            Calendar leaderElectionCreatedAt = Calendar.getInstance();
+            resourceMap.put("leaderElectionId", newLeaderElectionId);
+            resourceMap.put("leaderElectionIdCreatedAt", leaderElectionCreatedAt);
+
+            logger.info("resetLeaderElectionId: storing my runtimeId: {}, endpoints: {} and sling home path: {}", 
+                    new Object[]{runtimeId, endpointsAsString, slingHomePath, newLeaderElectionId, leaderElectionCreatedAt});
+            resourceResolver.commit();
+        } catch (LoginException e) {
+            logger.error("resetLeaderElectionid: could not login: "+e, e);
+        } catch (PersistenceException e) {
+            logger.error("resetLeaderElectionid: got PersistenceException: "+e, e);
+        } finally {
+            if (resourceResolver!=null) {
+                resourceResolver.close();
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Calculate a new leaderElectionId based on the current config and system time
+     */
+    private String newLeaderElectionId() {
+        int maxLongLength = String.valueOf(Long.MAX_VALUE).length();
+        String currentTimeMillisStr = String.format("%0"
+                + maxLongLength + "d", System.currentTimeMillis());
+
+        String prefix = "1";
+
+        final String newLeaderElectionId = prefix + "_"
+                + currentTimeMillisStr + "_" + slingId;
+        return newLeaderElectionId;
+    }
+
+    @Override
+    protected void doCheckView() {
+        super.doCheckView();
+
+        // discovery.oak relies on oak's discovery-lite descriptor
+        // to be updated independently in case of cluster view change.
+        // all that we can therefore do here is assume something
+        // might have changed and let discoveryService/viewStateManager
+        // filter out the 99.99% of unchanged cases.
+        discoveryService.handlePotentialTopologyChange();
+    }
+
+    protected void updateProperties() {
+        if (discoveryService == null) {
+            logger.error("issueHeartbeat: discoveryService is null");
+        } else {
+            discoveryService.updateProperties();
+        }
+    }}
diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..de9301f
--- /dev/null
+++ b/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,121 @@
+#
+#  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.
+#
+
+#
+# This file contains localization strings for configuration labels and
+# descriptions as used in the metatype.xml descriptor generated by the
+# the SCR plugin
+config.name=Apache Sling Oak-Based Discovery Service Configuration
+config.description = The configuration of the Oak based discovery service implementation.
+
+connectorPingTimeout.name = Connector Ping timeout (seconds)
+connectorPingTimeout.description = Configure the timeout (in seconds) after which an announcement \
+ sent via a topology connector is considered timed out. Default is 120 seconds.
+
+connectorPingInterval.name = Connector Ping interval (seconds)
+connectorPingInterval.description = Configure the interval (in seconds) according to which the \
+ topology connector pings are exchanged in the topology. Default is 30 seconds.
+
+discoveryLiteCheckInterval.name = Discovery-Lite Check interval (seconds)
+discoveryLiteCheckInterval.description = Configure the interval (in seconds) with which Oak's \
+ discoveryLite descriptor should be checked for changes. Default is 2 seconds. \
+ Note that the timeout value is configured within Oak directly.
+
+minEventDelay.name = Minimal Event Delay (seconds)
+minEventDelay.description = Configure a minimal delay (in seconds) between TOPOLOGY_CHANGING \
+ and TOPOLOGY_CHANGED. Any further changes happening during this delay are accumulated and \
+ combined in the TOPOLOGY_CHANGED after this delay. This helps avoiding event-flooding. \
+ Default is 3 seconds. A negative value or zero disables this delay.
+
+topologyConnectorUrls.name = Topology Connector URLs
+topologyConnectorUrls.description = URLs where to join a topology, e.g. \
+ http://localhost:4502/libs/sling/topology/connector
+
+topologyConnectorWhitelist.name = Topology Connector Whitelist
+topologyConnectorWhitelist.description = List of IPs and/or hostnames which are allowed to \
+ connect to the connector URL. There are four variants here: 1. provide a plain hostname. \
+ 2. provide an IP address. 3. provide a hostname or IP address with wildcards (* or ?). \
+ 4. provide an IP address with a subnet mask, either using the CIDR notation: 1.2.3.4/24 \
+ or an IP address, space, subnet mask: 1.2.3.4 255.255.255.0)
+
+discoveryResourcePath.name = Discovery Resource Path
+discoveryResourcePath.description = Path of resource where to keep discovery information. \
+ The default is /var/discovery/oak.
+
+leaderElectionRepositoryDescriptor.name = Repository Descriptor Name
+leaderElectionRepositoryDescriptor.description = Name of the repository descriptor to be taken \
+ into account for leader election: those instances have preference to become leader which have \
+ the corresponding descriptor value of 'false'.
+ 
+invertRepositoryDescriptor.name = Invert Repository Descriptor
+invertRepositoryDescriptor.description = Enabling this property allows to invert the \
+ repository descriptor value that is obtained via the configured 'leaderElectionRepositoryDescriptor' \
+ (thus only applies if that is configured). Default is 'false' (don't invert).
+
+autoStopLocalLoopEnabled.name = Auto-Stop Local-Loops
+autoStopLocalLoopEnabled.description = If true, and the discovery.impl detects a local-looping \
+ topology connector, the corresponding topology connector will be automatically stopped. \
+ This is useful to prevent unnecessary loops with eg pre-configured topology connectors.
+
+gzipConnectorRequestsEnabled.name = gzip requests
+gzipConnectorRequestsEnabled.description = If true, the payloads of topology connector requests \
+ will be gzipped. This is advisable on certain connector structures, eg in a tree structure, where \
+ a topology connector announces a large sub-topology. Note that this only works with \
+ the server running discovery.impl 1.0.4 and onwards. Replies are gzipped automatically.
+
+socketConnectTimeout.name = connector's socket.connect() timeout
+socketConnectTimeout.description = Timeout (in seconds!) for the topology connector's \
+ socket.connect()
+ 
+soTimeout.name = connector's read timeout
+soTimeout.description = Topology connector's socket timeout (SO_TIMEOUT) (in seconds!) which is \
+ the timeout for waiting for data
+
+hmacEnabled.name = Enable Hmac message signatures
+hmacEnabled.description = If true, and the Shared Key is set to the same value on all members of the \
+ topology, the messages will be validated using a HMAC of a digest of the body of the message. \
+ The hmac and message digest are in the HTTP request and response headers. Both requests and responses \
+ are signed.
+
+enableEncryption.name = Enable Message encryption
+enableEncryption.description = If Message HMACs are enabled and there is a shared key set, setting this to \
+ true will encrypt the body of the message using 128 bit AES encryption. Once encrypted you will not be able \
+ debug the messages at the http level.
+
+sharedKey.name = Message shared key.
+sharedKey.description = If message signing and encryption is used, this should be set to the same value \
+ on all members of the same topology. If any member of the topology has a different key it will effectively \
+ be excluded from the topology even if it attempts to send messages to other members of the topology.
+
+hmacSharedKeyTTL.name = Shared Key TTL
+hmacSharedKeyTTL.description = Shared keys for message signatures are derived from the configured shared key. \
+ Each derived key has a lifetime (TTL). Once that time has expired a new key is derived and used for hmac signatures. \
+ This setting, sets the TTL in ms. Keys that are 2 lifetimes old are ignored. Set according to you level of paranoia, \
+ but don't set to less than the greatest possible clock drift between members of the topology. The default is 4 hours. Setting \
+ to a ridiculously low value will increase the turnover of keys. Generating a key takes about 2ms. There is no risk of \
+ memory consumption with low values, only a risk of the topology falling apart due to incorrectly set clocks.
+
+backoffStableFactor.name = Backoff factor for stable connectors
+backoffStableFactor.description = When a topology connector is stable (ie no changes occuring in the announcements sent), \
+ then the heartbeat frequency is lowered, ie the heartbeatInterval for this connector is steadily increased, at maximum by the \
+ backoffStableFactor.
+
+backoffStandbyFactor.name = Backoff factor for standby connectors
+backoffStandbyFactor.description = When a topology connector is in standby mode (ie when it is redundant), the heartbeat \
+ frequency is lowered, ie the heartbeatInterval for this connector is increased , at maximum by the backoffStandbyFactor
diff --git a/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
new file mode 100644
index 0000000..7affca5
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/OakDiscoveryServiceTest.java
@@ -0,0 +1,116 @@
+/*
+ * 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.oak;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.UUID;
+
+import org.apache.sling.discovery.commons.providers.base.DummyListener;
+import org.apache.sling.discovery.commons.providers.spi.base.DescriptorHelper;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteConfig;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptorBuilder;
+import org.apache.sling.discovery.commons.providers.spi.base.DummySlingSettingsService;
+import org.apache.sling.discovery.commons.providers.spi.base.IdMapService;
+import org.apache.sling.discovery.oak.its.setup.OakVirtualInstanceBuilder;
+import org.junit.Test;
+
+public class OakDiscoveryServiceTest {
+
+    public final class SimpleCommonsConfig implements DiscoveryLiteConfig {
+        
+        private long bgIntervalMillis;
+        private long bgTimeoutMillis;
+
+        SimpleCommonsConfig(long bgIntervalMillis, long bgTimeoutMillis) {
+            this.bgIntervalMillis = bgIntervalMillis;
+            this.bgTimeoutMillis = bgTimeoutMillis;
+        }
+        
+        @Override
+        public String getSyncTokenPath() {
+            return "/var/synctokens";
+        }
+
+        @Override
+        public String getIdMapPath() {
+            return "/var/idmap";
+        }
+
+        @Override
+        public long getBgTimeoutMillis() {
+            return bgTimeoutMillis;
+        }
+
+        @Override
+        public long getBgIntervalMillis() {
+            return bgIntervalMillis;
+        }
+
+    }
+
+    @Test
+    public void testBindBeforeActivate() throws Exception {
+        OakVirtualInstanceBuilder builder = 
+                (OakVirtualInstanceBuilder) new OakVirtualInstanceBuilder()
+                .setDebugName("test")
+                .newRepository("/foo/bar", true);
+        String slingId = UUID.randomUUID().toString();;
+        DiscoveryLiteDescriptorBuilder discoBuilder = new DiscoveryLiteDescriptorBuilder();
+        discoBuilder.id("id").me(1).activeIds(1);
+        // make sure the discovery-lite descriptor is marked as not final
+        // such that the view is not already set before we want it to be
+        discoBuilder.setFinal(false);
+        DescriptorHelper.setDiscoveryLiteDescriptor(builder.getResourceResolverFactory(), 
+                discoBuilder);
+        IdMapService idMapService = IdMapService.testConstructor(new SimpleCommonsConfig(1000, -1), new DummySlingSettingsService(slingId), builder.getResourceResolverFactory());
+        assertTrue(idMapService.waitForInit(2000));
+        OakDiscoveryService discoveryService = (OakDiscoveryService) builder.getDiscoverService();
+        assertNotNull(discoveryService);
+        DummyListener listener = new DummyListener();
+        for(int i=0; i<100; i++) {
+            discoveryService.bindTopologyEventListener(listener);
+            discoveryService.unbindTopologyEventListener(listener);
+        }
+        discoveryService.bindTopologyEventListener(listener);
+        assertEquals(0, listener.countEvents());
+        discoveryService.activate(null);
+        assertEquals(0, listener.countEvents());
+        // some more confusion...
+        discoveryService.unbindTopologyEventListener(listener);
+        discoveryService.bindTopologyEventListener(listener);
+        // only set the final flag now - this makes sure that handlePotentialTopologyChange
+        // will actually detect a valid new, different view and send out an event -
+        // exactly as we want to
+        discoBuilder.setFinal(true);
+        DescriptorHelper.setDiscoveryLiteDescriptor(builder.getResourceResolverFactory(), 
+                discoBuilder);
+        discoveryService.handlePotentialTopologyChange();
+        assertTrue(discoveryService.getViewStateManager().waitForAsyncEvents(2000));
+        assertEquals(1, listener.countEvents());
+        discoveryService.unbindTopologyEventListener(listener);
+        assertEquals(1, listener.countEvents());
+        discoveryService.bindTopologyEventListener(listener);
+        assertTrue(discoveryService.getViewStateManager().waitForAsyncEvents(2000));
+        assertEquals(2, listener.countEvents()); // should now have gotten an INIT too
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/OakClusterLoadTest.java b/src/test/java/org/apache/sling/discovery/oak/its/OakClusterLoadTest.java
new file mode 100644
index 0000000..5ce0636
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/OakClusterLoadTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.oak.its;
+
+import org.apache.sling.discovery.base.its.AbstractClusterLoadTest;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.oak.its.setup.OakVirtualInstanceBuilder;
+
+public class OakClusterLoadTest extends AbstractClusterLoadTest {
+
+    @Override
+    public VirtualInstanceBuilder newBuilder() {
+        return new OakVirtualInstanceBuilder();
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/OakClusterTest.java b/src/test/java/org/apache/sling/discovery/oak/its/OakClusterTest.java
new file mode 100644
index 0000000..1d4e5df
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/OakClusterTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.oak.its;
+
+import org.apache.sling.discovery.base.its.AbstractClusterTest;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.oak.its.setup.OakVirtualInstanceBuilder;
+
+public class OakClusterTest extends AbstractClusterTest {
+
+    @Override
+    protected VirtualInstanceBuilder newBuilder() {
+        return new OakVirtualInstanceBuilder();
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/OakSingleInstanceTest.java b/src/test/java/org/apache/sling/discovery/oak/its/OakSingleInstanceTest.java
new file mode 100644
index 0000000..c0e5499
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/OakSingleInstanceTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.oak.its;
+
+import org.apache.sling.discovery.base.its.AbstractSingleInstanceTest;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.oak.its.setup.OakVirtualInstanceBuilder;
+
+public class OakSingleInstanceTest extends AbstractSingleInstanceTest {
+
+    @Override
+    protected VirtualInstanceBuilder newBuilder() {
+        return new OakVirtualInstanceBuilder();
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/OakTopologyEventTest.java b/src/test/java/org/apache/sling/discovery/oak/its/OakTopologyEventTest.java
new file mode 100644
index 0000000..3b033a2
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/OakTopologyEventTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.oak.its;
+
+import static org.junit.Assert.assertNotNull;
+
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.base.its.AbstractTopologyEventTest;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.oak.its.setup.OakVirtualInstanceBuilder;
+
+public class OakTopologyEventTest extends AbstractTopologyEventTest {
+
+    @Override
+    public VirtualInstanceBuilder newBuilder() {
+        return new OakVirtualInstanceBuilder();
+    }
+
+    @Override
+    public void assertEarlyAndFirstClusterViewIdMatches(TopologyView earlyTopo, TopologyView secondTopo) {
+        // for the oak discovery-lite variant there's nothing we can assert here
+        // except perhaps that they shouldn't be null..
+        assertNotNull(earlyTopo);
+        assertNotNull(secondTopo);
+    }
+    
+}
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/setup/OakTestConfig.java b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakTestConfig.java
new file mode 100644
index 0000000..f84251b
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakTestConfig.java
@@ -0,0 +1,80 @@
+/*
+ * 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.oak.its.setup;
+
+import org.apache.sling.discovery.base.its.setup.ModifiableTestBaseConfig;
+import org.apache.sling.discovery.oak.Config;
+
+public class OakTestConfig extends Config implements ModifiableTestBaseConfig {
+
+    public OakTestConfig() {
+        // empty
+    }
+    
+    public void setDiscoveryResourcePath(String discoveryResourcePath) {
+        this.discoveryResourcePath = discoveryResourcePath;
+    }
+
+    public void setMinEventDelay(int minEventDelay) {
+        this.minEventDelay = minEventDelay;
+    }
+
+    public void addTopologyConnectorWhitelistEntry(String whitelistEntry) {
+        if (topologyConnectorWhitelist==null) {
+            topologyConnectorWhitelist = new String[] {whitelistEntry};
+        } else {
+            String[] list = topologyConnectorWhitelist;
+            topologyConnectorWhitelist = new String[list.length+1];
+            System.arraycopy(list, 0, topologyConnectorWhitelist, 0, list.length);
+            topologyConnectorWhitelist[topologyConnectorWhitelist.length-1] = whitelistEntry;
+        }
+    }
+
+    @Override
+    public int getBackoffStableFactor() {
+        return 1;
+    }
+
+    @Override
+    public int getBackoffStandbyFactor() {
+        return 1;
+    }
+
+    public void setConnectorInterval(long connectorInterval) {
+        this.connectorPingInterval = connectorInterval;
+    }
+
+    public void setConnectorTimeout(long connectorTimeout) {
+        this.connectorPingTimeout = connectorTimeout;
+    }
+    
+    @Override
+    public void setViewCheckTimeout(int viewCheckTimeout) {
+        setConnectorTimeout(viewCheckTimeout);
+    }
+
+    @Override
+    public void setViewCheckInterval(int viewCheckInterval) {
+        setConnectorInterval(viewCheckInterval);
+    }
+    
+    public long getViewCheckerTimeout() {
+        return connectorPingTimeout;
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
new file mode 100644
index 0000000..2080c0e
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/setup/OakVirtualInstanceBuilder.java
@@ -0,0 +1,259 @@
+/*
+ * 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.oak.its.setup;
+
+import static org.junit.Assert.fail;
+
+import javax.jcr.Session;
+
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.discovery.base.commons.BaseDiscoveryService;
+import org.apache.sling.discovery.base.commons.ClusterViewService;
+import org.apache.sling.discovery.base.commons.ViewChecker;
+import org.apache.sling.discovery.base.its.setup.ModifiableTestBaseConfig;
+import org.apache.sling.discovery.base.its.setup.VirtualInstance;
+import org.apache.sling.discovery.base.its.setup.VirtualInstanceBuilder;
+import org.apache.sling.discovery.base.its.setup.mock.MockFactory;
+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.RepositoryTestHelper;
+import org.apache.sling.discovery.oak.OakDiscoveryService;
+import org.apache.sling.discovery.oak.cluster.OakClusterViewService;
+import org.apache.sling.discovery.oak.pinger.OakViewChecker;
+import org.apache.sling.jcr.api.SlingRepository;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import junitx.util.PrivateAccessor;
+
+public class OakVirtualInstanceBuilder extends VirtualInstanceBuilder {
+
+    NodeStore nodeStore;
+    private String path;
+    private OakTestConfig config;
+    private IdMapService idMapService;
+    private OakViewChecker oakViewChecker;
+    private SimulatedLeaseCollection leaseCollection;
+    private OakSyncTokenConsistencyService consistencyService;
+    
+    @Override
+    public VirtualInstanceBuilder createNewRepository() throws Exception {
+        nodeStore = new MemoryNodeStore();
+        SlingRepository repository = RepositoryTestHelper.newOakRepository(nodeStore);
+        factory = MockFactory.mockResourceResolverFactory(repository);
+        leaseCollection = new SimulatedLeaseCollection();
+        return this;
+    }
+    
+    @Override
+    public VirtualInstanceBuilder useRepositoryOf(VirtualInstanceBuilder other) throws Exception {
+        if (!(other instanceof OakVirtualInstanceBuilder)) {
+            throw new IllegalArgumentException("other must be of type OakVirtualInstanceBuilder but is: "+other);
+        }
+        OakVirtualInstanceBuilder otherOakbuilder = (OakVirtualInstanceBuilder)other;
+        nodeStore = otherOakbuilder.nodeStore;
+        SlingRepository repository = RepositoryTestHelper.newOakRepository(nodeStore);
+        factory = MockFactory.mockResourceResolverFactory(repository);
+        leaseCollection = otherOakbuilder.leaseCollection;
+        hookedToBuilder = other;
+        ownRepository = false;
+        return this;
+    }
+
+    @Override
+    public VirtualInstanceBuilder setPath(String path) {
+        this.path = path;
+        return this;
+    }
+
+    @Override
+    public Object[] getAdditionalServices(VirtualInstance instance) throws Exception {
+        return null;
+    }
+    
+    public IdMapService getIdMapService() {
+        if (idMapService==null) {
+            idMapService = createIdMapService();
+        }
+        return idMapService;
+    }
+
+    private IdMapService createIdMapService() {
+        return IdMapService.testConstructor(getConfig(), getSlingSettingsService(), getResourceResolverFactory());
+    }
+
+    @Override
+    protected ClusterViewService createClusterViewService() {
+        return OakClusterViewService.testConstructor(getSlingSettingsService(), getResourceResolverFactory(), getIdMapService(), getConfig());
+    }
+    
+    OakTestConfig getConfig() {
+        if (config==null) {
+            config = createConfig();
+        }
+        return config;
+    }
+    
+    @Override
+    public ModifiableTestBaseConfig getConnectorConfig() {
+        return getConfig();
+    }
+    
+    private OakTestConfig createConfig() {
+        OakTestConfig c = new OakTestConfig();
+        c.setDiscoveryResourcePath(path);
+        return c;
+    }
+    
+    @Override
+    protected ViewChecker createViewChecker() throws Exception {
+        getOakViewChecker();
+        return new ViewChecker() {
+            
+            private final Logger logger = LoggerFactory.getLogger(getClass());
+
+            private SimulatedLease lease = new SimulatedLease(getResourceResolverFactory(), leaseCollection, getSlingId());
+            
+            protected void activate(ComponentContext c) throws Throwable {
+                OakViewChecker pinger = getOakViewChecker();
+                PrivateAccessor.invoke(pinger, "activate", new Class[] {ComponentContext.class}, new Object[] {c});
+            }
+            
+            @Override
+            public void checkView() {
+                try {
+                    lease.updateDescriptor(getConfig());
+                } catch (Exception e) {
+                    logger.error("run: could not update lease: "+e);
+                }
+            }
+            
+            public void run() {
+                heartbeatAndCheckView();
+            }
+            
+            @Override
+            public void heartbeatAndCheckView() {
+//                next step is try to simulate the logic
+//                where no heartbeat means no descriptor yet
+//                one heartbeat means i'm visible for others
+//                as soon as I see others the descriptor is updated
+                try {
+                    lease.updateLeaseAndDescriptor(getConfig());
+                } catch (Exception e) {
+                    logger.error("run: could not update lease: "+e, e);
+                }
+                try{
+                    getOakViewChecker().run();
+                } catch(Exception e) {
+                    logger.error("run: could not ping: "+e, e);
+                }
+                if (!getIdMapService().isInitialized()) {
+                    if (!getIdMapService().waitForInit(1500)) {
+                        fail("init didnt work");
+                    }
+                }
+            }
+        };
+    }
+
+    private OakViewChecker getOakViewChecker() throws Exception {
+        if (oakViewChecker==null) {
+            oakViewChecker = createOakViewChecker() ;
+        }
+        return oakViewChecker;
+    }
+
+    private OakViewChecker createOakViewChecker() throws Exception {
+        return OakViewChecker.testConstructor(getSlingSettingsService(), getResourceResolverFactory(), getConnectorRegistry(), getAnnouncementRegistry(), getScheduler(), getConfig());
+    }
+
+    private OakSyncTokenConsistencyService getConsistencyService() throws Exception {
+        if (consistencyService == null) {
+            consistencyService = createConsistencyService();
+        }
+        return consistencyService;
+    }
+    
+    private OakSyncTokenConsistencyService createConsistencyService() {
+        return OakSyncTokenConsistencyService.testConstructorAndActivate(getConfig(), getIdMapService(), getSlingSettingsService(), getResourceResolverFactory());
+    }
+
+    @Override
+    protected BaseDiscoveryService createDiscoveryService() throws Exception {
+        return OakDiscoveryService.testConstructor(
+                getSlingSettingsService(), 
+                getAnnouncementRegistry(), 
+                getConnectorRegistry(), 
+                getClusterViewService(), 
+                getConfig(), 
+                getOakViewChecker(), 
+                getScheduler(), 
+                getIdMapService(), 
+                getConsistencyService(), 
+                getResourceResolverFactory());
+    }
+
+    @Override
+    public VirtualInstance build() throws Exception {
+        if (path==null) {
+            if (ownRepository) {
+                setPath("/var/discovery/impl/");
+                getConfig().setDiscoveryResourcePath("/var/discovery/impl/");
+            } else {
+                OakVirtualInstanceBuilder other = (OakVirtualInstanceBuilder) hookedToBuilder;
+                this.path = other.path;
+                getConfig().setDiscoveryResourcePath(other.path);
+            }
+        }
+        if (path==null) {
+            throw new IllegalStateException("no path set");
+        }
+        if (!path.startsWith("/")) {
+            throw new IllegalStateException("path must start with /: "+path);
+        }
+        if (!path.endsWith("/")) {
+            throw new IllegalStateException("path must end with /: "+path);
+        }
+        VirtualInstance result = new VirtualInstance(this) {
+
+        };
+        return result;
+    }
+    
+    @Override
+    protected void resetRepo() throws Exception {
+        leaseCollection.reset();
+        ResourceResolver rr = null;
+        Session l = null;
+        try {
+            rr = factory.getAdministrativeResourceResolver(null);
+            l = rr.adaptTo(Session.class);
+            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/oak/its/setup/SimulatedLease.java b/src/test/java/org/apache/sling/discovery/oak/its/setup/SimulatedLease.java
new file mode 100644
index 0000000..aa49818
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/setup/SimulatedLease.java
@@ -0,0 +1,59 @@
+/*
+ * 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.oak.its.setup;
+
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.discovery.commons.providers.spi.base.DescriptorHelper;
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptorBuilder;
+
+public class SimulatedLease {
+
+    private final SimulatedLeaseCollection collection;
+    private final ResourceResolverFactory factory;
+    private final String slingId;
+
+    public SimulatedLease(ResourceResolverFactory factory,
+            SimulatedLeaseCollection collection,
+            String slingId) {
+        this.factory = factory;
+        this.collection = collection;
+        collection.hooked(this);
+        this.slingId = slingId;
+    }
+    
+    @Override
+    public String toString() {
+        return "a SimulatedLease[slingId="+slingId+"]";
+    }
+    
+    public String getSlingId() {
+        return slingId;
+    }
+
+    public void updateDescriptor(OakTestConfig config) throws Exception {
+        DiscoveryLiteDescriptorBuilder builder = collection.getDescriptorFor(this, config);
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory, builder);
+    }
+    
+    public void updateLeaseAndDescriptor(OakTestConfig config) throws Exception {
+        DiscoveryLiteDescriptorBuilder builder = collection.updateAndGetDescriptorFor(this, config);
+        DescriptorHelper.setDiscoveryLiteDescriptor(factory, builder);
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/discovery/oak/its/setup/SimulatedLeaseCollection.java b/src/test/java/org/apache/sling/discovery/oak/its/setup/SimulatedLeaseCollection.java
new file mode 100644
index 0000000..eed9568
--- /dev/null
+++ b/src/test/java/org/apache/sling/discovery/oak/its/setup/SimulatedLeaseCollection.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.oak.its.setup;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.sling.discovery.commons.providers.spi.base.DiscoveryLiteDescriptorBuilder;
+import org.apache.sling.discovery.oak.Config;
+
+public class SimulatedLeaseCollection {
+
+    private final Map<String,Long> leaseUpdates = 
+            new HashMap<String, Long>();
+    
+    private final Map<String,Integer> clusterNodeIds =
+            new HashMap<String, Integer>();
+    
+    private int highestId = 0;
+    
+    private final String viewId = UUID.randomUUID().toString();
+    
+    List<SimulatedLease> leases = new LinkedList<SimulatedLease>();
+    
+    public SimulatedLeaseCollection() {
+        // empty
+    }
+    
+    public synchronized void hooked(SimulatedLease lease) {
+        leases.add(lease);
+    }
+
+    public synchronized DiscoveryLiteDescriptorBuilder getDescriptorFor(SimulatedLease simulatedLease, OakTestConfig config) {
+        return doUpdateAndGet(simulatedLease, config, false);
+    }
+    
+    public synchronized DiscoveryLiteDescriptorBuilder updateAndGetDescriptorFor(SimulatedLease simulatedLease, OakTestConfig config) {
+        return doUpdateAndGet(simulatedLease, config, true);
+    }
+
+    private DiscoveryLiteDescriptorBuilder doUpdateAndGet(SimulatedLease simulatedLease, OakTestConfig config, boolean updateLease) {
+        int clusterNodeId = getClusterNodeId(simulatedLease.getSlingId());
+        if (updateLease) {
+            leaseUpdates.put(simulatedLease.getSlingId(), System.currentTimeMillis());
+        }
+        DiscoveryLiteDescriptorBuilder discoBuilder = 
+                new DiscoveryLiteDescriptorBuilder();
+        discoBuilder.me(clusterNodeId);
+        discoBuilder.id(viewId);
+        discoBuilder.setFinal(true);       
+        List<Integer> actives = new LinkedList<Integer>();
+        List<Integer> inactives = new LinkedList<Integer>();
+        for (Map.Entry<String, Long> entry : leaseUpdates.entrySet()) {
+            int id = getClusterNodeId(entry.getKey());
+            if (isTimedout(entry.getValue(), config)) {
+                inactives.add(id);
+            } else {
+                actives.add(id);
+            }
+        }
+        discoBuilder.activeIds(actives.toArray(new Integer[0]));
+        discoBuilder.inactiveIds(inactives.toArray(new Integer[0]));
+        return discoBuilder;
+    }
+
+    private boolean isTimedout(Long lastHeartbeat, OakTestConfig config) {
+        return System.currentTimeMillis() > lastHeartbeat + (1000 * config.getViewCheckerTimeout());
+    }
+
+    private int getClusterNodeId(String slingId) {
+        Integer id = clusterNodeIds.get(slingId);
+        if (id==null) {
+            id = ++highestId;
+            clusterNodeIds.put(slingId, id);
+        }
+        return id;
+    }
+
+    public void reset() {
+        clusterNodeIds.clear();
+    }
+
+}
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>.