/* * 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.log.SequentialEntry; import c5db.interfaces.log.SequentialEntryCodec; import c5db.log.generated.OLogContentType; import c5db.log.generated.OLogEntryHeader; import c5db.replication.generated.LogEntry; import c5db.replication.generated.QuorumConfigurationMessage; import com.google.common.collect.Iterables; import com.google.common.math.IntMath; import io.protostuff.Schema; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import static c5db.log.EntryEncodingUtil.CrcError; import static c5db.log.EntryEncodingUtil.appendCrcToBufferList; import static c5db.log.EntryEncodingUtil.decodeAndCheckCrc; import static c5db.log.EntryEncodingUtil.encodeWithLengthAndCrc; import static c5db.log.EntryEncodingUtil.getAndCheckContent; import static c5db.log.EntryEncodingUtil.skip; import static c5db.log.EntryEncodingUtil.sumRemaining; /** * A SequentialEntry that can convert itself to and from Protostuff LogEntry objects. * In addition, it can serialize itself directly using the supplied Codec, allowing it * to be written to an {@link EncodedSequentialLog}. Its serialized form consists of a * header together with some content ({@link OLogContent}). * <p> * An OLogEntry is the kind of entry written to, and retrieved from, {@link OLog}. */ public final class OLogEntry extends SequentialEntry { private final long electionTerm; private final OLogContent content; public OLogEntry(long seqNum, long electionTerm, OLogContent content) { super(seqNum); assert content != null; this.electionTerm = electionTerm; this.content = content; } public long getElectionTerm() { return electionTerm; } public OLogContent getContent() { return content; } public OLogContentType getContentType() { return content.getType(); } public LogEntry toProtostuff() { switch (content.getType()) { case DATA: return new LogEntry(electionTerm, seqNum, ((OLogRawDataContent) content).getRawData(), null); case QUORUM_CONFIGURATION: return new LogEntry(electionTerm, seqNum, new ArrayList<>(), (QuorumConfigurationMessage) ((OLogProtostuffContent) content).getMessage()); } throw new RuntimeException("OLogEntry#toProtostuff"); } public static OLogEntry fromProtostuff(LogEntry entry) { final OLogContent content; if (entry.getQuorumConfiguration() != null) { content = new OLogProtostuffContent<>(entry.getQuorumConfiguration()); } else { content = new OLogRawDataContent(entry.getDataList()); } return new OLogEntry(entry.getIndex(), entry.getTerm(), content); } @Override public boolean equals(Object o) { if (o == null || (o.getClass() != this.getClass())) { return false; } OLogEntry that = (OLogEntry) o; return this.electionTerm == that.getElectionTerm() && this.seqNum == that.getSeqNum() && this.content.equals(that.getContent()); } @Override public int hashCode() { return ((int) seqNum) * 31 + ((int) electionTerm) * 31 + content.hashCode(); } @Override public String toString() { return "OLogEntry{" + "seqNum=" + seqNum + ", electionTerm=" + electionTerm + ", content=" + content; } public static class Codec implements SequentialEntryCodec<OLogEntry> { private static final Schema<OLogEntryHeader> SCHEMA = OLogEntryHeader.getSchema(); // TODO capability of having multiple 4-byte CRCs for large content private static final int CRC_BYTES = 4; @Override public ByteBuffer[] encode(OLogEntry entry) { try { final List<ByteBuffer> contentBufs = entry.getContent().serialize(); final int contentLength = sumRemaining(contentBufs); final OLogEntryHeader header = createHeader(entry, contentLength); final List<ByteBuffer> entryBufs = encodeWithLengthAndCrc(SCHEMA, header); entryBufs.addAll(appendCrcToBufferList(contentBufs)); return Iterables.toArray(entryBufs, ByteBuffer.class); } catch (IOException e) { throw new RuntimeException(e); } } @Override public OLogEntry decode(InputStream inputStream) throws IOException, CrcError { final OLogEntryHeader header = decodeAndCheckCrc(inputStream, SCHEMA); final ByteBuffer contentBuf = getAndCheckContent(inputStream, header.getContentLength()); return new OLogEntry( header.getSeqNum(), header.getTerm(), OLogContent.deserialize(contentBuf, header.getType())); } @Override public long skipEntryAndReturnSeqNum(InputStream inputStream) throws IOException { final OLogEntryHeader header = decodeAndCheckCrc(inputStream, SCHEMA); skipContent(inputStream, header.getContentLength()); return header.getSeqNum(); } public OLogEntryHeader skipEntryAndReturnHeader(InputStream inputStream) throws IOException { final OLogEntryHeader header = decodeAndCheckCrc(inputStream, SCHEMA); skipContent(inputStream, header.getContentLength()); return header; } private void skipContent(InputStream inputStream, int contentLength) throws IOException { skip(inputStream, IntMath.checkedAdd(contentLength, CRC_BYTES)); } private static OLogEntryHeader createHeader(OLogEntry entry, int contentLength) { return new OLogEntryHeader( entry.getSeqNum(), entry.getElectionTerm(), contentLength, entry.getContent().getType()); } } }