You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2013/04/22 11:27:37 UTC

svn commit: r1470424 [1/5] - in /sling/trunk/contrib/extensions/discovery/impl: ./ src/ src/main/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/sling/ src/main/java/org/apache/sling/discovery/ src/main/java/org/ap...

Author: cziegeler
Date: Mon Apr 22 09:27:35 2013
New Revision: 1470424

URL: http://svn.apache.org/r1470424
Log:
SLING-2827 :  discovery.impl: a resource based implementation of the discovery.api . Committed initial contribution from Stefan Egli (md5: d8891e5401114b2a629d3ff01044a1d6)

Added:
    sling/trunk/contrib/extensions/discovery/impl/   (with props)
    sling/trunk/contrib/extensions/discovery/impl/pom.xml   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/
    sling/trunk/contrib/extensions/discovery/impl/src/main/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewService.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHelper.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultClusterViewImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultInstanceDescriptionImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/View.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/ViewHelper.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/resource/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/resource/EstablishedClusterView.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescription.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/resource/IsolatedInstanceDescription.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/resource/ResourceHelper.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/TopologyChangeHandler.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/TopologyViewImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/announcement/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/announcement/Announcement.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/announcement/AnnouncementFilter.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/announcement/AnnouncementRegistry.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/announcement/AnnouncementRegistryImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/announcement/IncomingInstanceDescription.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/ConnectorRegistry.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/ConnectorRegistryImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClient.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClientInformation.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorServlet.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/main/resources/
    sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/
    sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/
    sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/ClusterTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/SingleInstanceTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/ClusterViewTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/InstanceDescriptionTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/Instance.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/MockFactory.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/MockedResource.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/MockedResourceResolver.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/OSGiFactory.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/OSGiMock.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/PropertyProviderImpl.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/TopologyTestHelper.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/TopologyViewImplTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/announcement/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/announcement/IncomingInstanceDescriptionTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/announcement/TopologyAnnouncementRegistryTest.java   (with props)
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/connector/
    sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/topology/connector/ConnectorRegistryTest.java   (with props)

Propchange: sling/trunk/contrib/extensions/discovery/impl/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Mon Apr 22 09:27:35 2013
@@ -0,0 +1,13 @@
+target
+bin
+*.iml
+*.ipr
+*.iws
+.settings
+.project
+.classpath
+.externalToolBuilders
+maven-eclipse.xml
+
+
+

Added: sling/trunk/contrib/extensions/discovery/impl/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/pom.xml?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/pom.xml (added)
+++ sling/trunk/contrib/extensions/discovery/impl/pom.xml Mon Apr 22 09:27:35 2013
@@ -0,0 +1,172 @@
+<?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>15</version>
+        <relativePath>../../../parent/pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.sling.discovery.impl</artifactId>
+    <packaging>bundle</packaging>
+    <version>0.0.2-SNAPSHOT</version>
+
+    <name>Apache Sling Resource-Based Discovery Service</name>
+    <description>Implementation of Apache Sling Discovery based on Sling Resource providing a ClusterView through resource-clustering (eg jackrabbit clustering) and a TopologyView through HTTP POST heartbeats announcing sub-topologies to each other.</description>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                 <artifactId>maven-surefire-plugin</artifactId>
+				<configuration>
+            		<redirectTestOutputToFile>true</redirectTestOutputToFile>
+        		</configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-scr-plugin</artifactId>
+                <version>1.11.0</version>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+            </plugin>
+        </plugins>
+    </build>
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.scr.annotations</artifactId>
+            <version>1.9.0</version>
+        </dependency>
+        <dependency>
+            <groupId>biz.aQute</groupId>
+            <artifactId>bndlib</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+        </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>0.1.0-SNAPSHOT</version>
+            <scope>provided</scope>
+		</dependency>
+		<dependency>
+			<groupId>org.apache.sling</groupId>
+			<artifactId>org.apache.sling.api</artifactId>
+			<version>2.3.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>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>commons-httpclient</groupId>
+			<artifactId>commons-httpclient</artifactId>
+			<version>3.1</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>
+      <!-- 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.apache.sling</groupId>
+        	<artifactId>org.apache.sling.commons.testing</artifactId>
+        	<version>2.0.14</version>
+        	<scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

