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 [4/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...

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClient.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClient.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClient.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClient.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,217 @@
+/*
+ * 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.topology.connector;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.UUID;
+
+import org.apache.commons.httpclient.Credentials;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.URIException;
+import org.apache.commons.httpclient.UsernamePasswordCredentials;
+import org.apache.commons.httpclient.auth.AuthScope;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.impl.Config;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A topology connector client is used for sending (pinging) a remote topology
+ * connector servlet and exchanging announcements with it
+ */
+public class TopologyConnectorClient implements
+        TopologyConnectorClientInformation {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** the endpoint url **/
+    private final URL connectorUrl;
+
+    /** the cluster view service **/
+    private final ClusterViewService clusterViewService;
+
+    /** the config service to user **/
+    private final Config config;
+
+    /** the id of this connection **/
+    private final UUID id;
+
+    /** the announcement registry **/
+    private final AnnouncementRegistry announcementRegistry;
+
+    /** the last inherited announcement **/
+    private Announcement lastInheritedAnnouncement;
+
+    /** the information as to where this connector came from **/
+    private final OriginInfo originInfo;
+
+    /** the information about this server **/
+    private final String serverInfo;
+
+    TopologyConnectorClient(final ClusterViewService clusterViewService,
+            final AnnouncementRegistry announcementRegistry, final Config config,
+            final URL connectorUrl, final OriginInfo originInfo, final String serverInfo) {
+        if (clusterViewService == null) {
+            throw new IllegalArgumentException(
+                    "clusterViewService must not be null");
+        }
+        if (announcementRegistry == null) {
+            throw new IllegalArgumentException(
+                    "announcementRegistry must not be null");
+        }
+        if (config == null) {
+            throw new IllegalArgumentException("config must not be null");
+        }
+        if (connectorUrl == null) {
+            throw new IllegalArgumentException("connectorUrl must not be null");
+        }
+        if (originInfo == null) {
+            throw new IllegalArgumentException("originInfo must not be null");
+        }
+        this.clusterViewService = clusterViewService;
+        this.announcementRegistry = announcementRegistry;
+        this.config = config;
+        this.connectorUrl = connectorUrl;
+        this.originInfo = originInfo;
+        this.serverInfo = serverInfo;
+        this.id = UUID.randomUUID();
+    }
+
+    /** ping the server and pass the announcements between the two **/
+    void ping() {
+        logger.debug("ping: connectorUrl=" + connectorUrl);
+        HttpClient httpClient = new HttpClient();
+        PostMethod method = new PostMethod(connectorUrl.toString());
+
+        try {
+            String userInfo = connectorUrl.getUserInfo();
+            if (userInfo != null) {
+                Credentials c = new UsernamePasswordCredentials(userInfo);
+                httpClient.getState().setCredentials(
+                        new AuthScope(method.getURI().getHost(), method
+                                .getURI().getPort()), c);
+            }
+
+            Announcement topologyAnnouncement = new Announcement(
+                    clusterViewService.getSlingId());
+            topologyAnnouncement.setOriginInfo(originInfo);
+            topologyAnnouncement.setServerInfo(serverInfo);
+            topologyAnnouncement.setLocalCluster(clusterViewService
+                    .getClusterView());
+            announcementRegistry.addAllExcept(topologyAnnouncement, null);
+            final String p = topologyAnnouncement.asJSON();
+
+            logger.debug("ping: topologyAnnouncement json is: " + p);
+            method.addParameter("topologyAnnouncement", p);
+            httpClient.executeMethod(method);
+            logger.debug("ping: done. code=" + method.getStatusCode() + " - "
+                    + method.getStatusText());
+            String responseBody = method.getResponseBodyAsString();
+            logger.debug("ping: response body=" + responseBody);
+            Announcement inheritedAnnouncement = Announcement
+                    .fromJSON(responseBody);
+            inheritedAnnouncement.setInherited(true);
+            if (!announcementRegistry
+                    .registerAnnouncement(inheritedAnnouncement)) {
+                logger.info("ping: connector response is from an instance which I already see in my topology"
+                        + inheritedAnnouncement);
+                lastInheritedAnnouncement = null;
+                return;
+            }
+            lastInheritedAnnouncement = inheritedAnnouncement;
+        } catch (URIException e) {
+            logger.error("ping: Got URIException: " + e, e);
+        } catch (IOException e) {
+            logger.error("ping: got IOException: " + e, e);
+        } catch (JSONException e) {
+            logger.error("ping: got JSONException: " + e, e);
+        } catch (RuntimeException re) {
+            logger.error("ping: got RuntimeException: " + re, re);
+        }
+    }
+
+    public URL getConnectorUrl() {
+        return connectorUrl;
+    }
+
+    public boolean isConnected() {
+        if (lastInheritedAnnouncement == null) {
+            return false;
+        } else {
+            return !lastInheritedAnnouncement.hasExpired(config);
+        }
+    }
+
+    public String getRemoteSlingId() {
+        if (lastInheritedAnnouncement == null) {
+            return null;
+        } else {
+            return lastInheritedAnnouncement.getOwnerId();
+        }
+    }
+
+    public OriginInfo getOriginInfo() {
+        return originInfo;
+    }
+
+    public String getId() {
+        return id.toString();
+    }
+
+    /** Disconnect this connector **/
+    public void disconnect() {
+        logger.debug("disconnect: connectorUrl=" + connectorUrl);
+
+        if (lastInheritedAnnouncement != null) {
+            announcementRegistry
+                    .unregisterAnnouncement(lastInheritedAnnouncement
+                            .getOwnerId());
+        }
+
+        HttpClient httpClient = new HttpClient();
+        PostMethod method = new PostMethod(connectorUrl.toString());
+
+        try {
+            String userInfo = connectorUrl.getUserInfo();
+            if (userInfo != null) {
+                Credentials c = new UsernamePasswordCredentials(userInfo);
+                httpClient.getState().setCredentials(
+                        new AuthScope(method.getURI().getHost(), method
+                                .getURI().getPort()), c);
+            }
+
+            method.addParameter("topologyDisconnect",
+                    clusterViewService.getSlingId());
+            httpClient.executeMethod(method);
+            logger.debug("disconnect: done. code=" + method.getStatusCode()
+                    + " - " + method.getStatusText());
+        } catch (URIException e) {
+            logger.error("disconnect: Got URIException: " + e, e);
+        } catch (IOException e) {
+            logger.error("disconnect: got IOException: " + e, e);
+        } catch (RuntimeException re) {
+            logger.error("disconnect: got RuntimeException: " + re, re);
+        }
+    }
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClientInformation.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClientInformation.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClientInformation.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorClientInformation.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,51 @@
+/*
+ * 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.topology.connector;
+
+import java.net.URL;
+
+/**
+ * provides information about a topology connector client
+ */
+public interface TopologyConnectorClientInformation {
+
+    public static enum OriginInfo {
+        Config, // this connector was created via config
+        WebConsole, // this connector was created via the wbconsole
+        Programmatically // this connector was created programmatically
+    }
+
+    /** the endpoint url where this connector is connecting to **/
+    public URL getConnectorUrl();
+
+    /** whether or not this connector was able to successfully connect **/
+    public boolean isConnected();
+
+    /** the sling id of the remote end **/
+    public String getRemoteSlingId();
+
+    // public List<String> listFallbackConnectorUrls();
+
+    /** the unique id of this connector **/
+    public String getId();
+
+    /** the information about how this connector was created **/
+    public OriginInfo getOriginInfo();
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorServlet.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorServlet.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/topology/connector/TopologyConnectorServlet.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,175 @@
+/*
+ * 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.topology.connector;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import javax.servlet.ServletException;
+
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.sling.SlingServlet;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingHttpServletResponse;
+import org.apache.sling.api.servlets.SlingAllMethodsServlet;
+import org.apache.sling.commons.json.JSONException;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.cluster.ClusterViewService;
+import org.apache.sling.discovery.impl.common.heartbeat.HeartbeatHandler;
+import org.apache.sling.discovery.impl.topology.announcement.Announcement;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementFilter;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Servlet which receives topology announcements at
+ * /libs/sling/topology/connector (which is reachable without authorization)
+ */
+@SlingServlet(paths = { "/libs/sling/topology/connector" })
+@Property(name = "sling.auth.requirements", value = { "-/libs/sling/topology/connector" })
+public class TopologyConnectorServlet extends SlingAllMethodsServlet {
+
+    public static final String TOPOLOGY_CONNECTOR_PATH = "/libs/sling/topology/connector";
+
+    private static final long serialVersionUID = 1300640476823585873L;
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private ClusterViewService clusterViewService;
+
+    @Reference
+    private HeartbeatHandler heartbeatHandler;
+
+    @Reference
+    private Config config;
+
+    /** the set of ips/hostnames which are allowed to connect to this servlet **/
+    private final Set<String> whitelist = new HashSet<String>();
+
+    protected void activate(final ComponentContext context) {
+        whitelist.clear();
+        String whitelistStr = config.getTopologyConnectorWhitelist();
+        StringTokenizer st = new StringTokenizer(whitelistStr, ",");
+        while (st.hasMoreTokens()) {
+            String entry = st.nextToken().trim();
+            logger.info("activate: adding whitelist entry: " + entry);
+            whitelist.add(entry);
+        }
+    }
+
+    @Override
+    protected void doPost(final SlingHttpServletRequest request,
+            final SlingHttpServletResponse response) throws ServletException,
+            IOException {
+
+        if (!isWhitelisted(request)) {
+            response.sendError(404); // in theory it would be 403==forbidden, but that would reveal that 
+                                     // a resource would exist there in the first place
+            return;
+        }
+
+        final String topologyAnnouncementJSON = request
+                .getParameter("topologyAnnouncement");
+        final String topologyDisconnect = request
+                .getParameter("topologyDisconnect");
+        if (topologyDisconnect != null) {
+            announcementRegistry.unregisterAnnouncement(topologyDisconnect);
+            return;
+        }
+        logger.debug("doPost: incoming topology announcement is: "
+                + topologyAnnouncementJSON);
+        final Announcement incomingTopologyAnnouncement;
+        try {
+            incomingTopologyAnnouncement = Announcement
+                    .fromJSON(topologyAnnouncementJSON);
+            incomingTopologyAnnouncement.removeInherited(clusterViewService
+                    .getSlingId());
+
+            if (clusterViewService.contains(incomingTopologyAnnouncement
+                    .getOwnerId())) {
+                logger.info("doPost: rejecting an announcement from an instance that is part of my cluster: "
+                        + incomingTopologyAnnouncement);
+                response.sendError(500);
+                return;
+            }
+            if (clusterViewService.containsAny(incomingTopologyAnnouncement
+                    .listInstances())) {
+                logger.info("doPost: rejecting an announcement as it contains instance(s) that is/are part of my cluster: "
+                        + incomingTopologyAnnouncement);
+                response.sendError(500);
+                return;
+            }
+            if (!announcementRegistry
+                    .registerAnnouncement(incomingTopologyAnnouncement)) {
+                logger.info("doPost: rejecting an announcement from an instance that I already see in my topology: "
+                        + incomingTopologyAnnouncement);
+                response.sendError(500);
+                return;
+            }
+
+            Announcement replyAnnouncement = new Announcement(
+                    clusterViewService.getSlingId());
+            replyAnnouncement.setLocalCluster(clusterViewService
+                    .getClusterView());
+            announcementRegistry.addAllExcept(replyAnnouncement,
+                    new AnnouncementFilter() {
+
+                        public boolean accept(Announcement announcement) {
+                            if (announcement.getPrimaryKey().equals(
+                                    incomingTopologyAnnouncement
+                                            .getPrimaryKey())) {
+                                return false;
+                            }
+                            return true;
+                        }
+                    });
+            final String p = replyAnnouncement.asJSON();
+            PrintWriter pw = response.getWriter();
+            pw.print(p);
+            pw.flush();
+        } catch (JSONException e) {
+            logger.error("doPost: Got a JSONException: " + e, e);
+            response.sendError(500);
+        }
+
+    }
+
+    /** Checks if the provided request's remote server is whitelisted **/
+    private boolean isWhitelisted(final SlingHttpServletRequest request) {
+        if (whitelist.contains(request.getRemoteAddr())) {
+            return true;
+        } else if (whitelist.contains(request.getRemoteHost())) {
+            return true;
+        }
+        logger.info("isWhitelisted: rejecting " + request.getRemoteAddr()
+                + ", " + request.getRemoteHost());
+        return false;
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties Mon Apr 22 09:27:35 2013
@@ -0,0 +1,28 @@
+#
+#  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
+
+org.apache.sling.discovery.impl.Config.description = The configuration of the \
+ resource based discovery service implementation.
+
+

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties
------------------------------------------------------------------------------
    svn:keywords = Id

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/main/resources/OSGI-INF/metatype/metatype.properties
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/ClusterTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/ClusterTest.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/ClusterTest.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/ClusterTest.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,267 @@
+/*
+ * 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.cluster;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import java.util.Iterator;
+import java.util.UUID;
+
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.impl.cluster.helpers.AcceptsMultiple;
+import org.apache.sling.discovery.impl.cluster.helpers.AssertingDiscoveryAware;
+import org.apache.sling.discovery.impl.setup.Instance;
+import org.apache.sling.discovery.impl.setup.PropertyProviderImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ClusterTest {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    Instance instance1;
+    Instance instance2;
+    Instance instance3;
+
+    private String property1Value;
+
+    protected String property2Value;
+
+    private String property1Name;
+
+    private String property2Name;
+
+    @Before
+    public void setup() throws Exception {
+        logger.debug("here we are");
+        instance1 = Instance.newStandaloneInstance("firstInstance", true);
+        instance2 = Instance.newClusterInstance("secondInstance", instance1,
+                false);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (instance3 != null) {
+            instance3.stop();
+        }
+        instance2.stop();
+        instance1.stop();
+        instance1 = null;
+        instance2 = null;
+        instance3 = null;
+    }
+
+    @Test
+    public void testClusterView() throws Exception {
+        assertNotNull(instance1);
+        assertNotNull(instance2);
+        assertNull(instance3);
+        instance3 = Instance.newClusterInstance("thirdInstance", instance1,
+                false);
+        assertNotNull(instance3);
+
+        assertEquals(instance1.getSlingId(), instance1.getClusterViewService()
+                .getSlingId());
+        assertEquals(instance2.getSlingId(), instance2.getClusterViewService()
+                .getSlingId());
+        assertEquals(instance3.getSlingId(), instance3.getClusterViewService()
+                .getSlingId());
+
+        int numC1 = instance1.getClusterViewService().getClusterView()
+                .getInstances().size();
+        assertEquals(1, numC1);
+        int numC2 = instance2.getClusterViewService().getClusterView()
+                .getInstances().size();
+        assertEquals(1, numC2);
+        int numC3 = instance3.getClusterViewService().getClusterView()
+                .getInstances().size();
+        assertEquals(1, numC3);
+
+        instance1.dumpRepo();
+
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        instance3.runHeartbeatOnce();
+
+        instance1.dumpRepo();
+        Thread.sleep(2000);
+
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        instance3.runHeartbeatOnce();
+        Thread.sleep(2000);
+
+        instance1.dumpRepo();
+        String clusterId1 = instance1.getClusterViewService().getClusterView()
+                .getId();
+        logger.info("clusterId1=" + clusterId1);
+        String clusterId2 = instance2.getClusterViewService().getClusterView()
+                .getId();
+        logger.info("clusterId2=" + clusterId2);
+        String clusterId3 = instance3.getClusterViewService().getClusterView()
+                .getId();
+        logger.info("clusterId3=" + clusterId3);
+        assertEquals(clusterId1, clusterId2);
+        assertEquals(clusterId1, clusterId3);
+
+        assertEquals(3, instance1.getClusterViewService().getClusterView()
+                .getInstances().size());
+        assertEquals(3, instance2.getClusterViewService().getClusterView()
+                .getInstances().size());
+        assertEquals(3, instance3.getClusterViewService().getClusterView()
+                .getInstances().size());
+    }
+
+    @Test
+    public void testAdditionalInstance() throws Throwable {
+        assertNotNull(instance1);
+        assertNotNull(instance2);
+
+        assertEquals(instance1.getSlingId(), instance1.getClusterViewService()
+                .getSlingId());
+        assertEquals(instance2.getSlingId(), instance2.getClusterViewService()
+                .getSlingId());
+
+        int numC1 = instance1.getClusterViewService().getClusterView()
+                .getInstances().size();
+        assertEquals(1, numC1);
+        int numC2 = instance2.getClusterViewService().getClusterView()
+                .getInstances().size();
+        assertEquals(1, numC2);
+
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+
+        instance1.dumpRepo();
+        Thread.sleep(2000);
+
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        Thread.sleep(2000);
+
+        instance1.dumpRepo();
+        String clusterId1 = instance1.getClusterViewService().getClusterView()
+                .getId();
+        logger.info("clusterId1=" + clusterId1);
+        String clusterId2 = instance2.getClusterViewService().getClusterView()
+                .getId();
+        logger.info("clusterId2=" + clusterId2);
+        assertEquals(clusterId1, clusterId2);
+
+        assertEquals(2, instance1.getClusterViewService().getClusterView()
+                .getInstances().size());
+        assertEquals(2, instance2.getClusterViewService().getClusterView()
+                .getInstances().size());
+
+        AssertingDiscoveryAware assertingDiscoveryAware = new AssertingDiscoveryAware();
+        assertingDiscoveryAware.addExpected(Type.TOPOLOGY_INIT);
+        assertEquals(1, assertingDiscoveryAware.getRemainingExpectedCount());
+        instance1.bindDiscoveryAware(assertingDiscoveryAware);
+        assertEquals(0, assertingDiscoveryAware.getRemainingExpectedCount());
+
+        // startup instance 3
+        AcceptsMultiple acceptsMultiple = new AcceptsMultiple(
+                Type.TOPOLOGY_CHANGING, Type.TOPOLOGY_CHANGED);
+        assertingDiscoveryAware.addExpected(acceptsMultiple);
+        instance3 = Instance.newClusterInstance("thirdInstance", instance1,
+                false);
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        instance3.runHeartbeatOnce();
+        Thread.sleep(2000);
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        instance3.runHeartbeatOnce();
+        Thread.sleep(2000);
+        assertEquals(1, acceptsMultiple.getEventCnt(Type.TOPOLOGY_CHANGED));
+    }
+
+    @Test
+    public void testPropertyProviders() throws Throwable {
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        assertNull(instance3);
+        instance3 = Instance.newClusterInstance("thirdInstance", instance1,
+                false);
+        instance3.runHeartbeatOnce();
+        Thread.sleep(2000);
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+        instance3.runHeartbeatOnce();
+        Thread.sleep(2000);
+
+        property1Value = UUID.randomUUID().toString();
+        property1Name = UUID.randomUUID().toString();
+        PropertyProviderImpl pp1 = new PropertyProviderImpl();
+        pp1.setProperty(property1Name, property1Value);
+        instance1.bindPropertyProvider(pp1, property1Name);
+
+        property2Value = UUID.randomUUID().toString();
+        property2Name = UUID.randomUUID().toString();
+        PropertyProviderImpl pp2 = new PropertyProviderImpl();
+        pp2.setProperty(property2Name, property2Value);
+        instance2.bindPropertyProvider(pp2, property2Name);
+
+        assertPropertyValues();
+
+        property1Value = UUID.randomUUID().toString();
+        pp1.setProperty(property1Name, property1Value);
+        instance1.runHeartbeatOnce();
+        instance2.runHeartbeatOnce();
+
+        assertPropertyValues();
+        assertNull(instance1.getClusterViewService().getClusterView()
+                .getInstances().get(0)
+                .getProperty(UUID.randomUUID().toString()));
+        assertNull(instance2.getClusterViewService().getClusterView()
+                .getInstances().get(0)
+                .getProperty(UUID.randomUUID().toString()));
+    }
+
+    private void assertPropertyValues() {
+        assertPropertyValues(instance1.getSlingId(), property1Name,
+                property1Value);
+        assertPropertyValues(instance2.getSlingId(), property2Name,
+                property2Value);
+    }
+
+    private void assertPropertyValues(String slingId, String name, String value) {
+        assertEquals(value, getInstance(instance1, slingId).getProperty(name));
+        assertEquals(value, getInstance(instance2, slingId).getProperty(name));
+    }
+
+    private InstanceDescription getInstance(Instance instance, String slingId) {
+        Iterator<InstanceDescription> it = instance.getClusterViewService()
+                .getClusterView().getInstances().iterator();
+        while (it.hasNext()) {
+            InstanceDescription id = it.next();
+            if (id.getSlingId().equals(slingId)) {
+                return id;
+            }
+        }
+        throw new IllegalStateException("instance not found: instance="
+                + instance + ", slingId=" + slingId);
+    }
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/SingleInstanceTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/SingleInstanceTest.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/SingleInstanceTest.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/SingleInstanceTest.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,206 @@
+/*
+ * 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.cluster;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+import org.apache.sling.discovery.impl.cluster.helpers.AssertingDiscoveryAware;
+import org.apache.sling.discovery.impl.common.resource.EstablishedInstanceDescription;
+import org.apache.sling.discovery.impl.common.resource.IsolatedInstanceDescription;
+import org.apache.sling.discovery.impl.setup.Instance;
+import org.apache.sling.discovery.impl.setup.PropertyProviderImpl;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SingleInstanceTest {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    Instance instance;
+
+    String propertyValue;
+
+    @Before
+    public void setup() throws Exception {
+        instance = Instance.newStandaloneInstance("standaloneInstance", true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        instance.stop();
+    }
+
+    @Test
+    public void testGetters() {
+        assertNotNull(instance);
+        logger.info("sling id=" + instance.getSlingId());
+        assertNotNull(instance.getClusterViewService().getClusterView());
+
+        ClusterView cv = instance.getClusterViewService().getClusterView();
+        logger.info("cluster view: id=" + cv.getId());
+        assertNotNull(cv.getId());
+        assertNotSame(cv.getId(), "");
+
+        List<InstanceDescription> instances = cv.getInstances();
+        assertNotNull(instances);
+        assertTrue(instances.size() == 1);
+
+        InstanceDescription myInstance = instances.get(0);
+        assertNotNull(myInstance);
+        assertTrue(myInstance.getClusterView() == cv);
+        logger.info("instance id: " + myInstance.getSlingId());
+        assertEquals(instance.getSlingId(), myInstance.getSlingId());
+
+        Map<String, String> properties = myInstance.getProperties();
+        assertNotNull(properties);
+
+        assertNull(myInstance.getProperty("foo"));
+
+        assertTrue(myInstance.isLeader());
+
+        assertTrue(myInstance.isLocal());
+    }
+
+    @Test
+    public void testPropertyProviders() throws Throwable {
+        final String propertyName = UUID.randomUUID().toString();
+        propertyValue = UUID.randomUUID().toString();
+        PropertyProviderImpl pp = new PropertyProviderImpl();
+        pp.setProperty(propertyName, propertyValue);
+        instance.bindPropertyProvider(pp, propertyName);
+
+        assertEquals(propertyValue,
+                instance.getClusterViewService().getClusterView()
+                        .getInstances().get(0).getProperty(propertyName));
+
+        propertyValue = UUID.randomUUID().toString();
+        pp.setProperty(propertyName, propertyValue);
+        instance.runHeartbeatOnce();
+
+        assertEquals(propertyValue,
+                instance.getClusterViewService().getClusterView()
+                        .getInstances().get(0).getProperty(propertyName));
+        assertNull(instance.getClusterViewService().getClusterView()
+                .getInstances().get(0)
+                .getProperty(UUID.randomUUID().toString()));
+    }
+
+    @Test
+    public void testDiscoveryAwares() throws Throwable {
+        instance.runHeartbeatOnce();
+        Thread.sleep(2000);
+        instance.runHeartbeatOnce();
+        Thread.sleep(2000);
+
+        AssertingDiscoveryAware assertingDiscoveryAware = new AssertingDiscoveryAware();
+        assertingDiscoveryAware.addExpected(Type.TOPOLOGY_INIT);
+        instance.bindDiscoveryAware(assertingDiscoveryAware);
+        assertEquals(0, assertingDiscoveryAware.getRemainingExpectedCount());
+
+        final String propertyName = UUID.randomUUID().toString();
+        propertyValue = UUID.randomUUID().toString();
+        PropertyProviderImpl pp = new PropertyProviderImpl();
+        pp.setProperty(propertyName, propertyValue);
+
+        assertingDiscoveryAware.addExpected(Type.PROPERTIES_CHANGED);
+
+        assertEquals(1, assertingDiscoveryAware.getRemainingExpectedCount());
+        assertEquals(0, pp.getGetCnt());
+        instance.bindPropertyProvider(pp, propertyName);
+        assertEquals(0, assertingDiscoveryAware.getRemainingExpectedCount());
+        // we can only assume that the getProperty was called at least once - it
+        // could be called multiple times though..
+        assertTrue(pp.getGetCnt() > 0);
+
+        assertingDiscoveryAware.addExpected(Type.PROPERTIES_CHANGED);
+
+        assertEquals(1, assertingDiscoveryAware.getRemainingExpectedCount());
+        pp.setGetCnt(0);
+        propertyValue = UUID.randomUUID().toString();
+        pp.setProperty(propertyName, propertyValue);
+        assertEquals(0, pp.getGetCnt());
+        instance.runHeartbeatOnce();
+        Thread.sleep(2000);
+        assertEquals(0, assertingDiscoveryAware.getRemainingExpectedCount());
+        assertEquals(1, pp.getGetCnt());
+
+        // a heartbeat repeat should not result in another call though
+        instance.runHeartbeatOnce();
+        Thread.sleep(2000);
+        assertEquals(0, assertingDiscoveryAware.getRemainingExpectedCount());
+        assertEquals(2, pp.getGetCnt());
+
+    }
+
+    @Test
+    public void testBootstrap() throws Throwable {
+        ClusterView initialClusterView = instance.getClusterViewService()
+                .getClusterView();
+        assertNotNull(initialClusterView);
+
+        AssertingDiscoveryAware ada = new AssertingDiscoveryAware();
+        ada.addExpected(Type.TOPOLOGY_INIT);
+        instance.bindDiscoveryAware(ada);
+        assertEquals(1, ada.getEvents().size());
+        TopologyEvent initEvent = ada.getEvents().remove(0);
+        assertNotNull(initEvent);
+
+        assertEquals(initialClusterView.getId(), initEvent.getNewView()
+                .getClusterViews().iterator().next().getId());
+        assertEquals(initialClusterView.getInstances().get(0).getSlingId(),
+                initEvent.getNewView().getLocalInstance().getSlingId());
+
+        // hard assumption that the class we get is an
+        // IsolatedInstanceDescription
+        // this is because we dont have any established clusterview yet - hence
+        // still entirely isolated
+        assertEquals(IsolatedInstanceDescription.class, initialClusterView
+                .getInstances().get(0).getClass());
+        assertEquals(IsolatedInstanceDescription.class, instance
+                .getClusterViewService().getClusterView().getInstances().get(0)
+                .getClass());
+        instance.runHeartbeatOnce();
+        Thread.sleep(1000);
+        instance.runHeartbeatOnce();
+        Thread.sleep(1000);
+        assertEquals(0, ada.getEvents().size());
+
+        // after the view was established though, we expect it to be a normal
+        // ResourceInstanceDescription
+        assertEquals(EstablishedInstanceDescription.class, instance
+                .getClusterViewService().getClusterView().getInstances().get(0)
+                .getClass());
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,60 @@
+/*
+ * 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.cluster.helpers;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+
+public class AcceptsMultiple implements TopologyEventAsserter {
+
+    private final Type[] acceptedTypes;
+
+    private final Map<Type, Integer> counts = new HashMap<Type, Integer>();
+
+    public AcceptsMultiple(Type... acceptedTypes) {
+        this.acceptedTypes = acceptedTypes;
+    }
+
+    public synchronized void assertOk(TopologyEvent event) {
+        for (int i = 0; i < acceptedTypes.length; i++) {
+            Type aType = acceptedTypes[i];
+            if (aType == event.getType()) {
+                // perfect
+                Integer c = counts.remove(aType);
+                if (c == null) {
+                    counts.put(aType, new Integer(1));
+                } else {
+                    counts.put(aType, new Integer(c + 1));
+                }
+                return;
+            }
+        }
+
+        throw new IllegalStateException("Got an Event which I did not expect: "
+                + event.getType());
+    }
+
+    public synchronized int getEventCnt(Type type) {
+        return counts.get(type);
+    }
+
+}

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsMultiple.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.impl.cluster.helpers;
+
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+
+public class AcceptsParticularTopologyEvent implements TopologyEventAsserter {
+
+    private final Type particularType;
+
+    private int eventCnt = 0;
+
+    /**
+     * @param singleInstanceTest
+     */
+    public AcceptsParticularTopologyEvent(Type particularType) {
+        this.particularType = particularType;
+    }
+
+    public void assertOk(TopologyEvent event) {
+        if (event.getType() == particularType) {
+            // fine
+            eventCnt++;
+        } else {
+            throw new IllegalStateException("expected " + particularType
+                    + ", got " + event.getType());
+        }
+    }
+
+    public int getEventCnt() {
+        return eventCnt;
+    }
+}
\ No newline at end of file

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AcceptsParticularTopologyEvent.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,67 @@
+/*
+ * 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.cluster.helpers;
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.DiscoveryAware;
+import org.apache.sling.discovery.TopologyEvent;
+import org.apache.sling.discovery.TopologyEvent.Type;
+
+public class AssertingDiscoveryAware implements DiscoveryAware {
+    private final List<TopologyEventAsserter> expectedEvents = new LinkedList<TopologyEventAsserter>();
+
+    public AssertingDiscoveryAware() {
+    }
+
+    private List<TopologyEvent> events_ = new LinkedList<TopologyEvent>();
+
+    public void handleTopologyEvent(TopologyEvent event) {
+        TopologyEventAsserter asserter = null;
+        synchronized (expectedEvents) {
+            if (expectedEvents.size() == 0) {
+                throw new IllegalStateException(
+                        "no expected events anymore. But got: " + event);
+            }
+            asserter = expectedEvents.remove(0);
+        }
+        if (asserter == null) {
+            throw new IllegalStateException("this should not occur");
+        }
+        asserter.assertOk(event);
+        events_.add(event);
+    }
+
+    public List<TopologyEvent> getEvents() {
+        return events_;
+    }
+
+    public void addExpected(Type expectedType) {
+        addExpected(new AcceptsParticularTopologyEvent(expectedType));
+    }
+
+    public void addExpected(TopologyEventAsserter topologyEventAsserter) {
+        expectedEvents.add(topologyEventAsserter);
+    }
+
+    public int getRemainingExpectedCount() {
+        return expectedEvents.size();
+    }
+}
\ No newline at end of file

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/AssertingDiscoveryAware.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,25 @@
+/*
+ * 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.cluster.helpers;
+
+import org.apache.sling.discovery.TopologyEvent;
+
+public interface TopologyEventAsserter {
+    public void assertOk(TopologyEvent event);
+}
\ No newline at end of file

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/cluster/helpers/TopologyEventAsserter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/ClusterViewTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/ClusterViewTest.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/ClusterViewTest.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/ClusterViewTest.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.impl.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.util.UUID;
+
+import org.junit.Test;
+
+public class ClusterViewTest {
+
+    @Test
+    public void testConstructor() throws Exception {
+
+        try {
+            new DefaultClusterViewImpl(null);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        try {
+            new DefaultClusterViewImpl("");
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        final String slingId = UUID.randomUUID().toString();
+        DefaultClusterViewImpl cv = new DefaultClusterViewImpl(slingId);
+        assertEquals(slingId, cv.getId());
+
+        try {
+            cv.getInstances();
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            cv.getLeader();
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        try {
+            cv.getInstances();
+            fail("should complain that there were never any instances added...");
+        } catch (Exception e) {
+            // ok
+        }
+        DefaultInstanceDescriptionImpl id0 = new DefaultInstanceDescriptionImpl(
+                null, false, false, UUID.randomUUID().toString(), null);
+        try {
+            cv.getInstances();
+            fail("should complain that there were never any instances added...");
+        } catch (Exception e) {
+            // ok
+        }
+        cv.addInstanceDescription(id0);
+        assertEquals(1, cv.getInstances().size());
+        assertSame(id0, cv.getInstances().get(0));
+        try {
+            cv.addInstanceDescription(id0);
+            fail("can only set clusterview once");
+        } catch (Exception e) {
+            // ok
+        }
+
+        assertEquals(1, cv.getInstances().size());
+        DefaultInstanceDescriptionImpl id = new DefaultInstanceDescriptionImpl(
+                cv, true, false, UUID.randomUUID().toString(), null);
+        assertEquals(2, cv.getInstances().size());
+        try {
+            cv.addInstanceDescription(id);
+            fail("can only set clusterview once - already set in constructor above");
+        } catch (Exception e) {
+            // ok
+        }
+        assertEquals(2, cv.getInstances().size());
+        assertSame(id, cv.getInstances().get(1));
+        assertSame(id, cv.getLeader());
+
+        DefaultInstanceDescriptionImpl id2 = new DefaultInstanceDescriptionImpl(
+                cv, false, false, UUID.randomUUID().toString(), null);
+        try {
+            cv.addInstanceDescription(id2);
+            fail("can only set clusterview once - already set in constructor above");
+        } catch (Exception e) {
+            // ok
+        }
+        assertEquals(3, cv.getInstances().size());
+        assertSame(id, cv.getLeader());
+
+        try {
+            cv.addInstanceDescription(id);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            cv.addInstanceDescription(id2);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            DefaultInstanceDescriptionImpl id3 = new DefaultInstanceDescriptionImpl(
+                    cv, true, false, UUID.randomUUID().toString(), null);
+            cv.addInstanceDescription(id3);
+            fail("should throw an exception");
+        } catch (Exception e) {
+            // ok
+        }
+    }
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/InstanceDescriptionTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/InstanceDescriptionTest.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/InstanceDescriptionTest.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/InstanceDescriptionTest.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.discovery.impl.common;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.Test;
+
+public class InstanceDescriptionTest {
+
+    @Test
+    public void testConstructor() throws Exception {
+        final String slingId = UUID.randomUUID().toString();
+
+        final DefaultClusterViewImpl clusterView = null;
+        final boolean isLeader = false;
+        final boolean isOwn = false;
+        final String theSlingId = null;
+        final Map<String, String> properties = null;
+        try {
+            constructInstanceDescription(clusterView, isLeader, isOwn,
+                    theSlingId, properties);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            constructInstanceDescription(null, false, false, "", null);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            constructInstanceDescription(null, false, false, slingId, null)
+                    .setClusterView(null);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        try {
+            constructInstanceDescription(null, false, false, slingId, null)
+                    .setProperties(null);
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+        DefaultInstanceDescriptionImpl id = constructInstanceDescription(null,
+                false, false, slingId, null);
+        id.setClusterView(new DefaultClusterViewImpl(UUID.randomUUID()
+                .toString()));
+        try {
+            id.setClusterView(new DefaultClusterViewImpl(UUID.randomUUID()
+                    .toString()));
+            fail("should have thrown an exception");
+        } catch (Exception e) {
+            // ok
+        }
+
+        assertEquals(slingId,
+                constructInstanceDescription(null, false, false, slingId, null)
+                        .getSlingId());
+        assertEquals(true,
+                constructInstanceDescription(null, true, false, slingId, null)
+                        .isLeader());
+        assertEquals(false,
+                constructInstanceDescription(null, false, false, slingId, null)
+                        .isLeader());
+        assertEquals(false,
+                constructInstanceDescription(null, false, false, slingId, null)
+                        .isLocal());
+
+    }
+
+    @Test
+    public void testNotOwnInstance() throws Exception {
+        final String slingId = UUID.randomUUID().toString();
+        assertEquals(true,
+                constructInstanceDescription(null, false, true, slingId, null)
+                        .isLocal());
+    }
+
+    @Test
+    public void testPropertiesSetting() throws Exception {
+        String slingId = UUID.randomUUID().toString();
+        DefaultInstanceDescriptionImpl id = constructInstanceDescription(null,
+                false, false, slingId, null);
+        id.setProperties(new HashMap<String, String>());
+        // it is actually ok to set the properties multiple times...
+        id.setProperties(new HashMap<String, String>());
+        id.setProperties(new HashMap<String, String>());
+        id.setProperties(new HashMap<String, String>());
+    }
+
+    public DefaultInstanceDescriptionImpl constructInstanceDescription(
+            final DefaultClusterViewImpl clusterView, final boolean isLeader,
+            final boolean isOwn, final String theSlingId,
+            final Map<String, String> properties) throws Exception {
+        return new DefaultInstanceDescriptionImpl(clusterView, isLeader, isOwn,
+                theSlingId, properties);
+    }
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,45 @@
+/*
+ * 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.common.resource;
+
+import java.util.Map;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.discovery.impl.common.DefaultClusterViewImpl;
+import org.apache.sling.discovery.impl.common.DefaultInstanceDescriptionImpl;
+import org.apache.sling.discovery.impl.common.InstanceDescriptionTest;
+import org.apache.sling.discovery.impl.setup.MockedResource;
+import org.apache.sling.discovery.impl.setup.MockedResourceResolver;
+
+public class EstablishedInstanceDescriptionTest extends InstanceDescriptionTest {
+
+    @Override
+    public DefaultInstanceDescriptionImpl constructInstanceDescription(
+            DefaultClusterViewImpl clusterView, boolean isLeader,
+            boolean isOwn, String theSlingId, Map<String, String> properties)
+            throws Exception {
+
+        Resource res = new MockedResource(new MockedResourceResolver(),
+                "/foo/bar", "nt:unstructured");
+
+        return new EstablishedInstanceDescription(clusterView, res, theSlingId,
+                isLeader, isOwn);
+    }
+
+}

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

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

Propchange: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/common/resource/EstablishedInstanceDescriptionTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/Instance.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/Instance.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/Instance.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/test/java/org/apache/sling/discovery/impl/setup/Instance.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,288 @@
+/*
+ * 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.setup;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+import junitx.util.PrivateAccessor;
+
+import org.apache.sling.api.SlingConstants;
+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.DiscoveryAware;
+import org.apache.sling.discovery.PropertyProvider;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.DiscoveryServiceImpl;
+import org.apache.sling.discovery.impl.cluster.ClusterViewService;
+import org.apache.sling.discovery.impl.cluster.ClusterViewServiceImpl;
+import org.apache.sling.discovery.impl.cluster.voting.VotingHandler;
+import org.apache.sling.discovery.impl.common.heartbeat.HeartbeatHandler;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.impl.topology.connector.ConnectorRegistry;
+import org.osgi.framework.Constants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Instance {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    public final String slingId = UUID.randomUUID().toString();
+
+    ClusterViewServiceImpl clusterViewService;
+
+    private final ResourceResolverFactory resourceResolverFactory;
+
+    private final HeartbeatHandler heartbeatHandler;
+
+    private final OSGiMock osgiMock;
+
+    private final DiscoveryServiceImpl discoveryService;
+
+    private final AnnouncementRegistry announcementRegistry;
+
+    private final ConnectorRegistry connectorRegistry;
+
+    private final VotingHandler votingHandler;
+
+    @SuppressWarnings("unused")
+    private final String debugName;
+
+    private ResourceResolver resourceResolver;
+
+    private int serviceId = 999;
+
+    private Instance(String debugName,
+            ResourceResolverFactory resourceResolverFactory, boolean resetRepo)
+            throws Exception {
+        this.debugName = debugName;
+
+        osgiMock = new OSGiMock();
+
+        this.resourceResolverFactory = resourceResolverFactory;
+
+        Config config = new Config() {
+            @Override
+            public long getHeartbeatTimeout() {
+                return 20000;
+            }
+        };
+
+        clusterViewService = OSGiFactory.createClusterViewServiceImpl(slingId,
+                resourceResolverFactory, config);
+        announcementRegistry = OSGiFactory.createITopologyAnnouncementRegistry(
+                resourceResolverFactory, config, slingId);
+        connectorRegistry = OSGiFactory.createConnectorRegistry(
+                announcementRegistry, config);
+        heartbeatHandler = OSGiFactory.createHeartbeatHandler(
+                resourceResolverFactory, slingId, announcementRegistry,
+                connectorRegistry, config,
+                resourceResolverFactory.getAdministrativeResourceResolver(null)
+                        .adaptTo(Repository.class));
+
+        discoveryService = OSGiFactory.createDiscoverService(slingId,
+                heartbeatHandler, clusterViewService, announcementRegistry,
+                resourceResolverFactory, config);
+
+        votingHandler = OSGiFactory.createVotingHandler(slingId,
+                resourceResolverFactory, config);
+
+        osgiMock.addService(clusterViewService);
+        osgiMock.addService(heartbeatHandler);
+        osgiMock.addService(discoveryService);
+        osgiMock.addService(announcementRegistry);
+        osgiMock.addService(votingHandler);
+
+        resourceResolver = resourceResolverFactory
+                .getAdministrativeResourceResolver(null);
+        Session session = resourceResolver.adaptTo(Session.class);
+        System.out
+                .println("GOING TO REGISTER LISTENER WITH SESSION " + session);
+        ObservationManager observationManager = session.getWorkspace()
+                .getObservationManager();
+
+        observationManager.addEventListener(
+                new EventListener() {
+
+                    public void onEvent(EventIterator events) {
+                        try {
+                            while (events.hasNext()) {
+                                Event event = events.nextEvent();
+                                Properties properties = new Properties();
+                                String topic;
+                                if (event.getType() == Event.NODE_ADDED) {
+                                    topic = SlingConstants.TOPIC_RESOURCE_ADDED;
+                                } else if (event.getType() == Event.NODE_MOVED) {
+                                    topic = SlingConstants.TOPIC_RESOURCE_CHANGED;
+                                } else if (event.getType() == Event.NODE_REMOVED) {
+                                    topic = SlingConstants.TOPIC_RESOURCE_REMOVED;
+                                } else {
+                                    topic = SlingConstants.TOPIC_RESOURCE_CHANGED;
+                                }
+                                try {
+                                    properties.put("path", event.getPath());
+                                    org.osgi.service.event.Event osgiEvent = new org.osgi.service.event.Event(
+                                            topic, properties);
+                                    votingHandler.handleEvent(osgiEvent);
+                                } catch (RepositoryException e) {
+                                    logger.warn("RepositoryException: " + e, e);
+                                }
+                            }
+                        } catch (Throwable th) {
+                            try {
+                                dumpRepo();
+                            } catch (Exception e) {
+                                e.printStackTrace();
+                            }
+                            logger.error(
+                                    "Throwable occurred in onEvent: " + th, th);
+                        }
+                    }
+                }, Event.NODE_ADDED | Event.NODE_REMOVED | Event.NODE_MOVED
+                        | Event.PROPERTY_CHANGED | Event.PROPERTY_ADDED
+                        | Event.PROPERTY_REMOVED | Event.PERSIST, "/", true,
+                null,
+                null, false);
+
+        osgiMock.activateAll(resetRepo);
+    }
+
+    public static Instance newStandaloneInstance(String debugName,
+            boolean resetRepo) throws Exception {
+        ResourceResolverFactory resourceResolverFactory = MockFactory
+                .mockResourceResolverFactory();
+        return new Instance(debugName, resourceResolverFactory, resetRepo);
+    }
+
+    public static Instance newClusterInstance(String debugName, Instance other,
+            boolean resetRepo) throws Exception {
+        return new Instance(debugName, other.resourceResolverFactory, resetRepo);
+    }
+
+    public void bindPropertyProvider(PropertyProvider propertyProvider,
+            String... propertyNames) throws Throwable {
+        Map<String, Object> props = new HashMap<String, Object>();
+        props.put(Constants.SERVICE_ID, (long) serviceId++);
+        props.put(PropertyProvider.PROPERTY_PROPERTIES, propertyNames);
+
+        PrivateAccessor.invoke(discoveryService, "bindPropertyProvider",
+                new Class[] { PropertyProvider.class, Map.class },
+                new Object[] { propertyProvider, props });
+    }
+
+    public String getSlingId() {
+        return slingId;
+    }
+
+    public ClusterViewService getClusterViewService() {
+        return clusterViewService;
+    }
+
+    public void runHeartbeatOnce() {
+        heartbeatHandler.run();
+    }
+
+    public void dumpRepo() throws Exception {
+        Session session = resourceResolverFactory
+                .getAdministrativeResourceResolver(null).adaptTo(Session.class);
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: start");
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: repo = " + session.getRepository());
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: |");
+
+        dump(session.getRootNode());
+
+        // session.logout();
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: end");
+        logger.info("dumpRepo: |");
+        logger.info("dumpRepo: |");
+
+        session.logout();
+    }
+
+    private void dump(Node node) throws RepositoryException {
+        if (node.getPath().equals("/jcr:system")
+                || node.getPath().equals("/rep:policy")) {
+            // ignore that one
+            return;
+        }
+
+        PropertyIterator pi = node.getProperties();
+        StringBuffer sb = new StringBuffer();
+        while (pi.hasNext()) {
+            Property p = pi.nextProperty();
+            sb.append(" ");
+            sb.append(p.getName());
+            sb.append("=");
+            if (p.getType() == PropertyType.BOOLEAN) {
+                sb.append(p.getBoolean());
+            } else if (p.getType() == PropertyType.STRING) {
+                sb.append(p.getString());
+            } else if (p.getType() == PropertyType.DATE) {
+                sb.append(p.getDate().getTime());
+            } else {
+                sb.append("<unknown type=" + p.getType() + "/>");
+            }
+        }
+
+        logger.info("dump: node=" + node + "   - properties:" + sb);
+        NodeIterator it = node.getNodes();
+        while (it.hasNext()) {
+            Node child = it.nextNode();
+            dump(child);
+        }
+    }
+
+    public void stop() throws LoginException {
+        if (resourceResolver != null) {
+            resourceResolver.close();
+        }
+    }
+
+    public void bindDiscoveryAware(DiscoveryAware discoveryAware)
+            throws Throwable {
+        PrivateAccessor.invoke(discoveryService, "bindDiscoveryAware",
+                new Class[] { DiscoveryAware.class },
+                new Object[] { discoveryAware });
+    }
+
+}

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

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

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