c5db.interfaces.replication.QuorumConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for c5db.interfaces.replication.QuorumConfiguration.java

Source

/*
 * Copyright 2014 WANdisco
 *
 *  WANdisco 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 c5db.interfaces.replication;

import c5db.replication.generated.QuorumConfigurationMessage;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.SortedMultiset;
import com.google.common.collect.TreeMultiset;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * Immutable value type representing a configuration of which peers are members of a quorum.
 * It satisfies the following invariants: if isTransitional is true, then allPeers is
 * the union of prevPeers and nextPeers. If isTransitional is false, then prevPeers
 * and nextPeers are empty.
 */
public final class QuorumConfiguration {

    public final boolean isTransitional;

    private final Set<Long> allPeers;
    private final Set<Long> prevPeers;
    private final Set<Long> nextPeers;

    public static final QuorumConfiguration EMPTY = new QuorumConfiguration(new HashSet<>());

    public static QuorumConfiguration of(Collection<Long> peerCollection) {
        return new QuorumConfiguration(peerCollection);
    }

    public static QuorumConfiguration fromProtostuff(
            c5db.replication.generated.QuorumConfigurationMessage message) {
        if (message.getTransitional()) {
            return new QuorumConfiguration(message.getPrevPeersList(), message.getNextPeersList());
        } else {
            return new QuorumConfiguration(message.getAllPeersList());
        }
    }

    public QuorumConfiguration getTransitionalConfiguration(Collection<Long> newPeerCollection) {
        if (isTransitional) {
            return new QuorumConfiguration(prevPeers, newPeerCollection);
        } else {
            return new QuorumConfiguration(allPeers, newPeerCollection);
        }
    }

    public QuorumConfiguration getCompletedConfiguration() {
        assert isTransitional;

        return new QuorumConfiguration(nextPeers);
    }

    public QuorumConfigurationMessage toProtostuff() {
        return new QuorumConfigurationMessage(isTransitional, Lists.newArrayList(allPeers),
                Lists.newArrayList(prevPeers), Lists.newArrayList(nextPeers));
    }

    public Set<Long> allPeers() {
        return allPeers;
    }

    public Set<Long> prevPeers() {
        return prevPeers;
    }

    public Set<Long> nextPeers() {
        return nextPeers;
    }

    public boolean isEmpty() {
        return allPeers.size() == 0 && prevPeers.size() == 0 && nextPeers.size() == 0;
    }

    /**
     * Determine if the peers in sourceSet include a majority of the peers in this configuration.
     */
    public boolean setContainsMajority(Set<Long> sourceSet) {
        if (isTransitional) {
            return setComprisesMajorityOfAnotherSet(sourceSet, prevPeers)
                    && setComprisesMajorityOfAnotherSet(sourceSet, nextPeers);
        } else {
            return setComprisesMajorityOfAnotherSet(sourceSet, allPeers);
        }
    }

    /**
     * Given a map which tells the last acknowledged entry index for different peers, find the maximum
     * index value which is less than or equal to a majority of this configuration's peers' indexes.
     */
    public long calculateCommittedIndex(Map<Long, Long> peersLastAckedIndex) {
        if (isTransitional) {
            return Math.min(getGreatestIndexCommittedByMajority(prevPeers, peersLastAckedIndex),
                    getGreatestIndexCommittedByMajority(nextPeers, peersLastAckedIndex));
        } else {
            return getGreatestIndexCommittedByMajority(allPeers, peersLastAckedIndex);
        }
    }

    private QuorumConfiguration(Collection<Long> peers) {
        this.isTransitional = false;
        allPeers = ImmutableSet.copyOf(peers);
        prevPeers = nextPeers = ImmutableSet.of();
    }

    private QuorumConfiguration(Collection<Long> prevPeers, Collection<Long> nextPeers) {
        this.isTransitional = true;
        this.prevPeers = ImmutableSet.copyOf(prevPeers);
        this.nextPeers = ImmutableSet.copyOf(nextPeers);
        this.allPeers = Sets.union(this.prevPeers, this.nextPeers).immutableCopy();
    }

    private static long getGreatestIndexCommittedByMajority(Set<Long> peers, Map<Long, Long> peersLastAckedIndex) {
        SortedMultiset<Long> committedIndexes = TreeMultiset.create();
        committedIndexes.addAll(peers.stream().map(peerId -> peersLastAckedIndex.getOrDefault(peerId, 0L))
                .collect(Collectors.toList()));
        return Iterables.get(committedIndexes.descendingMultiset(), calculateNumericalMajority(peers.size()) - 1);
    }

    private static <T> boolean setComprisesMajorityOfAnotherSet(Set<T> sourceSet, Set<T> destinationSet) {
        return Sets.intersection(sourceSet, destinationSet)
                .size() >= calculateNumericalMajority(destinationSet.size());
    }

    private static int calculateNumericalMajority(int setSize) {
        return (setSize / 2) + 1;
    }

    @Override
    public String toString() {
        return "QuorumConfiguration{" + "isTransitional=" + isTransitional + ", allPeers=" + allPeers
                + ", prevPeers=" + prevPeers + ", nextPeers=" + nextPeers + '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        QuorumConfiguration that = (QuorumConfiguration) o;

        return isTransitional == that.isTransitional && allPeers.equals(that.allPeers)
                && nextPeers.equals(that.nextPeers) && prevPeers.equals(that.prevPeers);
    }

    @Override
    public int hashCode() {
        int result = (isTransitional ? 1 : 0);
        result = 31 * result + allPeers.hashCode();
        result = 31 * result + prevPeers.hashCode();
        result = 31 * result + nextPeers.hashCode();
        return result;
    }
}