Propchange: sling/trunk/contrib/extensions/discovery/impl/pom.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/contrib/extensions/discovery/impl/pom.xml
------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: sling/trunk/contrib/extensions/discovery/impl/pom.xml
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,219 @@
+/*
+ * 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.impl;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Dictionary;
+
+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.osgi.service.component.ComponentContext;
+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 'description'
+ */
+@Component(metatype = true, label = "Sling Resource-Based Discovery Service Configuration")
+@Service(value = { Config.class })
+public class Config {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** node used to keep instance information such as last heartbeat, properties, incoming announcements **/
+    private static final String CLUSTERINSTANCES_NODE = "/clusterInstances";
+
+    /** node used to keep the currently established view **/
+    private static final String ESTABLISHED_VIEW_NODE = "/establishedView";
+
+    /** node used to keep the previously established view **/
+    private static final String PREVIOUS_VIEW_NODE = "/previousView";
+
+    /** node used to keep ongoing votings **/
+    private static final String ONGOING_VOTING_NODE = "/ongoingVotings";
+
+    public static final long DEFAULT_HEARTBEAT_TIMEOUT = 20;
+    @Property(label = "Heartbeat timeout (seconds)", description = "Configure the timeout (in seconds) after which an instance is considered dead/crashed, eg 20.")
+    public static final String HEARTBEAT_TIMEOUT_KEY = "heartbeatTimeout";
+    private long heartbeatTimeout = DEFAULT_HEARTBEAT_TIMEOUT;
+
+    public static final long DEFAULT_HEARTBEAT_INTERVAL = 15;
+    @Property(label = "Heartbeat interval (seconds)", description = "Configure the interval (in seconds) according to which the heartbeats are exchanged in the topology, eg 15.")
+    public static final String HEARTBEAT_INTERVAL_KEY = "heartbeatInterval";
+    private long heartbeatInterval = DEFAULT_HEARTBEAT_INTERVAL;
+
+    @Property(label = "Topology Connector URL", description = "URL where to join a topology, eg http://localhost:4502/libs/sling/topology/connector")
+    public static final String TOPOLOGY_CONNECTOR_URL_KEY = "topologyConnectorUrl";
+    private URL topologyConnectorUrl = null;
+
+    private static final String DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST = "localhost,127.0.0.1";
+    @Property(label = "Topology Connector Whitelist", description = "comma separated list of ips and/or hostnames which are allowed to connect to /libs/sling/topology/connector")
+    public static final String TOPOLOGY_CONNECTOR_WHITELIST_KEY = "topologyConnectorWhitelist";
+    private String topologyConnectorWhitelist = DEFAULT_TOPOLOGY_CONNECTOR_WHITELIST;
+
+    private static final String DEFAULT_DISCOVERY_RESOURCE_PATH = "/var/discovery/impl";
+    @Property(label = "Discovery Resource Path", description = "Path of resource where to keep discovery information, e.g /var/discovery/impl")
+    public static final String DISCOVERY_RESOURCE_PATH_KEY = "discoveryResourcePath";
+    private String discoveryResourcePath = DEFAULT_DISCOVERY_RESOURCE_PATH;
+
+    @Property(label = "Repository Descriptor Name", 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'")
+    public static final String LEADER_ELECTION_REPOSITORY_DESCRIPTOR_NAME_KEY = "leaderElectionRepositoryDescriptor";
+    private String leaderElectionRepositoryDescriptor = null;
+
+    protected void activate(final ComponentContext componentContext) {
+        logger.debug("activate: config activated.");
+        configure(componentContext.getProperties());
+    }
+
+    protected void configure(final Dictionary<?, ?> properties) {
+        this.heartbeatTimeout = PropertiesUtil.toLong(
+                properties.get(HEARTBEAT_TIMEOUT_KEY),
+                DEFAULT_HEARTBEAT_TIMEOUT);
+        logger.debug("configure: heartbeatTimeout='{}''", this.heartbeatTimeout);
+        this.heartbeatInterval = PropertiesUtil.toLong(
+                properties.get(HEARTBEAT_INTERVAL_KEY),
+                DEFAULT_HEARTBEAT_INTERVAL);
+        logger.debug("configure: heartbeatInterval='{}''",
+                this.heartbeatInterval);
+        String topologyConnectorUrlStr = PropertiesUtil.toString(
+                properties.get(TOPOLOGY_CONNECTOR_URL_KEY), null);
+        try {
+            this.topologyConnectorUrl = new URL(topologyConnectorUrlStr);
+            logger.debug("configure: topologyConnectorUrl='{}''",
+                    this.topologyConnectorUrl);
+        } catch (MalformedURLException e) {
+            logger.error("configure: could not set topologyConnectorUrl: " + e,
+                    e);
+        }
+        this.topologyConnectorWhitelist = PropertiesUtil.toString(
+                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);
+        
+        this.leaderElectionRepositoryDescriptor = PropertiesUtil.toString(
+                properties.get(LEADER_ELECTION_REPOSITORY_DESCRIPTOR_NAME_KEY),
+                null);
+        logger.debug("configure: leaderElectionRepositoryDescriptor='{}''",
+                this.leaderElectionRepositoryDescriptor);
+    }
+
+    /**
+     * Returns the timeout (in seconds) after which an instance or voting is considered invalid/timed out
+     * @return the timeout (in seconds) after which an instance or voting is considered invalid/timed out
+     */
+    public long getHeartbeatTimeout() {
+        return heartbeatTimeout;
+    }
+
+    /**
+     * Returns the interval (in seconds) in which heartbeats are sent
+     * @return the interval (in seconds) in which heartbeats are sent
+     */
+    public long getHeartbeatInterval() {
+        return heartbeatInterval;
+    }
+
+    /**
+     * Returns the URL to which to open a topology connector - or null if no topology connector
+     * is configured (default is null)
+     * @return the URL to which to open a topology connector - or null if no topology connector
+     * is configured
+     */
+    public URL getTopologyConnectorURL() {
+        return topologyConnectorUrl;
+    }
+
+    /**
+     * 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;
+    }
+    
+    /**
+     * Returns the resource path where cluster instance informations are stored.
+     * @return the resource path where cluster instance informations are stored
+     */
+    public String getClusterInstancesPath() {
+        return discoveryResourcePath + CLUSTERINSTANCES_NODE;
+    }
+    
+    /**
+     * Returns the resource path where the established view is stored.
+     * @return the resource path where the established view is stored
+     */
+    public String getEstablishedViewPath() {
+        return discoveryResourcePath + ESTABLISHED_VIEW_NODE;
+    }
+    
+    /**
+     * Returns the resource path where ongoing votings are stored.
+     * @return the resource path where ongoing votings are stored
+     */
+    public String getOngoingVotingsPath() {
+        return discoveryResourcePath + ONGOING_VOTING_NODE;
+    }
+    
+    /**
+     * Returns the resource path where the previous view is stored. 
+     * @return the resource path where the previous view is stored
+     */
+    public String getPreviousViewPath() {
+        return discoveryResourcePath + PREVIOUS_VIEW_NODE;
+    }
+    
+    /**
+     * Returns the repository descriptor key which is to be included in the
+     * cluster leader election - or null. 
+     * <p>
+     * When set, the value (treated as a boolean) of the repository descriptor 
+     * is prepended to the leader election id.
+     * @return the repository descriptor key which is to be included in the
+     * cluster leader election - or null
+     */
+    public String getLeaderElectionRepositoryDescriptor() {
+        return leaderElectionRepositoryDescriptor;
+    }
+}

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/Config.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,521 @@
+/*
+ * 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.impl;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.RepositoryException;
+
+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.Resource;
+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.DiscoveryAware;
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.PropertyProvider;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.impl.cluster.ClusterViewService;
+import org.apache.sling.discovery.impl.common.heartbeat.HeartbeatHandler;
+import org.apache.sling.discovery.impl.common.resource.ResourceHelper;
+import org.apache.sling.discovery.impl.topology.TopologyViewImpl;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.impl.topology.connector.ConnectorRegistry;
+import org.apache.sling.discovery.impl.topology.connector.TopologyConnectorClientInformation.OriginInfo;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+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).
+ */
+@Service(value = { DiscoveryService.class, DiscoveryServiceImpl.class })
+@Component(immediate = true)
+public class DiscoveryServiceImpl implements DiscoveryService {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private SlingSettingsService settingsService;
+
+    @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, referenceInterface = DiscoveryAware.class)
+    private DiscoveryAware[] discoveryAwares = new DiscoveryAware[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 HeartbeatHandler heartbeatHandler;
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private ConnectorRegistry connectorRegistry;
+
+    @Reference
+    private ClusterViewService clusterViewService;
+
+    @Reference
+    private Config config;
+
+    /** the slingId of the local instance **/
+    private String slingId;
+
+    /** the old view previously valid and sent to the discoveryawares **/
+    private TopologyViewImpl oldView = null;
+
+    /**
+     * Activate this service
+     */
+    protected void activate(final ComponentContext context) {
+        logger.debug("DiscoveryServiceImpl activating...");
+
+        if (settingsService == null) {
+            throw new IllegalStateException("settingsService not found");
+        }
+        if (heartbeatHandler == null) {
+            throw new IllegalStateException("heartbeatHandler not found");
+        }
+
+        slingId = settingsService.getSlingId();
+
+        oldView = (TopologyViewImpl) getTopology();
+
+        // 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).
+        heartbeatHandler.initialize(this,
+                clusterViewService.getIsolatedClusterViewId());
+
+        final DiscoveryAware[] registeredServices;
+        synchronized (lock) {
+            activated = true;
+            registeredServices = this.discoveryAwares;
+            doUpdateProperties();
+
+        }
+        TopologyViewImpl newView = (TopologyViewImpl) getTopology();
+        TopologyEvent event = new TopologyEvent(Type.TOPOLOGY_INIT, null,
+                newView);
+        for (final DiscoveryAware da : registeredServices) {
+            da.handleTopologyEvent(event);
+        }
+        oldView = newView;
+
+        URL topologyConnectorURL = config.getTopologyConnectorURL();
+        if (topologyConnectorURL != null) {
+            connectorRegistry.registerOutgoingConnection(clusterViewService,
+                    topologyConnectorURL, OriginInfo.Config);
+        }
+
+        logger.debug("DiscoveryServiceImpl activated.");
+    }
+
+    /**
+     * Deactivate this service
+     */
+    protected void deactivate(final ComponentContext componentContext) {
+        logger.debug("DiscoveryServiceImpl deactivated.");
+        synchronized (lock) {
+            activated = false;
+        }
+    }
+
+    /**
+     * bind a discovery aware
+     */
+    protected void bindDiscoveryAware(final DiscoveryAware clusterAware) {
+
+        logger.debug("bindDiscoveryAware: Binding DiscoveryAware {}",
+                clusterAware);
+
+        boolean activated = false;
+        synchronized (lock) {
+            List<DiscoveryAware> currentList = new ArrayList<DiscoveryAware>(
+                    Arrays.asList(discoveryAwares));
+            currentList.add(clusterAware);
+            this.discoveryAwares = currentList
+                    .toArray(new DiscoveryAware[currentList.size()]);
+            activated = this.activated;
+        }
+
+        if (activated) {
+            clusterAware.handleTopologyEvent(new TopologyEvent(
+                    Type.TOPOLOGY_INIT, null, getTopology()));
+        }
+    }
+
+    /**
+     * Unbind a discovery aware
+     */
+    protected void unbindDiscoveryAware(final DiscoveryAware clusterAware) {
+
+        logger.debug("unbindDiscoveryAware: Releasing DiscoveryAware {}",
+                clusterAware);
+
+        synchronized (lock) {
+            List<DiscoveryAware> currentList = new ArrayList<DiscoveryAware>(
+                    Arrays.asList(discoveryAwares));
+            currentList.remove(clusterAware);
+            this.discoveryAwares = currentList
+                    .toArray(new DiscoveryAware[currentList.size()]);
+        }
+    }
+
+    /**
+     * Bind a new property provider.
+     */
+    private void bindPropertyProvider(final PropertyProvider propertyProvider,
+            final Map<String, Object> props) {
+        logger.debug("bindPropertyProvider: Binding PropertyProvider {}",
+                propertyProvider);
+
+        final DiscoveryAware[] awares;
+        synchronized (lock) {
+            final ProviderInfo info = new ProviderInfo(propertyProvider, props);
+            this.providerInfos.add(info);
+            Collections.sort(this.providerInfos);
+            this.doUpdateProperties();
+            if (activated) {
+                awares = this.discoveryAwares;
+            } else {
+                awares = null;
+            }
+        }
+        if (awares != null) {
+            handlePotentialTopologyChange();
+        }
+    }
+
+    /**
+     * Update a property provider.
+     */
+    @SuppressWarnings("unused")
+    private void updatedPropertyProvider(
+            final PropertyProvider propertyProvider,
+            final Map<String, Object> props) {
+        logger.debug("bindPropertyProvider: Updating PropertyProvider {}",
+                propertyProvider);
+
+        this.unbindPropertyProvider(propertyProvider, props, false);
+        this.bindPropertyProvider(propertyProvider, props);
+    }
+
+    /**
+     * Unbind a property provider
+     */
+    @SuppressWarnings("unused")
+    private void unbindPropertyProvider(
+            final PropertyProvider propertyProvider,
+            final Map<String, Object> props) {
+        this.unbindPropertyProvider(propertyProvider, props, true);
+    }
+
+    /**
+     * Unbind a property provider
+     */
+    private void unbindPropertyProvider(
+            final PropertyProvider propertyProvider,
+            final Map<String, Object> props, final boolean inform) {
+        logger.debug("unbindPropertyProvider: Releasing PropertyProvider {}",
+                propertyProvider);
+
+        final DiscoveryAware[] awares;
+        synchronized (lock) {
+            final ProviderInfo info = new ProviderInfo(propertyProvider, props);
+            this.providerInfos.remove(info);
+            this.doUpdateProperties();
+            if (activated) {
+                awares = this.discoveryAwares;
+            } else {
+                awares = null;
+            }
+        }
+        if (inform && awares != null) {
+            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");
+
+            Node node = myInstance.adaptTo(Node.class);
+
+            PropertyIterator pit = node.getProperties();
+            while (pit.hasNext()) {
+                Property p = pit.nextProperty();
+                if (newProps.containsKey(p.getName())) {
+                    // perfect
+                    continue;
+                } else if (p.getName().equals("jcr:primaryType")) {
+                    // ignore
+                    continue;
+                } else {
+                    // remove
+                    p.remove();
+                }
+            }
+
+            for (Iterator<Entry<String, String>> it = newProps.entrySet()
+                    .iterator(); it.hasNext();) {
+                Entry<String, String> entry = it.next();
+                logger.debug("doUpdateProperties: " + entry.getKey() + "="
+                        + entry.getValue());
+                node.setProperty(entry.getKey(), entry.getValue());
+            }
+
+            node.getSession().save();
+        } 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 (RepositoryException e) {
+            logger.error("handleEvent: got a RepositoryException: " + e, e);
+            throw new RuntimeException(
+                    "Exception while talking to repository (" + e + ")", e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+
+        logger.debug("doUpdateProperties: updating properties done.");
+    }
+
+    /**
+     * @see DiscoveryService#getTopology()
+     */
+    public TopologyView getTopology() {
+        TopologyViewImpl topology = new TopologyViewImpl();
+
+        if (clusterViewService == null) {
+            throw new IllegalStateException(
+                    "DiscoveryService not yet initialized with IClusterViewService");
+        }
+
+        ClusterView localClusterView = clusterViewService.getClusterView();
+
+        List<InstanceDescription> localInstances = localClusterView
+                .getInstances();
+        topology.addInstances(localInstances);
+
+        Collection<InstanceDescription> attachedInstances = announcementRegistry
+                .listInstances();
+        topology.addInstances(attachedInstances);
+
+        // TODO: isCurrent() might be wrong!!!
+
+        return topology;
+    }
+
+    /**
+     * Update the properties and sent a topology event if applicable
+     */
+    public void updateProperties() {
+        synchronized (lock) {
+            doUpdateProperties();
+            handlePotentialTopologyChange();
+        }
+    }
+
+    /**
+     * Internal handle method which checks if anything in the topology has
+     * changed and informs the DiscoveryAwares if such a change occurred.
+     * <p>
+     * All changes should go through this method. This method keeps track of
+     * oldView/newView as well.
+     */
+    private void handlePotentialTopologyChange() {
+        synchronized (lock) {
+            if (oldView == null) {
+                throw new IllegalStateException("oldView must not be null");
+            }
+            TopologyViewImpl newView = (TopologyViewImpl) getTopology();
+            TopologyViewImpl oldView = this.oldView;
+
+            Type difference = newView.compareTopology(oldView);
+            if (difference == null) {
+                // then dont send any event then
+                return;
+            }
+
+            oldView.markOld();
+            for (final DiscoveryAware da : discoveryAwares) {
+                da.handleTopologyEvent(new TopologyEvent(difference, oldView,
+                        newView));
+            }
+            this.oldView = newView;
+        }
+    }
+
+    /**
+     * 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 Map<String, Object> serviceProps;
+        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.serviceProps = serviceProps;
+            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();
+            final Object namesObj = serviceProps
+                    .get(PropertyProvider.PROPERTY_PROPERTIES);
+            if (namesObj instanceof String) {
+                final String val = provider.getProperty((String) namesObj);
+                if (val != null) {
+                    this.properties.put((String) namesObj, val);
+                }
+            } else if (namesObj instanceof String[]) {
+                for (final String name : (String[]) namesObj) {
+                    final String val = provider.getProperty(name);
+                    if (val != null) {
+                        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 handleTopologyChanged() {
+        logger.debug("handleTopologyChanged: informing the discoveryawares...");
+        handlePotentialTopologyChange();
+    }
+
+}

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/DiscoveryServiceImpl.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,77 @@
+/*
+ * 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.impl;
+
+import java.util.Calendar;
+
+import org.apache.felix.scr.annotations.Component;
+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.sling.discovery.PropertyProvider;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.service.component.ComponentContext;
+
+/**
+ * Example, default PropertyProvider which provides a few interesting static, as
+ * well as a volatile, properties.
+ * <p>
+ * The volatile property is called 'infrastructure.lastPropertiesUpdate' and
+ * allows to conclude when the properties were last read and propagated through
+ * the topology
+ */
+@Component
+@Service(value = { PropertyProvider.class })
+@Properties({ @Property(name = PropertyProvider.PROPERTY_PROPERTIES, value = {
+        "infrastructure.slingId", "infrastructure.slingHome",
+        "infrastructure.lastPropertiesUpdate", "infrastructure.port" }) })
+public class InfrastructurePropertyProvider implements PropertyProvider {
+
+    @Reference
+    private SlingSettingsService settingsService;
+
+    /** the http port on which this instance is listening - provided as infrastructure.port property **/
+    private String port = "";
+
+    /**
+     * Activate this property provider - this reads and stores the http port on which this instance is listening
+     */
+    protected void activate(final ComponentContext cc) {
+        port = cc.getBundleContext().getProperty("org.osgi.service.http.port");
+
+    }
+
+    /**
+     * Serve the properties
+     */
+    public String getProperty(final String name) {
+        if (name.equals("infrastructure.slingId")) {
+            return settingsService.getSlingId();
+        } else if (name.equals("infrastructure.slingHome")) {
+            return settingsService.getSlingHomePath();
+        } else if (name.equals("infrastructure.lastPropertiesUpdate")) {
+            return Calendar.getInstance().getTime().toString();
+        } else if (name.equals("infrastructure.port")) {
+            return port;
+        }
+        return null;
+    }
+
+}

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/InfrastructurePropertyProvider.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,722 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Dictionary;
+import java.util.HashSet;
+import java.util.Hashtable;
+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.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.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.DiscoveryAware;
+import org.apache.sling.discovery.DiscoveryService;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.InstanceFilter;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyView;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.impl.cluster.ClusterViewService;
+import org.apache.sling.discovery.impl.topology.announcement.Announcement;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry.ListScope;
+import org.apache.sling.discovery.impl.topology.connector.ConnectorRegistry;
+import org.apache.sling.discovery.impl.topology.connector.TopologyConnectorClientInformation;
+import org.apache.sling.discovery.impl.topology.connector.TopologyConnectorClientInformation.OriginInfo;
+import org.apache.sling.discovery.impl.topology.connector.TopologyConnectorServlet;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Simple webconsole which gives an overview of the topology visible by the
+ * discovery service
+ */
+@Service(value = { DiscoveryAware.class })
+@Component(immediate = true)
+@SuppressWarnings("serial")
+public class TopologyWebConsolePlugin extends AbstractWebConsolePlugin implements DiscoveryAware {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** the url part where topology joins are posted to **/
+    private static final String JOIN = "/join";
+
+    /** the url part where topology disconnects are posted to **/
+    private static final String DISCONNECT = "/disconnect/";
+
+    /** 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");
+
+    /** the service registry where this webconsole is registered. used for deactivate/unregister **/
+    private ServiceRegistration serviceRegistration;
+
+    @Reference
+    private ClusterViewService clusterViewService;
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private ConnectorRegistry connectorRegistry;
+
+    @Reference
+    private DiscoveryService discoveryService;
+
+    @Override
+    public String getLabel() {
+        return "topology";
+    }
+
+    @Override
+    public String getTitle() {
+        return "Topology Management";
+    }
+    
+    @Activate
+    @Override
+    public void activate(final BundleContext bundleContext) {
+        super.activate(bundleContext);
+        logger.info("activate: activating...");
+        Dictionary<String, Object> props = new Hashtable<String, Object>();
+        props.put(
+                org.osgi.framework.Constants.SERVICE_DESCRIPTION,
+                "MEEEE Web Console Plugin to display Background servlets and ExecutionEngine status");
+        props.put(org.osgi.framework.Constants.SERVICE_VENDOR,
+                "The Apache Software Foundation");
+        props.put(org.osgi.framework.Constants.SERVICE_PID, getClass()
+                .getName());
+        props.put(WebConsoleConstants.PLUGIN_LABEL, getLabel());
+
+        serviceRegistration = bundleContext.registerService(
+                WebConsoleConstants.SERVICE_NAME, this, props);
+    }
+
+    @Deactivate
+    @Override
+    public void deactivate() {
+        super.deactivate();
+        if (serviceRegistration != null) {
+            serviceRegistration.unregister();
+            serviceRegistration = null;
+        }
+    }
+
+    @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("")) {
+            renderOverview(pw, discoveryService.getTopology());
+        } else {
+            StringTokenizer st = new StringTokenizer(pathInfo, "/");
+            final String nodeId = st.nextToken();
+            renderProperties(pw, nodeId);
+        }
+    }
+
+    /**
+     * Render the properties page of a particular instance
+     */
+    private void renderProperties(final PrintWriter pw, final String nodeId) {
+        logger.debug("renderProperties: nodeId=" + nodeId);
+        Set<InstanceDescription> instances = discoveryService.getTopology()
+                .findInstances(new InstanceFilter() {
+
+                    public boolean accept(InstanceDescription instance) {
+                        String slingId = instance.getSlingId();
+                        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\">");
+            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>");
+        }
+    }
+
+    /**
+     * 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.println("<a href=\"/system/console/configMgr/org.apache.sling.discovery.impl.Config\">Configure Discovery Service</a>");
+        pw.println("<br/>");
+        pw.println("<br/>");
+        pw.println("<p class=\"statline ui-state-highlight\">Topology</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\">");
+        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\">Own (this) 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</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, odd);
+
+        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, odd);
+        }
+
+        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("<form action=\"/system/console/topology"
+                + JOIN
+                + "\" method=\"POST\" style=\"display: block; width: auto; min-height: 50px;\" scrolltop=\"0\" scrollleft=\"0\">");
+
+        String url = "http://localhost:4502"
+                + TopologyConnectorServlet.TOPOLOGY_CONNECTOR_PATH;
+
+        pw.println("<br/>");
+        pw.println("<br/>");
+        pw.println("<p class=\"input\">"
+                + "Join topology at: "
+                + "<label>"
+                + "<input class=\"ui-state-default ui-corner-all inputText\" type=\"text\" value=\""
+                + url + "\" name=\"connectorUrl\" style=\"width: 300px\"/>"
+                + "</label>");
+        pw.println("<input class=\"ui-state-default ui-corner-all\" type=\"submit\" value=\"Join\">");
+        pw.println("</form>");
+        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 cluster, final boolean odd) {
+        Collection<Announcement> announcements = null;
+        for (Iterator<InstanceDescription> it = cluster.getInstances()
+                .iterator(); it.hasNext();) {
+            InstanceDescription instanceDescription = it.next();
+            String oddEven = odd ? "odd" : "even";
+            pw.println("<tr class=\"" + oddEven + " ui-state-default\">");
+            boolean isLocal = instanceDescription.isLocal();
+            String slingId = instanceDescription.getSlingId();
+            slingId = "<a href=\"/system/console/topology/" + slingId + "\">"
+                    + slingId + "</a>";
+            if (isLocal) {
+                slingId = "<b>" + slingId + "</b>";
+            }
+            pw.println("<td>" + slingId + "</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 (clusterViewService.contains(instanceDescription.getSlingId())) {
+                pw.println("<td>local</td>");
+                pw.println("<td>n/a</td>");
+            } else {
+                pw.println("<td>remote</td>");
+                if (announcements == null) {
+                    announcements = announcementRegistry
+                            .listAnnouncements(ListScope.AllInSameCluster);
+                }
+                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;
+                        }
+                    }
+                }
+                if (parentAnnouncement != null) {
+                    pw.println("<td>" + parentAnnouncement.getOwnerId()
+                            + "</td>");
+                } else {
+                    pw.println("<td><b>error</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\">");
+        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\">Origin info</th>");
+        pw.println("<th class=\"header ui-widget-header\">Persistent</th>");
+        // pw.println("<th class=\"header ui-widget-header\">Fallback connector urls</th>");
+        pw.println("<th class=\"header ui-widget-header\">Disconnect</th>");
+        pw.println("</tr>");
+        pw.println("</thead>");
+        pw.println("<tbody>");
+
+        Collection<TopologyConnectorClientInformation> outgoingConnections = connectorRegistry
+                .listOutgoingConnections();
+        for (Iterator<TopologyConnectorClientInformation> it = outgoingConnections
+                .iterator(); it.hasNext();) {
+            TopologyConnectorClientInformation topologyConnectorClient = it
+                    .next();
+            String oddEven = odd ? "odd" : "even";
+            odd = !odd;
+            pw.println("<tr class=\"" + oddEven + " ui-state-default\">");
+            pw.println("<td>"
+                    + topologyConnectorClient.getConnectorUrl().toString()
+                    + "</td>");
+            String remoteSlingId = topologyConnectorClient.getRemoteSlingId();
+            if (topologyConnectorClient.isConnected() && remoteSlingId != null) {
+                pw.println("<td>" + remoteSlingId + "</td>");
+            } else {
+                pw.println("<td><b>not connected</b></td>");
+            }
+            if (topologyConnectorClient.getOriginInfo() == OriginInfo.Config) {
+                pw.println("<td>");
+                pw.println("<a href=\"/system/console/configMgr/org.apache.sling.discovery.impl.Config\">Config</a>");
+                pw.println("</td>");
+                pw.println("<td>persistent</td>");
+            } else {
+                pw.println("<td>" + topologyConnectorClient.getOriginInfo()
+                        + "</td>");
+                if (topologyConnectorClient.getOriginInfo() == OriginInfo.WebConsole) {
+                    pw.println("<td>not persistent</td>");
+                } else {
+                    pw.println("<td>n/a</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("<td>");
+            final String id = topologyConnectorClient.getId();
+            pw.println("<form id=\"" + id
+                    + "\" method=\"post\" action=\"/system/console/topology"
+                    + DISCONNECT + id + "\">");
+            pw.println("<input type=\"hidden\" name=\"name\" value=\"value\" />");
+            pw.println(" <a onclick=\"document.getElementById('" + id
+                    + "').submit();\">click here to disconnect</a>");
+            pw.println("</form>");
+            pw.println("</td>");
+            pw.println("</tr>");
+        }
+
+        pw.println("</tbody>");
+        pw.println("</table>");
+    }
+
+    /**
+     * 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\">");
+        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\">Origin info</th>");
+        pw.println("<th class=\"header ui-widget-header\">Persistent</th>");
+        pw.println("</tr>");
+        pw.println("</thead>");
+        pw.println("<tbody>");
+
+        Collection<Announcement> outgoingConnections = announcementRegistry
+                .listAnnouncements(ListScope.OnlyInherited);
+        for (Iterator<Announcement> it = outgoingConnections.iterator(); it
+                .hasNext();) {
+            Announcement incomingAnnouncement = it.next();
+            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>");
+
+            }
+
+            if (incomingAnnouncement.getOriginInfo() == OriginInfo.Config) {
+                pw.println("<td>Config (remote)</td>");
+                pw.println("<td>persistent</td>");
+            } else {
+                pw.println("<td>" + incomingAnnouncement.getOriginInfo()
+                        + " (remote)</td>");
+                if (incomingAnnouncement.getOriginInfo() == OriginInfo.WebConsole) {
+                    pw.println("<td>not persistent</td>");
+                } else {
+                    pw.println("<td>n/a</td>");
+                }
+            }
+            pw.println("</tr>");
+        }
+
+        pw.println("</tbody>");
+        pw.println("</table>");
+        pw.println("<br/>");
+        pw.println("<br/>");
+    }
+
+    @Override
+    protected void doPost(final HttpServletRequest req, final HttpServletResponse resp)
+            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());
+        String connectorUrl = req.getParameter("connectorUrl");
+        if (JOIN.equals(pathInfo)) {
+            logger.debug("doPost: " + JOIN + " called with connectorUrl="
+                    + connectorUrl);
+            try {
+                connectorRegistry.registerOutgoingConnection(
+                        clusterViewService, new URL(connectorUrl),
+                        OriginInfo.WebConsole);
+                resp.sendRedirect(root);
+            } catch (Exception e) {
+                logger.error("doPost: 500: " + e);
+                resp.sendRedirect(root);
+            }
+        } else if (pathInfo != null && pathInfo.startsWith(DISCONNECT)) {
+            logger.debug("doPost: " + DISCONNECT + " called with full info: "
+                    + pathInfo);
+            String id = pathInfo.substring(DISCONNECT.length());
+            logger.debug("doPost: id=" + id);
+            connectorRegistry.unregisterOutgoingConnection(id);
+            resp.sendRedirect(root);
+        } else {
+            logger.error("doPost: invalid POST to " + pathInfo);
+            resp.sendError(404);
+        }
+    }
+
+    /**
+     * 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) {
+
+            Set<InstanceDescription> newInstances = event.getNewView()
+                    .getInstances();
+            StringBuffer sb = new StringBuffer();
+            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();
+                StringBuffer diff = diff(oldProps, newProps);
+                if (diff.length() > 0) {
+                    if (sb.length() != 0) {
+                        sb.append(", ");
+                    }
+                    sb.append("on instance "
+                            + newInstanceDescription.getSlingId() + ": " + diff);
+                }
+            }
+
+            addEventLog(event.getType(), sb.toString());
+        } else if (event.getType() == Type.TOPOLOGY_INIT) {
+            StringBuffer details = new StringBuffer();
+            for (Iterator<InstanceDescription> it = event.getNewView()
+                    .getInstances().iterator(); it.hasNext();) {
+                InstanceDescription newInstance = it.next();
+                if (details.length() != 0) {
+                    details.append(", ");
+                }
+                details.append(newInstance.getSlingId());
+            }
+            addEventLog(event.getType(),
+                    "view: " + shortViewInfo(event.getNewView()) + ". "
+                            + details);
+        } else {
+            if (event.getOldView() == null) {
+                addEventLog(event.getType(),
+                        "new view: " + shortViewInfo(event.getNewView()));
+            } else {
+                StringBuffer details = new StringBuffer();
+                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");
+                    }
+                }
+
+                addEventLog(
+                        event.getType(),
+                        "old view: " + shortViewInfo(event.getOldView())
+                                + ", new view: "
+                                + shortViewInfo(event.getNewView()) + ". "
+                                + details);
+            }
+        }
+    }
+
+    /**
+     * 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 dateStr = sdf.format(Calendar.getInstance().getTime());
+        final String logEntry = dateStr + ": " + 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);
+            }
+        }
+
+    }
+
+    /**
+     * 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 StringBuffer 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());
+
+        StringBuffer sb = new StringBuffer();
+
+        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;
+    }
+}

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision rev url

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/TopologyWebConsolePlugin.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain