/* * 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.interfaces.replication.QuorumConfiguration; import c5db.log.generated.OLogContentType; import c5db.log.generated.OLogEntryHeader; import c5db.replication.generated.QuorumConfigurationMessage; import com.google.common.math.IntMath; import io.protostuff.Schema; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import static c5db.log.EntryEncodingUtil.CrcError; import static c5db.log.EntryEncodingUtil.decodeAndCheckCrc; import static c5db.log.EntryEncodingUtil.getAndCheckContent; import static c5db.log.EntryEncodingUtil.skip; /** * A description of an OLogEntry for use by reader utilities. * <p> * OLogEntry log entries encoded to some medium, such as a log file on disk, using * {@link c5db.log.OLogEntry.Codec} may be decoded (deserialized) using a different Codec. * This class and its Codec are one such example. Their goal is to look at a serialized * OLogEntry and describe its features. The intended use of this is for readers such * as CatOLog that may wish to analyze and display features of the serialized entry * not normally visible when decoded as an OLogEntry object, such as CRC check validity. * <p> * As such, the provided Codec below can only decode, not encode. */ public final class OLogEntryDescription extends SequentialEntry { private final long electionTerm; private final int contentLength; private final OLogContentType type; private final boolean headerCrcIsValid; private final boolean contentCrcIsValid; // Only used for type == QUORUM_CONFIGURATION private final QuorumConfiguration quorumConfiguration; public OLogEntryDescription(long seqNum, long electionTerm, int contentLength, OLogContentType type, boolean headerCrcIsValid, boolean contentCrcIsValid, QuorumConfiguration quorumConfiguration) { super(seqNum); this.electionTerm = electionTerm; this.contentLength = contentLength; this.type = type; this.headerCrcIsValid = headerCrcIsValid; this.contentCrcIsValid = contentCrcIsValid; this.quorumConfiguration = quorumConfiguration; } public long getElectionTerm() { return electionTerm; } public int getContentLength() { return contentLength; } public OLogContentType getType() { return type; } public boolean isHeaderCrcValid() { return headerCrcIsValid; } public boolean isContentCrcValid() { return contentCrcIsValid; } public QuorumConfiguration getQuorumConfiguration() { return quorumConfiguration; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } OLogEntryDescription that = (OLogEntryDescription) o; return seqNum == that.getSeqNum() && electionTerm == that.electionTerm && contentLength == that.contentLength && type == that.type && contentCrcIsValid == that.contentCrcIsValid && headerCrcIsValid == that.headerCrcIsValid && (quorumConfiguration == that.quorumConfiguration || quorumConfiguration.equals(that.quorumConfiguration)); } @Override public int hashCode() { int result = (int) (seqNum ^ (seqNum >>> 32)); result = 31 * result + (int) (electionTerm ^ (electionTerm >>> 32)); result = 31 * result + contentLength; result = 31 * result + type.hashCode(); result = 31 * result + (headerCrcIsValid ? 1 : 0); result = 31 * result + (contentCrcIsValid ? 1 : 0); result = 31 * result + quorumConfiguration.hashCode(); return result; } @Override public String toString() { return "OLogEntryDescription{" + "seqNum='" + seqNum + '\'' + ", electionTerm=" + electionTerm + ", contentLength=" + contentLength + ", type=" + type + ", headerCrcIsValid=" + headerCrcIsValid + ", contentCrcIsValid=" + contentCrcIsValid + ", quorumConfiguration=" + quorumConfiguration; } public static class Codec implements SequentialEntryCodec<OLogEntryDescription> { private static final Schema<OLogEntryHeader> SCHEMA = OLogEntryHeader.getSchema(); private static final int CRC_BYTES = 4; @Override public ByteBuffer[] encode(OLogEntryDescription entry) { // TODO since this is essentially a read-only codec, it doesn't need to encode. Does this mean the Codec // TODO interface/concept is the wrong abstraction, or should be broken down further? throw new UnsupportedOperationException("encode"); } @Override public OLogEntryDescription decode(InputStream inputStream) throws IOException, CrcError { // TODO (possibly) handle even a corrupted header final OLogEntryHeader header = decodeAndCheckCrc(inputStream, SCHEMA); boolean contentCrcIsValid = true; ByteBuffer contentBuffer = null; QuorumConfiguration quorumConfiguration = null; try { contentBuffer = getAndCheckContent(inputStream, header.getContentLength()); } catch (CrcError e) { contentCrcIsValid = false; } if (contentBuffer != null && header.getType() == OLogContentType.QUORUM_CONFIGURATION) { quorumConfiguration = deserializeQuorumConfiguration(header, contentBuffer); } return new OLogEntryDescription( header.getSeqNum(), header.getTerm(), header.getContentLength(), header.getType(), true, contentCrcIsValid, quorumConfiguration); } @Override public long skipEntryAndReturnSeqNum(InputStream inputStream) throws IOException { final OLogEntryHeader header = decodeAndCheckCrc(inputStream, SCHEMA); skipContent(inputStream, header.getContentLength()); return header.getSeqNum(); } private void skipContent(InputStream inputStream, int contentLength) throws IOException { skip(inputStream, IntMath.checkedAdd(contentLength, CRC_BYTES)); } private QuorumConfiguration deserializeQuorumConfiguration(OLogEntryHeader header, ByteBuffer buffer) { assert header.getType() == OLogContentType.QUORUM_CONFIGURATION; OLogContent content = OLogContent.deserialize(buffer, OLogContentType.QUORUM_CONFIGURATION); OLogEntry entry = new OLogEntry(0, 0, content); QuorumConfigurationMessage quorumConfigurationMessage = entry.toProtostuff().getQuorumConfiguration(); return QuorumConfiguration.fromProtostuff(quorumConfigurationMessage); } } }