/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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 com.twitter.distributedlog; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import org.apache.commons.codec.binary.Base64; import org.apache.commons.codec.binary.Hex; import java.nio.ByteBuffer; /** * DistributedLog Sequence Number (DLSN) is the system generated sequence number for log record. * * <p>DLSN is comprised with 3 components: * <ul> * <li>LogSegment Sequence Number: the sequence number of log segment that the record is written in * <li>Entry Id: the entry id of the entry that the record is batched in * <li>Slot Id: the slot id that the record is in the entry * </ul> * * @see LogRecordWithDLSN */ public class DLSN implements Comparable<DLSN> { public static final byte VERSION0 = (byte) 0; public static final byte VERSION1 = (byte) 1; // The initial DLSN that DL starts with public static final DLSN InitialDLSN = new DLSN(1, 0 , 0); // The non-inclusive lower bound DLSN public static final DLSN NonInclusiveLowerBound = new DLSN(1, 0 , -1); // Invalid DLSN public static final DLSN InvalidDLSN = new DLSN(0,-1,-1); static final byte CUR_VERSION = VERSION1; static final int VERSION0_LEN = Long.SIZE * 3 + Byte.SIZE; static final int VERSION1_LEN = Long.SIZE * 3 / Byte.SIZE + 1; private final long logSegmentSequenceNo; private final long entryId; private final long slotId; public DLSN(long logSegmentSequenceNo, long entryId, long slotId) { this.logSegmentSequenceNo = logSegmentSequenceNo; this.entryId = entryId; this.slotId = slotId; } /** * Return the sequence number of the log segment that the record is written to. * * @return sequence number of the log segment that the record is written to. */ public long getLogSegmentSequenceNo() { return logSegmentSequenceNo; } /** * use {@link #getLogSegmentSequenceNo()} instead */ @Deprecated long getLedgerSequenceNo() { return logSegmentSequenceNo; } /** * Return the entry id of the batch that the record is written to. * * @return entry id of the batch that the record is written to. */ public long getEntryId() { return entryId; } /** * Return the slot id in the batch that the record is written to. * * @return slot id in the batch that the record is written to. */ public long getSlotId() { return slotId; } @Override public int compareTo(DLSN that) { if (this.logSegmentSequenceNo != that.logSegmentSequenceNo) { return (this.logSegmentSequenceNo < that.logSegmentSequenceNo)? -1 : 1; } else if (this.entryId != that.entryId) { return (this.entryId < that.entryId)? -1 : 1; } else { return (this.slotId < that.slotId)? -1 : (this.slotId == that.slotId)? 0 : 1; } } /** * Serialize the DLSN into bytes with current version. * * @return the serialized bytes */ public byte[] serializeBytes() { return serializeBytes(CUR_VERSION); } /** * Serialize the DLSN into bytes with given <code>version</code>. * * @param version * version to serialize the DLSN * @return the serialized bytes */ public byte[] serializeBytes(byte version) { Preconditions.checkArgument(version <= CUR_VERSION && version >= VERSION0); byte[] data = new byte[CUR_VERSION == version ? VERSION1_LEN : VERSION0_LEN]; ByteBuffer bb = ByteBuffer.wrap(data); bb.put(version); bb.putLong(logSegmentSequenceNo); bb.putLong(entryId); bb.putLong(slotId); return data; } /** * Serialize the DLSN into base64 encoded string. * * @return serialized base64 string * @see #serializeBytes() */ public String serialize() { return serialize(CUR_VERSION); } /** * Serialize the DLSN into base64 encoded string with given <code>version</code>. * * @param version * version to serialize the DLSN * @return the serialized base64 string * @see #serializeBytes(byte) */ public String serialize(byte version) { return Base64.encodeBase64String(serializeBytes(version)); } /** * Deserialize the DLSN from base64 encoded string <code>dlsn</code>. * * @param dlsn * base64 encoded string * @return dlsn */ public static DLSN deserialize(String dlsn) { byte[] data = Base64.decodeBase64(dlsn); return deserializeBytes(data); } /** * Deserialize the DLSN from bytes array. * * @param data * serialized bytes * @return dlsn */ public static DLSN deserializeBytes(byte[] data) { ByteBuffer bb = ByteBuffer.wrap(data); byte version = bb.get(); if (VERSION0 == version) { if (VERSION0_LEN != data.length) { throw new IllegalArgumentException("Invalid version zero DLSN " + Hex.encodeHexString(data)); } } else if (VERSION1 == version) { if (VERSION1_LEN != data.length) { throw new IllegalArgumentException("Invalid version one DLSN " + Hex.encodeHexString(data)); } } else { throw new IllegalArgumentException("Invalid DLSN : version = " + version + ", " + Hex.encodeHexString(data)); } return new DLSN(bb.getLong(), bb.getLong(), bb.getLong()); } // Keep original version0 logic for testing. @VisibleForTesting static DLSN deserialize0(String dlsn) { byte[] data = Base64.decodeBase64(dlsn); ByteBuffer bb = ByteBuffer.wrap(data); byte version = bb.get(); if (VERSION0 != version || VERSION0_LEN != data.length) { throw new IllegalArgumentException("Invalid DLSN " + dlsn); } return new DLSN(bb.getLong(), bb.getLong(), bb.getLong()); } @Override public String toString() { return "DLSN{" + "logSegmentSequenceNo=" + logSegmentSequenceNo + ", entryId=" + entryId + ", slotId=" + slotId + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof DLSN)) return false; DLSN dlsn = (DLSN) o; if (entryId != dlsn.entryId) return false; if (logSegmentSequenceNo != dlsn.logSegmentSequenceNo) return false; if (slotId != dlsn.slotId) return false; return true; } @Override public int hashCode() { int result = (int) (logSegmentSequenceNo ^ (logSegmentSequenceNo >>> 32)); result = 31 * result + (int) (entryId ^ (entryId >>> 32)); result = 31 * result + (int) (slotId ^ (slotId >>> 32)); return result; } /** * Positions to a DLSN greater than the current value - this may not * correspond to an actual LogRecord, its just used by the positioning logic * to position the reader * * @return the next DLSN */ public DLSN getNextDLSN() { return new DLSN(logSegmentSequenceNo, entryId, slotId + 1); } /** * Positions to a DLSN greater than the current value - this may not * correspond to an actual LogRecord, its just used by the positioning logic * to position the reader * * @return the next DLSN */ public DLSN positionOnTheNextLedger() { return new DLSN(logSegmentSequenceNo + 1 , 0, 0); } }