c5db.log.InRamLog.java Source code

Java tutorial

Introduction

Here is the source code for c5db.log.InRamLog.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.log;

import c5db.interfaces.replication.QuorumConfiguration;
import c5db.interfaces.replication.ReplicatorLog;
import c5db.replication.generated.LogEntry;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * ReplicatorLog hosted in memory, e.g. for unit testing ReplicatorInstance in-memory. This
 * implementation just provides the basics needed to make the consensus algorithm work.
 */
public class InRamLog implements ReplicatorLog {

    private final List<LogEntry> log = new ArrayList<>();

    public InRamLog() {
    }

    @Override
    public synchronized ListenableFuture<Boolean> logEntries(List<LogEntry> entries) {
        validateEntries(entries);
        log.addAll(entries);

        return blockingFuture(true);
    }

    @Override
    public synchronized ListenableFuture<List<LogEntry>> getLogEntries(long start, long end) {
        assert start > 0;
        assert end >= start;

        List<LogEntry> foundEntries = log.stream()
                .filter((entry) -> start <= entry.getIndex() && entry.getIndex() < end)
                .collect(Collectors.toList());

        if (foundEntries.size() != (end - start)) {
            throw new RuntimeException(
                    "requested [" + start + ", " + end + "); received" + foundEntries.toString());
        }

        return blockingFuture(foundEntries);
    }

    @Override
    public synchronized long getLogTerm(long index) {
        assert index > 0;

        Optional<LogEntry> requestedEntry = optionallyGetEntryInternal(index);

        if (requestedEntry.isPresent()) {
            return requestedEntry.get().getTerm();
        } else {
            return 0;
        }
    }

    @Override
    public synchronized long getLastTerm() {
        if (log.isEmpty()) {
            return 0;
        }
        return log.get(log.size() - 1).getTerm();
    }

    @Override
    public synchronized long getLastIndex() {
        if (log.isEmpty()) {
            return 0;
        }
        return log.get(log.size() - 1).getIndex();
    }

    @Override
    public synchronized ListenableFuture<Boolean> truncateLog(long entryIndex) {
        LogEntry firstRemovedEntry = getEntryInternal(entryIndex);
        int listIndex = log.lastIndexOf(firstRemovedEntry);
        log.subList(listIndex, log.size()).clear();

        return blockingFuture(true);
    }

    @Override
    public synchronized QuorumConfiguration getLastConfiguration() {
        for (LogEntry entry : Lists.reverse(log)) {
            if (entry.getQuorumConfiguration() != null) {
                return QuorumConfiguration.fromProtostuff(entry.getQuorumConfiguration());
            }
        }

        return QuorumConfiguration.EMPTY;
    }

    @Override
    public synchronized long getLastConfigurationIndex() {
        for (LogEntry entry : Lists.reverse(log)) {
            if (entry.getQuorumConfiguration() != null) {
                return entry.getIndex();
            }
        }

        return 0;
    }

    private void validateEntries(List<LogEntry> entries) {
        // Ensure ascending with no gaps
        long lastIndex = getLastIndex();
        for (LogEntry e : entries) {
            if (lastIndex == 0 || e.getIndex() == lastIndex + 1) {
                lastIndex = e.getIndex();
            } else {
                throw new RuntimeException("entries not in sequence: " + entries.toString());
            }
        }
    }

    private Optional<LogEntry> optionallyGetEntryInternal(long index) {
        return log.stream().filter((entry) -> entry.getIndex() == index).findFirst();
    }

    private LogEntry getEntryInternal(long index) {
        Optional<LogEntry> requestedEntry = optionallyGetEntryInternal(index);

        if (!requestedEntry.isPresent()) {
            throw new RuntimeException("entry index " + index + " not found");
        }

        return requestedEntry.get();
    }

    private static <V> ListenableFuture<V> blockingFuture(V result) {
        SettableFuture<V> future = SettableFuture.create();
        new Thread(() -> future.set(result)).start();
        return future;
    }
}