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 [2/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/cluster/ClusterViewService.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewService.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewService.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewService.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;
+
+import java.util.Collection;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+
+/**
+ * The ClusterViewService is responsible and provides access to the view
+ * established in a JcR cluster.
+ */
+public interface ClusterViewService {
+
+    /** the sling id of the local instance **/
+    String getSlingId();
+
+    /** the current cluster view **/
+    ClusterView getClusterView();
+
+    /**
+     * the view id of the cluster view when isolated - ie before any view is
+     * established
+     **/
+    String getIsolatedClusterViewId();
+
+    /** checks whether the cluster view contains a particular sling id **/
+    boolean contains(String slingId);
+
+    /** checks whether the cluster contains any of the provided instances **/
+    boolean containsAny(Collection<InstanceDescription> listInstances);
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/ClusterViewServiceImpl.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,176 @@
+/*
+ * 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 java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.UUID;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.common.View;
+import org.apache.sling.discovery.impl.common.ViewHelper;
+import org.apache.sling.discovery.impl.common.resource.EstablishedClusterView;
+import org.apache.sling.discovery.impl.common.resource.IsolatedInstanceDescription;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Default implementation of the ClusterViewService interface.
+ * <p>
+ * This class is a reader only - it accesses the repository to read the
+ * currently established view
+ */
+@Service
+@Component
+public class ClusterViewServiceImpl implements ClusterViewService {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private SlingSettingsService settingsService;
+
+    @Reference
+    private ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    private Config config;
+    
+    /** the cluster view representing the isolated mode - ie only my own instance in one cluster. used at bootstrap **/
+    private ClusterView isolatedClusterView;
+
+    /** the cluster view id of the isolatedClusterView **/
+    private String isolatedClusterViewId = UUID.randomUUID().toString();
+
+    private IsolatedInstanceDescription ownInstance;
+
+    public String getIsolatedClusterViewId() {
+        return isolatedClusterViewId;
+    }
+
+    protected void activate(final ComponentContext context) {
+        ResourceResolver resourceResolver = null;
+        try {
+            resourceResolver = resourceResolverFactory
+                    .getAdministrativeResourceResolver(null);
+            Resource instanceResource = resourceResolver
+                    .getResource(config.getClusterInstancesPath() + "/"
+                            + getSlingId());
+            ownInstance = new IsolatedInstanceDescription(instanceResource,
+                    isolatedClusterViewId, getSlingId());
+            isolatedClusterView = ownInstance.getClusterView();
+        } catch (LoginException e) {
+            logger.error("Could not do a login: " + e, e);
+            throw new RuntimeException("Could not do a login", e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    public String getSlingId() {
+        return settingsService.getSlingId();
+    }
+
+    public boolean contains(final String slingId) {
+        List<InstanceDescription> localInstances = getClusterView()
+                .getInstances();
+        for (Iterator<InstanceDescription> it = localInstances.iterator(); it
+                .hasNext();) {
+            InstanceDescription aLocalInstance = it.next();
+            if (aLocalInstance.getSlingId().equals(slingId)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public boolean containsAny(Collection<InstanceDescription> listInstances) {
+        for (Iterator<InstanceDescription> it = listInstances.iterator(); it
+                .hasNext();) {
+            InstanceDescription instanceDescription = it.next();
+            if (contains(instanceDescription.getSlingId())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ClusterView getClusterView() {
+        ResourceResolver resourceResolver = null;
+        try {
+            resourceResolver = resourceResolverFactory
+                    .getAdministrativeResourceResolver(null);
+
+            View view = ViewHelper.getEstablishedView(resourceResolver, config);
+            if (view == null) {
+                logger.debug("getEstablishedView: no view established at the moment. isolated mode");
+                Resource instanceResource = resourceResolver
+                        .getResource(config.getClusterInstancesPath() + "/"
+                                + getSlingId());
+                ownInstance.readProperties(instanceResource);
+                return isolatedClusterView;
+            }
+
+            EstablishedClusterView clusterViewImpl = new EstablishedClusterView(
+                    config, view, getSlingId());
+            boolean foundLocal = false;
+            for (Iterator<InstanceDescription> it = clusterViewImpl
+                    .getInstances().iterator(); it.hasNext();) {
+                InstanceDescription instance = it.next();
+                if (instance.isLocal()) {
+                    foundLocal = true;
+                }
+            }
+            if (foundLocal) {
+                return clusterViewImpl;
+            } else {
+                logger.error("getEstablishedView: the existing established view does not incude the local instance yet! Assming isolated mode.");
+                Resource instanceResource = resourceResolver
+                        .getResource(config.getClusterInstancesPath() + "/"
+                                + getSlingId());
+                ownInstance.readProperties(instanceResource);
+                return isolatedClusterView;
+            }
+        } catch (LoginException e) {
+            logger.error(
+                    "handleEvent: could not log in administratively: " + e, e);
+            return null;
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHandler.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,381 @@
+/*
+ * 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.voting;
+
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+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.api.SlingConstants;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.common.resource.ResourceHelper;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.framework.Constants;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The osgi event handler responsible for following any votings and vote
+ * accordingly
+ */
+@Component(immediate = true)
+@Service(value = EventHandler.class)
+@Properties({
+        @Property(name = Constants.SERVICE_DESCRIPTION, value = "New Voting Event Listener."),
+        @Property(name = EventConstants.EVENT_TOPIC, value = {
+                SlingConstants.TOPIC_RESOURCE_ADDED,
+                SlingConstants.TOPIC_RESOURCE_CHANGED,
+                SlingConstants.TOPIC_RESOURCE_REMOVED }) })
+// ,
+// @Property(name = EventConstants.EVENT_FILTER, value = "(path="
+// + org.apache.sling.discovery.viewmgr.Constants.ROOT_PATH + ")") })
+public class VotingHandler implements EventHandler {
+
+    private Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    @Reference
+    private SlingSettingsService slingSettingsService;
+
+    @Reference
+    private ResourceResolverFactory resolverFactory;
+
+    @Reference
+    private Config config;
+
+    /** the sling id of the local instance **/
+    private String slingId;
+
+    protected void activate(final ComponentContext context)
+            throws RepositoryException {
+        slingId = slingSettingsService.getSlingId();
+        logger = LoggerFactory.getLogger(this.getClass().getCanonicalName()
+                + "." + slingId);
+    }
+
+    /**
+     * handle repository changes and react to ongoing votings
+     */
+    public void handleEvent(final Event event) {
+        String resourcePath = (String) event.getProperty("path");
+        String ongoingVotingsPath = config.getOngoingVotingsPath();
+
+        if (resourcePath == null) {
+            // not of my business
+            return;
+        }
+        if (!resourcePath.startsWith(ongoingVotingsPath)) {
+            // not of my business
+            return;
+        }
+
+        ResourceResolver resourceResolver = null;
+        try {
+            resourceResolver = resolverFactory
+                    .getAdministrativeResourceResolver(null);
+        } catch (LoginException e) {
+            logger.error(
+                    "handleEvent: could not log in administratively: " + e, e);
+            return;
+        }
+        try {
+            analyzeVotings(resourceResolver);
+        } catch (RepositoryException e) {
+            logger.error(
+                    "handleEvent: got a RepositoryException during votings analysis: "
+                            + e, e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    /**
+     * Analyze any ongoing voting in the repository
+     */
+    private void analyzeVotings(final ResourceResolver resourceResolver)
+            throws RepositoryException {
+        VotingView winningVote = VotingHelper.getWinningVoting(
+                resourceResolver, config);
+        if (winningVote != null) {
+            if (winningVote.isInitiatedBy(slingId)) {
+                logger.debug("analyzeVotings: my voting was winning. I'll mark it as established then! "
+                        + winningVote);
+                promote(resourceResolver, winningVote.getResource());
+            } else {
+                logger.debug("analyzeVotings: there is a winning vote. No need to vote any further. Expecting it to get promoted to established: "
+                        + winningVote);
+            }
+        }
+
+        List<VotingView> ongoingVotings = VotingHelper
+                .listOpenNonWinningVotings(resourceResolver,
+                        config);
+
+        Resource clusterNodesRes = resourceResolver
+                .getResource(config.getClusterInstancesPath());
+
+        Iterator<VotingView> it = ongoingVotings.iterator();
+        while (it.hasNext()) {
+            VotingView ongoingVotingRes = it.next();
+            if (winningVote != null && !winningVote.equals(ongoingVotingRes)
+                    && !ongoingVotingRes.hasVotedOrIsInitiator(slingId)) {
+                // there is a winning vote, it doesn't equal
+                // ongoingVotingRes, and I have not voted on
+                // ongoingVotingRes yet.
+                // so I vote no there now
+                ongoingVotingRes.vote(slingId, false);
+                it.remove();
+            } else if (!ongoingVotingRes.matchesLiveView(clusterNodesRes,
+                    config)) {
+                logger.warn("analyzeVotings: encountered a voting which does not match mine. Voting no: "
+                        + ongoingVotingRes);
+                ongoingVotingRes.vote(slingId, false);
+                it.remove();
+            } else if (ongoingVotingRes.isInitiatedBy(slingId)
+                    && ongoingVotingRes.hasNoVotes()) {
+                logger.debug("analyzeVotings: there were no votes for my voting, so I have to remove it: "
+                        + ongoingVotingRes);
+                ongoingVotingRes.remove();
+                it.remove();
+            }
+        }
+
+        if (winningVote != null) {
+            logger.debug("analyzeVotings: done with vote-handling. there was a winner: "
+                    + winningVote);
+            return;
+        }
+
+        if (ongoingVotings.size() == 0) {
+            return;
+        }
+
+        if (ongoingVotings.size() == 1) {
+            VotingView votingResource = ongoingVotings.get(0);
+            if (votingResource.isInitiatedBy(slingId)) {
+                logger.debug("analyzeVotings: only one voting found, and it is mine. I dont have to vote therefore: "
+                        + votingResource);
+                return;
+            } // else:
+            logger.debug("analyzeVotings: only one voting found for which I did not yet vote - and it is not mine. I'll vote yes then: "
+                    + votingResource);
+            votingResource.vote(slingId, true);
+        }
+
+        // otherwise there is more than one voting going on, all matching my
+        // view of the cluster
+        Collections.sort(ongoingVotings, new Comparator<VotingView>() {
+
+            public int compare(VotingView o1, VotingView o2) {
+                if (o1 == o2) {
+                    return 0;
+                }
+                if (o1 == null && o2 != null) {
+                    return 1;
+                }
+                if (o2 == null && o1 != null) {
+                    return -1;
+                }
+                // now both are non-null
+                return (o1.getViewId().compareTo(o2.getViewId()));
+            }
+        });
+
+        // having sorted, the lowest view should now win!
+        // first lets check if I have voted yes already
+        VotingView myYesVoteResource = VotingHelper.getYesVotingOf(resourceResolver,
+                config, slingId);
+        VotingView lowestVoting = ongoingVotings.get(0);
+
+        if (myYesVoteResource != null && lowestVoting.equals(myYesVoteResource)) {
+            // all fine. then I've voted for the lowest viewId - which is
+            // the whole idea
+            logger.debug("analyzeVotings: my voted for view is currently already the lowest id. which is good. I dont have to change any voting. "
+                    + myYesVoteResource);
+        } else if (myYesVoteResource == null) {
+            // I've not voted yet - so I should vote for the lowestVoting
+            logger.debug("analyzeVotings: I apparently have not yet voted. So I shall vote now for the lowest id which is: "
+                    + lowestVoting);
+            lowestVoting.vote(slingId, true);
+        } else {
+            // otherwise I've already voted, but not for the lowest. which
+            // is a shame.
+            // I shall change my mind!
+            logger.warn("analyzeVotings: I've already voted - but it so happened that there was a lower voting created after I voted... so I shall change my vote from "
+                    + myYesVoteResource + " to " + lowestVoting);
+            myYesVoteResource.vote(slingId, null);
+            lowestVoting.vote(slingId, true);
+        }
+        logger.debug("analyzeVotings: all done now. I've voted yes for "
+                + lowestVoting);
+    }
+
+    /**
+     * Promote a particular voting to be the new established view
+     */
+    private void promote(final ResourceResolver resourceResolver,
+            final Resource winningVoteResource) throws RepositoryException {
+        final Resource previousViewsResource = ResourceHelper
+                .getOrCreateResource(
+                        resourceResolver,
+                        config.getPreviousViewPath());
+        final Resource establishedViewsResource = ResourceHelper
+                .getOrCreateResource(
+                        resourceResolver,
+                        config.getEstablishedViewPath());
+        final Resource ongoingVotingsResource = ResourceHelper
+                .getOrCreateResource(
+                        resourceResolver,
+                        config.getOngoingVotingsPath());
+
+        logger.debug("promote: previousViewsResource="
+                + previousViewsResource.getPath());
+        logger.debug("promote: establishedViewsResource="
+                + establishedViewsResource.getPath());
+        logger.debug("promote: ongoingVotingsResource="
+                + ongoingVotingsResource.getPath());
+        logger.debug("promote: winningVoteResource="
+                + winningVoteResource.getPath());
+
+        final Session session = establishedViewsResource.adaptTo(Node.class)
+                .getSession();
+
+        // step 1: remove any nodes under previousViews
+        final Iterator<Resource> it1 = previousViewsResource.getChildren().iterator();
+        while (it1.hasNext()) {
+            Resource previousView = it1.next();
+            previousView.adaptTo(Node.class).remove();
+        }
+
+        // step 2: retire the existing established view.
+        // Note that there must always only be one. But if there's more, retire
+        // them all now.
+        final Iterator<Resource> it = establishedViewsResource.getChildren()
+                .iterator();
+        boolean first = true;
+        while (it.hasNext()) {
+            Resource retiredView = it.next();
+            if (first) {
+                first = !first;
+                logger.debug("promote: moving the old established view to previous views: "
+                        + retiredView.getPath());
+                session.move(
+                        retiredView.getPath(),
+                        config.getPreviousViewPath()
+                                + "/" + retiredView.getName());
+            } else {
+                logger.debug("promote: retiring an erroneously additionally established node "
+                        + retiredView.getPath());
+                retiredView.adaptTo(Node.class).remove();
+            }
+        }
+
+        // step 3: move the winning vote resource under the
+        // establishedViewsResource
+
+        // 3a: set the leaderid
+        final Iterator<Resource> it2 = winningVoteResource.getChild("members")
+                .getChildren().iterator();
+        String leaderElectionId = null;
+        String leaderid = null;
+        while (it2.hasNext()) {
+            Resource aMember = it2.next();
+            String leid = aMember.adaptTo(ValueMap.class).get(
+                    "leaderElectionId", String.class);
+            if (leaderElectionId == null
+                    || (leid != null && leid.compareTo(leaderElectionId) < 0)) {
+                leaderElectionId = leid;
+                leaderid = aMember.getName();
+            }
+        }
+        logger.debug("promote: leader is " + leaderid
+                + " - with leaderElectionId=" + leaderElectionId);
+        winningVoteResource.adaptTo(Node.class).setProperty("leaderId",
+                leaderid);
+        winningVoteResource.adaptTo(Node.class).setProperty("leaderElectionId",
+                leaderElectionId);
+        winningVoteResource.adaptTo(Node.class).setProperty("promotedAt",
+                Calendar.getInstance());
+
+        // 3b: move the result under /established
+        final String newEstablishedViewPath = establishedViewsResource.getPath()
+                + "/" + winningVoteResource.getName();
+        logger.debug("promote: promote to new established node "
+                + newEstablishedViewPath);
+        session.move(winningVoteResource.getPath(), newEstablishedViewPath);
+
+        // step 4: delete all ongoing votings...
+        final Iterable<Resource> ongoingVotingsChildren = ongoingVotingsResource
+                .getChildren();
+        if (ongoingVotingsChildren != null) {
+            Iterator<Resource> it4 = ongoingVotingsChildren.iterator();
+            while (it4.hasNext()) {
+                Resource anOngoingVoting = it4.next();
+                anOngoingVoting.adaptTo(Node.class).remove();
+            }
+        }
+
+        // step 5: make sure there are no duplicate ongoingVotings nodes
+        // created. if so, cleanup
+        final Iterator<Resource> it5 = ongoingVotingsResource.getParent()
+                .getChildren().iterator();
+        while (it5.hasNext()) {
+            Resource resource = it5.next();
+            if (!resource
+                    .getPath()
+                    .startsWith(
+                            config.getOngoingVotingsPath())) {
+                continue;
+            }
+            if (resource
+                    .getPath()
+                    .equals(config.getOngoingVotingsPath())) {
+                // then it's [0] so to speak .. which we're not cleaning up
+                continue;
+            }
+            logger.warn("promote: cleaning up a duplicate ongoingVotingPath: "
+                    + resource.getPath());
+            resource.adaptTo(Node.class).remove();
+        }
+
+        logger.debug("promote: done with promotiong. saving.");
+        session.save();
+    }
+}
\ No newline at end of file

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHelper.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHelper.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHelper.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingHelper.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,154 @@
+/*
+ * 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.voting;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.common.resource.ResourceHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Helper class for voting
+ */
+public class VotingHelper {
+
+    private final static Logger logger = LoggerFactory
+            .getLogger(VotingHelper.class);
+
+    /**
+     * List all the votings that are currently 'open' but 'not winning'.
+     * <p>
+     * 'Open' means that they have not expired yet, have zero no-votes,
+     * and match the view that this instance has of the cluster.
+     * <p>
+     * 'Not winning' means that a voting still did not receive a vote
+     * from everybody
+     * @return the list of matching votings
+     */
+    public static List<VotingView> listOpenNonWinningVotings(
+            final ResourceResolver resourceResolver, final Config config) {
+        final String ongoingVotingsPath = config.getOngoingVotingsPath();
+        final Resource ongoingVotingsResource = resourceResolver
+                .getResource(ongoingVotingsPath);
+        if (ongoingVotingsResource == null) {
+            logger.warn("listOpenNonWinningVotings: no ongoing votings parent resource found");
+            return new ArrayList<VotingView>();
+        }
+        final Iterable<Resource> children = ongoingVotingsResource.getChildren();
+        final Iterator<Resource> it = children.iterator();
+        final List<VotingView> result = new LinkedList<VotingView>();
+        if (!it.hasNext()) {
+            return result;
+        }
+        while (it.hasNext()) {
+            Resource aChild = it.next();
+            VotingView c = new VotingView(aChild);
+            if (c.matchesLiveView(config)
+                    && c.isOngoingVoting(config) && !c.hasNoVotes()
+                    && !c.isWinning()) {
+                logger.debug("listOpenNonWinningVotings: found a valid voting: "
+                        + aChild
+                        + ", properties="
+                        + ResourceHelper.getPropertiesForLogging(aChild));
+                result.add(c);
+            } else {
+                logger.debug("listOpenNonWinningVotings: found an invalid voting: "
+                        + aChild
+                        + ", properties="
+                        + ResourceHelper.getPropertiesForLogging(aChild));
+            }
+        }
+        logger.debug("listOpenNonWinningVotings: votings found: "
+                + result.size());
+        return result;
+    }
+
+    /**
+     * Return the still valid (ongoing) and winning (received a yes vote
+     * from everybody) voting
+     * @return the valid and winning voting
+     */
+    public static VotingView getWinningVoting(
+            final ResourceResolver resourceResolver, final Config config) {
+        String ongoingVotingsPath = config.getOngoingVotingsPath();
+        Resource ongoingVotingsResource = resourceResolver
+                .getResource(ongoingVotingsPath);
+        if (ongoingVotingsResource == null) {
+            logger.warn("getWinningVoting: no ongoing votings parent resource found");
+            return null;
+        }
+        Iterable<Resource> children = ongoingVotingsResource.getChildren();
+        Iterator<Resource> it = children.iterator();
+        List<VotingView> result = new LinkedList<VotingView>();
+        while (it.hasNext()) {
+            Resource aChild = it.next();
+            VotingView c = new VotingView(aChild);
+            if (c.isOngoingVoting(config) && c.isWinning()) {
+                logger.debug("getWinningVoting: a winning voting: " + c);
+                result.add(c);
+            }
+        }
+        if (result.size() == 1) {
+            return result.get(0);
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the voting for which the given slingId has vote yes or was the 
+     * initiator (which is equal to yes).
+     * @param slingId the instance for which its yes vote should be looked up
+     * @return the voting for which the given slingId has votes yes or was the
+     * initiator
+     */
+    public static VotingView getYesVotingOf(final ResourceResolver resourceResolver,
+            final Config config,
+            final String slingId) {
+        final String ongoingVotingsPath = config.getOngoingVotingsPath();
+        final Resource ongoingVotingsResource = resourceResolver
+                .getResource(ongoingVotingsPath);
+        final Iterable<Resource> children = ongoingVotingsResource.getChildren();
+        final Iterator<Resource> it = children.iterator();
+        final List<VotingView> result = new LinkedList<VotingView>();
+        while (it.hasNext()) {
+            Resource aChild = it.next();
+            VotingView c = new VotingView(aChild);
+            if (c.hasVotedOrIsInitiator(slingId)) {
+                result.add(c);
+            }
+        }
+        if (result.size() >= 1) {
+            // if result.size() is higher than 1, that means that there is more
+            // than 1 yes vote
+            // from myself - which is a bug!
+            return result.get(0);
+        } else {
+            return null;
+        }
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/cluster/voting/VotingView.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,284 @@
+/*
+ * 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.voting;
+
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.common.View;
+import org.apache.sling.discovery.impl.common.resource.ResourceHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DAO for an ongoing voting, providing a few helper methods
+ */
+public class VotingView extends View {
+
+    /**
+     * use static logger to avoid frequent initialization as is potentially the
+     * case with ClusterViewResource.
+     **/
+    private final static Logger logger = LoggerFactory
+            .getLogger(VotingView.class);
+
+    /**
+     * Create a new voting with the given list of instances, the given 
+     * voting/view id and the given slingid of the initiator.
+     * @param newViewId the new voting/view id
+     * @param initiatorId the slingid of the initiator
+     * @param liveInstances the list of live instances to add to the voting
+     * @return a DAO object representing the voting
+     */
+    public static VotingView newVoting(final ResourceResolver resourceResolver,
+            final Config config,
+            final String newViewId, String initiatorId, final Set<String> liveInstances)
+            throws RepositoryException {
+        final Resource votingResource = ResourceHelper.createResource(
+                resourceResolver, config.getOngoingVotingsPath() + "/"
+                        + newViewId);
+        final Node node = votingResource.adaptTo(Node.class);
+        node.setProperty("votingStart", Calendar.getInstance());
+        final Node membersNode = node.addNode("members");
+        final Iterator<String> it = liveInstances.iterator();
+        while (it.hasNext()) {
+            String memberId = it.next();
+            Node newMember = membersNode.addNode(memberId);
+            if (memberId.equals(initiatorId)) {
+                newMember.setProperty("initiator", true);
+            }
+            Resource instanceResource = ResourceHelper.getOrCreateResource(
+                    resourceResolver, config.getClusterInstancesPath() + "/"
+                            + memberId);
+            String leaderElectionId = instanceResource.adaptTo(ValueMap.class)
+                    .get("leaderElectionId", String.class);
+            newMember.setProperty("leaderElectionId", leaderElectionId);
+        }
+        node.getSession().save();
+        return new VotingView(votingResource);
+    }
+
+    /**
+     * Construct a voting view based on the given resource
+     * @param viewResource the resource which is the place the voting is kept
+     */
+    public VotingView(final Resource viewResource) {
+        super(viewResource);
+    }
+
+    @Override
+    public String toString() {
+        final Resource members = getResource().getChild("members");
+        String initiatorId = null;
+        final StringBuffer sb = new StringBuffer();
+        if (members != null) {
+            Iterator<Resource> it = members.getChildren().iterator();
+            while (it.hasNext()) {
+                Resource r = it.next();
+                if (sb.length() != 0) {
+                    sb.append(", ");
+                }
+                sb.append(r.getName());
+                ValueMap properties = r.adaptTo(ValueMap.class);
+                if (properties != null) {
+                    Boolean initiator = properties.get("initiator",
+                            Boolean.class);
+                    if (initiator != null && initiator) {
+                        initiatorId = r.getName();
+                    }
+                }
+            }
+        }
+        return "a VotingView[viewId=" + getViewId() + ", initiator="
+                + initiatorId + ", members=" + sb + "]";
+    }
+
+    /**
+     * Checks whether this voting is still ongoing - that is, whether
+     * a valid votingStart is set and whether that's within the heartbeat timeout configured
+     * @param config
+     * @return
+     */
+    public boolean isOngoingVoting(final Config config) {
+        final ValueMap properties = getResource().adaptTo(ValueMap.class);
+        if (properties == null) {
+            // no properties, odd. then it's not a valid voting.
+            return false;
+        }
+        final Date votingStartDate = properties.get("votingStart", Date.class);
+        if (votingStartDate == null) {
+            logger.debug("isOngoingVoting: got a voting without votingStart. Likely in creation: "
+                    + getResource());
+            return false;
+        }
+        final long votingStart = votingStartDate.getTime();
+        final long now = System.currentTimeMillis();
+        final long diff = now - votingStart;
+        return diff < 1000 * config.getHeartbeatTimeout();
+    }
+
+    /**
+     * Checks whether there are any no votes on this voting
+     * @return true if there are any no votes on this voting
+     */
+    public boolean hasNoVotes() {
+        final Iterator<Resource> it = getResource().getChild("members").getChildren()
+                .iterator();
+        while (it.hasNext()) {
+            Resource aMemberRes = it.next();
+            ValueMap properties = aMemberRes.adaptTo(ValueMap.class);
+            Boolean vote = properties.get("vote", Boolean.class);
+            if (vote != null && !vote) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Checks whether the given slingId has voted yes or is the initiator of this voting
+     * @param slingId the sling id to check for
+     * @return true if the given slingId has voted yes or is the initiator of this voting
+     */
+    public boolean hasVotedOrIsInitiator(final String slingId) {
+        final Resource memberResource = getResource().getChild("members").getChild(
+                slingId);
+        if (memberResource == null) {
+            return false;
+        }
+        final ValueMap properties = memberResource.adaptTo(ValueMap.class);
+        if (properties == null) {
+            return false;
+        }
+        final Boolean initiator = properties.get("initiator", Boolean.class);
+        if (initiator != null && initiator) {
+            return true;
+        }
+        final Boolean vote = properties.get("vote", Boolean.class);
+        return vote != null && vote;
+    }
+
+    /**
+     * Checks whether this voting was initiated by the given slingId
+     * @return whether this voting was initiated by the given slingId
+     */
+    public boolean isInitiatedBy(final String slingId) {
+        final Resource memberResource = getResource().getChild("members").getChild(
+                slingId);
+        if (memberResource == null) {
+            return false;
+        }
+        final ValueMap properties = memberResource.adaptTo(ValueMap.class);
+        if (properties == null) {
+            return false;
+        }
+        final Boolean initiator = properties.get("initiator", Boolean.class);
+        return (initiator != null && initiator);
+    }
+
+    /**
+     * add a vote from the given slingId to this voting
+     * @param slingId the slingId which is voting
+     * @param vote true for a yes-vote, false for a no-vote
+     */
+    public void vote(final String slingId, final Boolean vote) {
+        logger.debug("vote: slingId=" + slingId + ", vote=" + vote);
+        final Resource memberResource = getResource().getChild("members").getChild(
+                slingId);
+        if (memberResource == null) {
+            logger.error("vote: no memberResource found for slingId=" + slingId
+                    + ", resource=" + getResource());
+            return;
+        }
+        final Node memberNode = memberResource.adaptTo(Node.class);
+        Session session = null;
+        try {
+            session = memberNode.getSession();
+            if (vote == null) {
+                memberNode.setProperty("vote", (Value) null);
+            } else {
+                memberNode.setProperty("vote", vote);
+            }
+            session.save();
+        } catch (RepositoryException re) {
+            logger.error("RepositoryException while voting: " + re, re);
+            if (session != null) {
+                try {
+                    session.refresh(false);
+                } catch (RepositoryException e) {
+                    logger.error(
+                            "RepositoryException after trying to refresh session after another RepositoryException. "
+                                    + re, re);
+                }
+            }
+        }
+    }
+
+    /**
+     * Checks whether this voting is winning - winning is when it has
+     * votes from each of the members and all are yes votes
+     * @return true if this voting is winning
+     */
+    public boolean isWinning() {
+        final Resource members = getResource().getChild("members");
+        final Iterable<Resource> children = members.getChildren();
+        final Iterator<Resource> it = children.iterator();
+        boolean isWinning = false;
+        while (it.hasNext()) {
+            Resource aMemberRes = it.next();
+            ValueMap properties = aMemberRes.adaptTo(ValueMap.class);
+            Boolean initiator = properties.get("initiator", Boolean.class);
+            Boolean vote = properties.get("vote", Boolean.class);
+            if (initiator != null && initiator) {
+                isWinning = true;
+                continue;
+            }
+            if (vote != null && vote) {
+                isWinning = true;
+                continue;
+            }
+            return false;
+        }
+        return isWinning;
+    }
+
+    /**
+     * Checks if this voting matches the current live view
+     */
+    public boolean matchesLiveView(final Config config) {
+        Resource clusterNodesRes = getResource().getResourceResolver()
+                .getResource(config.getClusterInstancesPath());
+        if (clusterNodesRes == null) {
+            return false;
+        }
+        return matchesLiveView(clusterNodesRes, config);
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultClusterViewImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultClusterViewImpl.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultClusterViewImpl.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultClusterViewImpl.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,128 @@
+/*
+ * 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 java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+
+/**
+ * Default implementation of the ClusterView interface.
+ * <p>
+ */
+public class DefaultClusterViewImpl implements ClusterView {
+
+    /** the id of this cluster view **/
+    private final String id;
+
+    /** the list of instances as part of this cluster **/
+    private final List<InstanceDescription> instances = new LinkedList<InstanceDescription>();
+
+    public DefaultClusterViewImpl(final String id) {
+        if (id == null || id.length() == 0) {
+            throw new IllegalArgumentException("id must not be null");
+        }
+        this.id = id;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null || !(obj instanceof DefaultClusterViewImpl)) {
+            return false;
+        }
+        final DefaultClusterViewImpl other = (DefaultClusterViewImpl) obj;
+        if (!this.id.equals(other.id)) {
+            return false;
+        }
+        if (!this.getLeader().equals(other.getLeader())) {
+            return false;
+        }
+        if (this.instances.size() != other.instances.size()) {
+            return false;
+        }
+        for (Iterator<InstanceDescription> it = instances.iterator(); it
+                .hasNext();) {
+            InstanceDescription instance = it.next();
+            if (!other.instances.contains(instance)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public int hashCode() {
+        return id.hashCode();
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Add the given instance to this cluster and set the cluster on the instance (back pointer)
+     * @param instance the instance to add to this cluster
+     */
+    public void addInstanceDescription(final DefaultInstanceDescriptionImpl instance) {
+        if (instances.contains(instance)) {
+            throw new IllegalArgumentException("cannot add same instance twice");
+        }
+        if (instance.isLeader() && doGetLeader() != null) {
+            throw new IllegalArgumentException(
+                    "cannot add another leader. there already is one");
+        }
+        instances.add(instance);
+        instance.setClusterView(this);
+    }
+
+    public List<InstanceDescription> getInstances() {
+        if (instances.size() == 0) {
+            throw new IllegalStateException("no instance was ever added");
+        }
+        return Collections.unmodifiableList(instances);
+    }
+
+    public InstanceDescription getLeader() {
+        final InstanceDescription result = doGetLeader();
+        if (result != null) {
+            return result;
+        }
+        throw new IllegalStateException("no leader was added");
+    }
+
+    /**
+     * Lookup the leader of this cluster
+     * @return the leader of this cluster - should never return null
+     */
+    private InstanceDescription doGetLeader() {
+        for (Iterator<InstanceDescription> it = instances.iterator(); it
+                .hasNext();) {
+            InstanceDescription anInstance = it.next();
+            if (anInstance.isLeader()) {
+                return anInstance;
+            }
+        }
+        return null;
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultInstanceDescriptionImpl.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultInstanceDescriptionImpl.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultInstanceDescriptionImpl.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/DefaultInstanceDescriptionImpl.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,162 @@
+/*
+ * 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 java.util.Collections;
+import java.util.Map;
+
+import org.apache.sling.discovery.ClusterView;
+import org.apache.sling.discovery.InstanceDescription;
+
+/**
+ * Base implementation for the InstanceDescription interface.
+ * <p>
+ * Allows creation of the object with clusterview and/or properties null - to be
+ * set later but before usage!
+ * <p>
+ */
+public class DefaultInstanceDescriptionImpl implements InstanceDescription {
+
+    /** the cluster view of which this instance is part of **/
+    private ClusterView clusterView;
+    
+    /** whether this instance is the leader in the cluster **/
+    private boolean isLeader;
+    
+    /** whether this instance is the local/own one **/
+    private boolean isLocal;
+    
+    /** the sling id of this instance **/
+    private String slingId;
+    
+    /** the properties of this instance **/
+    private Map<String, String> properties;
+
+    public DefaultInstanceDescriptionImpl(final DefaultClusterViewImpl clusterView,
+            final boolean isLeader, final boolean isOwn, final String slingId,
+            final Map<String, String> properties) {
+        // slingId must not be null - clusterView and properties can be though
+        if (slingId == null || slingId.length() == 0) {
+            throw new IllegalArgumentException("slingId must not be null");
+        }
+        this.isLeader = isLeader;
+        this.isLocal = isOwn;
+        this.slingId = slingId;
+        this.properties = properties;
+        if (clusterView != null) {
+            clusterView.addInstanceDescription(this);
+            if (this.clusterView == null) {
+                throw new IllegalStateException(
+                        "clusterView should have been set by now");
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "an InstanceDescription[slindId=" + slingId + ", isLeader="
+                + isLeader + ", isOwn=" + isLocal + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        return slingId.hashCode();
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null || !(obj instanceof DefaultInstanceDescriptionImpl)) {
+            return false;
+        }
+        final DefaultInstanceDescriptionImpl other = (DefaultInstanceDescriptionImpl) obj;
+        if (!this.slingId.equals(other.slingId)) {
+            return false;
+        }
+        if (!this.slingId.equals(other.slingId)) {
+            return false;
+        }
+        if (!properties.equals(other.properties)) {
+            return false;
+        }
+        if (!this.getClusterView().getId()
+                .equals(other.getClusterView().getId())) {
+            return false;
+        }
+        return true;
+    }
+
+    public ClusterView getClusterView() {
+        if (clusterView == null) {
+            throw new IllegalStateException("clusterView was never set");
+        }
+        return clusterView;
+    }
+
+    /**
+     * Sets the cluster on this instance
+     * @param clusterView
+     */
+    protected void setClusterView(ClusterView clusterView) {
+        if (this.clusterView != null) {
+            throw new IllegalStateException("can only set clusterView once");
+        }
+        if (clusterView == null) {
+            throw new IllegalArgumentException("clusterView must not be null");
+        }
+        this.clusterView = clusterView;
+    }
+
+    public boolean isLeader() {
+        return isLeader;
+    }
+
+    public boolean isLocal() {
+        return isLocal;
+    }
+
+    public String getSlingId() {
+        return slingId;
+    }
+
+    public String getProperty(final String name) {
+        if (properties == null) {
+            throw new IllegalStateException("properties were never set");
+        }
+        return properties.get(name);
+    }
+
+    public Map<String, String> getProperties() {
+        if (properties == null) {
+            throw new IllegalStateException("properties were never set");
+        }
+        return Collections.unmodifiableMap(properties);
+    }
+
+    /**
+     * Sets the properties of this instance
+     * @param properties
+     */
+    protected void setProperties(final Map<String, String> properties) {
+        if (properties == null) {
+            throw new IllegalArgumentException("properties must not be null");
+        }
+        this.properties = properties;
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/View.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/View.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/View.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/View.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,141 @@
+/*
+ * 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 java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.discovery.impl.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * DAO for a view stored in the repository.
+ */
+public class View {
+
+    /**
+     * use static logger to avoid frequent initialization as is potentially the
+     * case with ClusterViewResource.
+     **/
+    private final static Logger logger = LoggerFactory.getLogger(View.class);
+
+    /** the underlying resource which represents this view **/
+    private final Resource resource;
+
+    public View(final Resource resource) {
+        this.resource = resource;
+    }
+
+    /**
+     * Returns the underlying resource of this view.
+     * @return the underlying resource of this view
+     */
+    public Resource getResource() {
+        return resource;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (obj == null) {
+            return false;
+        } else if (!(obj instanceof View)) {
+            return false;
+        }
+        View other = (View) obj;
+        return getResource().getPath().equals(other.getResource().getPath());
+    }
+
+    @Override
+    public int hashCode() {
+        return getResource().getPath().hashCode();
+    }
+
+    /**
+     * Returns the id of this view.
+     * @return the id of this view
+     */
+    public String getViewId() {
+        return getResource().getName();
+    }
+
+    /**
+     * Checks whether this view matches the 'live view' as represented in the clusterInstances resource
+     * @param clusterInstancesRes the clusterInstances resource against which to check
+     * @return
+     */
+    public boolean matchesLiveView(final Resource clusterInstancesRes, final Config config) {
+        return matches(ViewHelper.determineLiveInstances(clusterInstancesRes,
+                config));
+    }
+
+    /**
+     * Compare this view with the given set of slingIds
+     * @param view a set of slingIds against which to compare this view
+     * @return true if this view matches the given set of slingIds
+     */
+    public boolean matches(final Set<String> view) {
+        final Set<String> viewCopy = new HashSet<String>(view);
+        final Resource members = resource.getChild("members");
+        if (members == null) {
+            return false;
+        }
+        final Iterator<Resource> it = members.getChildren().iterator();
+        while (it.hasNext()) {
+            Resource aMemberRes = it.next();
+
+            if (!viewCopy.remove(aMemberRes.getName())) {
+                return false;
+            }
+        }
+        // now the ViewCopy set must be empty to represent a match
+        return (viewCopy.size() == 0);
+    }
+
+    /**
+     * Delete this view from the repository
+     */
+    public void remove() {
+        final Node myNode = getResource().adaptTo(Node.class);
+        Session session = null;
+        try {
+            session = myNode.getSession();
+            myNode.remove();
+            session.save();
+            session = null;
+        } catch (RepositoryException e) {
+            logger.error("remove: Could not remove node: " + e, e);
+        } finally {
+            if (session != null) {
+                try {
+                    session.refresh(false);
+                } catch (RepositoryException e1) {
+                    logger.error("remove: Could not refresh session: " + e1, e1);
+                }
+            }
+        }
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/ViewHelper.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/ViewHelper.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/ViewHelper.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/ViewHelper.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,133 @@
+/*
+ * 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 java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ValueMap;
+import org.apache.sling.discovery.impl.Config;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** helper for views **/
+public class ViewHelper {
+
+    private static final Logger logger = LoggerFactory
+            .getLogger(ViewHelper.class);
+
+    /**
+     * Return the list of cluster instances that are 'live', ie that have
+     * sent a heartbeat within the configured heartbeat timeout
+     * @param clusterInstancesResource
+     * @param config
+     * @return
+     */
+    public static Set<String> determineLiveInstances(
+            final Resource clusterInstancesResource, final Config config) {
+        final Set<String> myView = new HashSet<String>();
+        final Iterator<Resource> it = clusterInstancesResource.getChildren()
+                .iterator();
+        while (it.hasNext()) {
+            Resource aClusterInstance = it.next();
+            if (isHeartBeatCurrent(aClusterInstance, config)) {
+                myView.add(aClusterInstance.getName());
+            }
+        }
+        return myView;
+    }
+
+    /**
+     * Chck if the given resource has a heartbeat sent within the
+     * configured heartbeat timeout
+     * @param aClusterInstanceResource
+     * @param config
+     * @return
+     */
+    private static boolean isHeartBeatCurrent(
+            Resource aClusterInstanceResource, final Config config) {
+        final ValueMap properties = aClusterInstanceResource.adaptTo(ValueMap.class);
+        final Date lastHeartbeat = properties.get("lastHeartbeat", Date.class);
+        final long now = System.currentTimeMillis();
+        if (lastHeartbeat == null) {
+            return false;
+        }
+        final long then = lastHeartbeat.getTime();
+        final long diff = now - then;
+        return (diff < 1000 * config.getHeartbeatTimeout());
+    }
+
+    /**
+     * Return the currently established cluster view - or null if there is no
+     * cluster view established at the moment.
+     * 
+     * @param resourceResolver
+     * @return
+     */
+    public static View getEstablishedView(final ResourceResolver resourceResolver, final Config config) {
+        final Resource establishedParent = resourceResolver
+                .getResource(config.getEstablishedViewPath());
+        if (establishedParent == null) {
+            return null;
+        }
+        final Iterable<Resource> children = establishedParent.getChildren();
+        if (children == null) {
+            return null;
+        }
+        final Iterator<Resource> it = children.iterator();
+        if (!it.hasNext()) {
+            return null;
+        }
+        Resource establishedView = it.next();
+        if (!it.hasNext()) {
+            return new View(establishedView);
+        }
+        // emergency cleanup in case there is more than one established view:
+        while (true) {
+            logger.error("getEstablishedView: more than one established view encountered! Removing: "
+                    + establishedView);
+            new View(establishedView).remove();
+            if (!it.hasNext()) {
+                return null;
+            }
+            establishedView = it.next();
+        }
+    }
+
+    /**
+     * Check if the established view matches the given set of slingIds
+     */
+    public static boolean establishedViewMatches(
+            final ResourceResolver resourceResolver, final Config config, final Set<String> view)
+            throws RepositoryException {
+        final View establishedView = ViewHelper.getEstablishedView(resourceResolver, config);
+        if (establishedView == null) {
+            return false;
+        } else {
+            return (establishedView.matches(view));
+        }
+    }
+
+}

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

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

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

Added: sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java?rev=1470424&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java (added)
+++ sling/trunk/contrib/extensions/discovery/impl/src/main/java/org/apache/sling/discovery/impl/common/heartbeat/HeartbeatHandler.java Mon Apr 22 09:27:35 2013
@@ -0,0 +1,325 @@
+/*
+ * 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.heartbeat;
+
+import java.util.Calendar;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.PersistenceException;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.commons.scheduler.Scheduler;
+import org.apache.sling.discovery.impl.Config;
+import org.apache.sling.discovery.impl.DiscoveryServiceImpl;
+import org.apache.sling.discovery.impl.cluster.voting.VotingHelper;
+import org.apache.sling.discovery.impl.cluster.voting.VotingView;
+import org.apache.sling.discovery.impl.common.ViewHelper;
+import org.apache.sling.discovery.impl.common.resource.ResourceHelper;
+import org.apache.sling.discovery.impl.topology.announcement.AnnouncementRegistry;
+import org.apache.sling.discovery.impl.topology.connector.ConnectorRegistry;
+import org.apache.sling.settings.SlingSettingsService;
+import org.osgi.service.component.ComponentContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The heartbeat handler is responsible and capable of issuing both local and
+ * remote heartbeats and registers a periodic job with the scheduler for doing so.
+ * <p>
+ * Local heartbeats are stored in the repository. Remote heartbeats are POSTs to
+ * remote TopologyConnectorServlets.
+ */
+@Service(value = { HeartbeatHandler.class })
+@Component
+public class HeartbeatHandler implements Runnable {
+
+    private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+    /** the name used for the period job with the scheduler **/
+    private static final String NAME = "discovery.impl.heartbeat.runner";
+
+    @Reference
+    private SlingSettingsService slingSettingsService;
+
+    @Reference
+    private ResourceResolverFactory resourceResolverFactory;
+
+    @Reference
+    private ConnectorRegistry connectorRegistry;
+
+    @Reference
+    private AnnouncementRegistry announcementRegistry;
+
+    @Reference
+    private Scheduler scheduler;
+
+    @Reference
+    private Config config;
+
+    /** the discovery service reference is used to get properties updated before heartbeats are sent **/
+    private DiscoveryServiceImpl discoveryService;
+
+    /** the sling id of the local instance **/
+    private String slingId;
+
+    /** the id which is to be used for the next voting **/
+    private String nextVotingId = UUID.randomUUID().toString();
+
+    /** whether or not to reset the leaderElectionId at next heartbeat time **/
+    private boolean resetLeaderElectionId = false;
+
+    @Activate
+    protected void activate(ComponentContext context)
+            throws RepositoryException {
+        slingId = slingSettingsService.getSlingId();
+        // on activate the resetLeaderElectionId is set to true to ensure that
+        // the 'leaderElectionId' property is reset on next heartbeat issuance.
+        // the idea being that a node which leaves the cluster should not
+        // become leader on next join - and by resetting the leaderElectionId
+        // to the current time, this is ensured.
+        resetLeaderElectionId = true;
+    }
+
+    @Deactivate
+    protected void deactivate() {
+        scheduler.removeJob(NAME);
+    }
+    
+    /**
+     * The initialize method is called by the DiscoveryServiceImpl.activate
+     * as we require the discoveryService (and the discoveryService has
+     * a reference on us - but we cant have circular references in osgi).
+     * <p>
+     * The initialVotingId is used to avoid an unnecessary topologyChanged event
+     * when switching form isolated to established view but with only the local
+     * instance in the view.
+     */
+    public void initialize(final DiscoveryServiceImpl discoveryService,
+            final String initialVotingId) {
+        this.discoveryService = discoveryService;
+        this.nextVotingId = initialVotingId;
+        issueHeartbeat();
+
+        try {
+            scheduler.addPeriodicJob(NAME, this,
+                    null, config.getHeartbeatInterval(), false);
+        } catch (Exception e) {
+            logger.error("activate: Could not start heartbeat runner: " + e, e);
+        }
+    }
+    
+    public void run() {
+        // issue a heartbeat
+        issueHeartbeat();
+
+        // check the view
+        checkView();
+    }
+
+    /** Get or create a ResourceResolver **/
+    private ResourceResolver getResourceResolver() throws LoginException {
+        if (resourceResolverFactory == null) {
+            logger.error("getResourceResolver: resourceResolverFactory is null!");
+            return null;
+        }
+        return resourceResolverFactory.getAdministrativeResourceResolver(null);
+    }
+
+    /** Calcualte the local cluster instance path **/
+    private String getLocalClusterNodePath() {
+        return config.getClusterInstancesPath() + "/" + slingId;
+    }
+
+    /**
+     * Issue a heartbeat.
+     * <p>
+     * This action consists of first updating the local properties,
+     * then issuing a cluster-local heartbeat (within the repository)
+     * and then a remote heartbeat (to all the topology connectors
+     * which announce this part of the topology to others)
+     */
+    private void issueHeartbeat() {
+        if (discoveryService == null) {
+            logger.error("issueHeartbeat: discoveryService is null");
+        } else {
+            discoveryService.updateProperties();
+        }
+        issueClusterLocalHeartbeat();
+        issueRemoteHeartbeats();
+    }
+
+    /** Issue a remote heartbeat using the topology connectors **/
+    private void issueRemoteHeartbeats() {
+        if (connectorRegistry == null) {
+            logger.error("issueRemoteHeartbeats: connectorRegistry is null");
+            return;
+        }
+        connectorRegistry.pingOutgoingConnections();
+    }
+
+    /** Issue a cluster local heartbeat (into the repository) **/
+    private void issueClusterLocalHeartbeat() {
+        ResourceResolver resourceResolver = null;
+        final String myClusterNodePath = getLocalClusterNodePath();
+        try {
+            resourceResolver = getResourceResolver();
+            if (resourceResolver == null) {
+                logger.error("issueClusterLocalHeartbeat: no resourceresolver available!");
+                return;
+            }
+
+            final Resource resource = ResourceHelper.getOrCreateResource(
+                    resourceResolver, myClusterNodePath);
+            final Node node = resource.adaptTo(Node.class);
+            node.setProperty("lastHeartbeat", Calendar.getInstance());
+            if (resetLeaderElectionId || !node.hasProperty("leaderElectionId")) {
+                int maxLongLength = String.valueOf(Long.MAX_VALUE).length();
+                String currentTimeMillisStr = String.format("%0"
+                        + maxLongLength + "d", System.currentTimeMillis());
+
+                String prefix = "0";
+                
+                String leaderElectionRepositoryDescriptor = config.getLeaderElectionRepositoryDescriptor();
+                if (leaderElectionRepositoryDescriptor!=null && leaderElectionRepositoryDescriptor.length()!=0) {
+                    // when this property is configured, check the value of the repository descriptor
+                    // and if that value is set, include it in the leader election id
+                    
+                    final Session session = resourceResolver.adaptTo(Session.class);
+                    if ( session != null ) {
+                        String value = session.getRepository()
+                                .getDescriptor(leaderElectionRepositoryDescriptor);
+                        if (value != null && value.equalsIgnoreCase("true")) {
+                            prefix = "1";
+                        }
+                    }
+                }
+                node.setProperty("leaderElectionId", prefix + "_"
+                        + currentTimeMillisStr + "_" + slingId);
+                resetLeaderElectionId = false;
+            }
+            resourceResolver.commit();
+
+        } catch (LoginException e) {
+            logger.error("issueHeartbeat: could not log in administratively: "
+                    + e, e);
+        } catch (PersistenceException e) {
+            logger.error("issueHeartbeat: Got a PersistenceException: "
+                    + myClusterNodePath + " " + e, e);
+        } catch (RepositoryException e) {
+            logger.error("issueHeartbeat: Got a RepositoryExceptionnnn: "
+                    + myClusterNodePath + " " + e, e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    /** Check whether the established view matches the reality, ie matches the 
+     * heartbeats
+     */
+    private void checkView() {
+        // check the remotes first
+        if (announcementRegistry == null) {
+            logger.error("announcementRegistry is null");
+            return;
+        }
+        announcementRegistry.checkExpiredAnnouncements();
+
+        ResourceResolver resourceResolver = null;
+        try {
+            resourceResolver = getResourceResolver();
+            doCheckView(resourceResolver);
+        } catch (LoginException e) {
+            logger.error("checkView: could not log in administratively: " + e,
+                    e);
+        } catch (RepositoryException e) {
+            logger.error(
+                    "checkView: encountered a repository exception during view check: "
+                            + e, e);
+        } finally {
+            if (resourceResolver != null) {
+                resourceResolver.close();
+            }
+        }
+    }
+
+    /** do the established-against-heartbeat view check using the given resourceResolver. **/
+    private void doCheckView(final ResourceResolver resourceResolver)
+            throws RepositoryException {
+
+        final VotingView winningVoting = VotingHelper.getWinningVoting(
+                resourceResolver, config);
+        int numOpenNonWinningVotes = VotingHelper.listOpenNonWinningVotings(
+                resourceResolver, config).size();
+        if (winningVoting != null || (numOpenNonWinningVotes > 0)) {
+            // then there are votings pending and I shall wait for them to
+            // settle
+            logger.debug("doCheckView: "
+                    + numOpenNonWinningVotes
+                    + " ongoing votings, no one winning yet - I shall wait for them to settle.");
+            return;
+        }
+
+        final Resource clusterNodesRes = ResourceHelper.getOrCreateResource(
+                resourceResolver, config.getClusterInstancesPath());
+        final Set<String> liveInstances = ViewHelper.determineLiveInstances(
+                clusterNodesRes, config);
+
+        if (ViewHelper.establishedViewMatches(resourceResolver, config, liveInstances)) {
+            // that's the normal case. the established view matches what we're
+            // seeing.
+            // all happy and fine
+            logger.debug("doCheckView: no pending nor winning votes. view is fine. we're all happy.");
+            return;
+        }
+        logger.debug("doCheckView: no pending nor winning votes. But: view does not match established or no established yet. Initiating a new voting");
+        Iterator<String> it = liveInstances.iterator();
+        while (it.hasNext()) {
+            logger.debug("doCheckView: one of the live instances is: "
+                    + it.next());
+        }
+
+        // we seem to be the first to realize that the currently established
+        // view doesnt match
+        // the currently live instances.
+
+        // initiate a new voting
+        String votingId;
+        synchronized (this) {
+            votingId = nextVotingId;
+            nextVotingId = UUID.randomUUID().toString();
+        }
+        VotingView.newVoting(resourceResolver, config, votingId, slingId, liveInstances);
+    }
+
+}

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

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

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