package org.robotninjas.barge.log;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import journal.io.api.Journal;
import journal.io.api.Location;
import org.robotninjas.barge.ClusterConfig;
import org.robotninjas.barge.Replica;
import org.robotninjas.barge.api.*;
import java.io.File;
import java.io.IOException;
import static com.google.common.base.Functions.toStringFunction;
import static com.google.common.base.Throwables.propagate;
import static journal.io.api.Journal.ReadType;
import static journal.io.api.Journal.WriteType;
class RaftJournal {
private final Journal journal;
private final ClusterConfig config;
public RaftJournal(Journal journal, ClusterConfig config) {
this.journal = journal;
this.config = config;
}
private JournalEntry read(Location loc) {
try {
byte[] data = journal.read(loc, ReadType.SYNC);
return JournalEntry.parseFrom(data);
} catch (IOException e) {
throw propagate(e);
}
}
private void delete(Location loc) {
try {
journal.delete(loc);
} catch (IOException e) {
propagate(e);
}
}
private Location write(JournalEntry entry) {
try {
return journal.write(entry.toByteArray(), WriteType.SYNC);
} catch (IOException e) {
throw propagate(e);
}
}
public Entry get(Mark mark) {
JournalEntry entry = read(mark.getLocation());
return entry.getAppend().getEntry();
}
public void truncateHead(Mark mark) {
try {
for (Location loc : journal.undo(mark.getLocation())) {
delete(loc);
}
} catch (IOException e) {
throw propagate(e);
}
}
public void truncateTail(Mark mark) {
try {
Location location = mark.getLocation();
Iterable<Location> locations = FluentIterable
.from(journal.redo(location))
.skip(1);
for (Location loc : locations) {
delete(loc);
}
} catch (IOException e) {
throw propagate(e);
}
}
public Mark appendEntry(Entry entry, long index) {
JournalEntry je =
JournalEntry.newBuilder()
.setAppend(Append.newBuilder()
.setEntry(entry)
.setIndex(index)
.build())
.build();
Location location = write(je);
return new Mark(location);
}
public Mark appendTerm(long term) {
Location location =
write(JournalEntry.newBuilder()
.setTerm(Term.newBuilder()
.setTerm(term)
.build())
.build());
return new Mark(location);
}
public Mark appendCommit(long commit) {
Location location =
write(JournalEntry.newBuilder()
.setCommit(Commit.newBuilder()
.setIndex(commit)
.build())
.build());
return new Mark(location);
}
public Mark appendVote(Optional<Replica> vote) {
Location location =
write(JournalEntry.newBuilder()
.setVote(Vote.newBuilder()
.setVotedFor(vote.transform(toStringFunction()).or(""))
.build())
.build());
return new Mark(location);
}
public Mark appendSnapshot(File file, long index, long term) {
Location location =
write(JournalEntry.newBuilder()
.setSnapshot(Snapshot.newBuilder()
.setLastIncludedIndex(index)
.setLastIncludedTerm(term)
.setSnapshotFile(file.getName())
.build())
.build());
return new Mark(location);
}
public void replay(Visitor visitor) {
try {
for (Location location : journal.redo()) {
JournalEntry entry = read(location);
Mark mark = new Mark(location);
if (entry.hasAppend()) {
Append append = entry.getAppend();
visitor.append(mark, append.getEntry(), append.getIndex());
} else if (entry.hasCommit()) {
Commit commit = entry.getCommit();
visitor.commit(mark, commit.getIndex());
} else if (entry.hasTerm()) {
Term term = entry.getTerm();
visitor.term(mark, term.getTerm());
} else if (entry.hasVote()) {
Vote vote = entry.getVote();
String votedfor = vote.getVotedFor();
Replica replica = votedfor == null
? null : config.getReplica(votedfor);
visitor.vote(mark, Optional.fromNullable(replica));
}
}
} catch (IOException e) {
propagate(e);
}
}
static interface Visitor {
void term(Mark mark, long term);
void vote(Mark mark, Optional<Replica> vote);
void commit(Mark mark, long commit);
void append(Mark mark, Entry entry, long index);
}
static class Mark {
private final Location location;
private Mark(Location location) {
this.location = location;
}
private Location getLocation() {
return location;
}
}
}