/* * Copyright (c) 2008 - 2011, Jan Stender, Bjoern Kolbeck, Mikael Hoegqvist, * Felix Hupfeld, Zuse Institute Berlin * * Licensed under the BSD License, see LICENSE file for details. * */ package de.mxro.thrd.babudb05.log; import java.util.zip.Checksum; import de.mxro.thrd.babudb05.lsmdb.LSMDBRequest; import de.mxro.thrd.babudb05.lsmdb.LSN; import de.mxro.thrd.xstreemfs.foundation.buffer.BufferPool; import de.mxro.thrd.xstreemfs.foundation.buffer.ReusableBuffer; import de.mxro.thrd.xstreemfs.foundation.logging.Logging; /** * * @author bjko */ public class LogEntry { /** * length of the entry's header (excluding the length field itself) */ protected static final int headerLength = Integer.SIZE / 8 * 4 + Long.SIZE / 8 + Byte.SIZE / 8; public static final boolean USE_CHECKSUMS = true; public static final byte PAYLOAD_TYPE_INSERT = 0; public static final byte PAYLOAD_TYPE_SNAP = 1; public static final byte PAYLOAD_TYPE_CREATE = 2; public static final byte PAYLOAD_TYPE_COPY = 3; public static final byte PAYLOAD_TYPE_DELETE = 4; public static final byte PAYLOAD_TYPE_SNAP_DELETE = 5; public static final byte PAYLOAD_TYPE_TRANSACTION = 6; /** * view ID of the log entry. The view ID is an epoch number which creates a * total order on the log entries (viewId.logSequenceNo). */ protected int viewId = -1; protected long logSequenceNo = -1L; protected int checksum; protected ReusableBuffer payload; protected SyncListener listener; private LSMDBRequest<?> attachment; protected byte payloadType; private LogEntry() { } public LogEntry(ReusableBuffer payload, SyncListener l, byte payloadType) { assert(payload != null); this.payload = payload; this.listener = l; this.payloadType = payloadType; } public void assignId(int viewId, long logSequenceNo) { this.viewId = viewId; this.logSequenceNo = logSequenceNo; } public ReusableBuffer serialize(Checksum csumAlgo) { assert (viewId > 0); assert (logSequenceNo > 0); final int bufSize = headerLength + payload.remaining(); ReusableBuffer buf = BufferPool.allocate(bufSize); buf.putInt(bufSize); buf.putInt(checksum); buf.putInt(viewId); buf.putLong(logSequenceNo); buf.put(payloadType); buf.put(payload); payload.flip(); // otherwise payload is not reusable buf.putInt(bufSize); buf.flip(); if (USE_CHECKSUMS) { // reset the old checksum to 0, before calculating a new one buf.position(Integer.SIZE / 8); buf.putInt(0); buf.position(0); csumAlgo.update(buf.array(), 0, bufSize); int cPos = buf.position(); // write the checksum to the buffer buf.position(Integer.SIZE / 8); buf.putInt((int) csumAlgo.getValue()); buf.position(cPos); } return buf; } public void setListener(SyncListener listener) { this.listener = listener; } public SyncListener getListener() { return listener; } public ReusableBuffer getPayload() { return this.payload; } public int getViewId() { return this.viewId; } public long getLogSequenceNo() { return this.logSequenceNo; } public LSN getLSN() { if (this.viewId == -1 && this.logSequenceNo == -1L) return null; else return new LSN(this.viewId, this.logSequenceNo); } public static void checkIntegrity(ReusableBuffer data) throws LogEntryException { int cPos = data.position(); if (data.remaining() < Integer.SIZE / 8) throw new LogEntryException("Empty data. Cannot read log entry."); int length1 = data.getInt(); if ((length1 - Integer.SIZE / 8) > data.remaining()) { data.position(cPos); Logging.logMessage(Logging.LEVEL_DEBUG, null, "not long enough"); throw new LogEntryException("The log entry is incomplete. " + "The length indicated in the header exceeds the " + "available data."); } data.position(cPos + length1 - Integer.SIZE / 8); int length2 = data.getInt(); data.position(cPos); if (length1 != length2) { throw new LogEntryException("Invalid Frame. The length entries do" + " not match; length1=" + length1 + ", length2=" + length2); } } public static LogEntry deserialize(ReusableBuffer data, Checksum csumAlgo) throws LogEntryException { checkIntegrity(data); final int startPos = data.position(); final int bufSize = data.getInt(); LogEntry e = new LogEntry(); e.checksum = data.getInt(); e.viewId = data.getInt(); e.logSequenceNo = data.getLong(); e.payloadType = data.get(); final int payloadSize = bufSize - headerLength; int payloadPosition = data.position(); ReusableBuffer payload = data.createViewBuffer(); payload.range(payloadPosition, payloadSize); e.payload = payload; if (USE_CHECKSUMS) { // reset the old checksum to 0, before calculating a new one data.position(startPos + Integer.SIZE / 8); data.putInt(0); data.position(startPos); csumAlgo.update(data.array(), startPos, bufSize); int csum = (int) csumAlgo.getValue(); // write back the checksum to the buffer data.position(startPos + Integer.SIZE / 8); data.putInt((int) e.checksum); if (csum != e.checksum) { throw new LogEntryException( "Invalid Checksum. Checksum in log entry and calculated " + "checksum do not match."); } } data.position(startPos); return e; } public void free() { BufferPool.free(payload); payload = null; } public LSMDBRequest<?> getAttachment() { return attachment; } public void setAttachment(LSMDBRequest<?> attachment) { this.attachment = attachment; } public byte getPayloadType() { return payloadType; } /* (non-Javadoc) * @see java.lang.Object#clone() */ @Override public LogEntry clone() { LogEntry result = new LogEntry(payload.createViewBuffer(), listener, payloadType); result.assignId(viewId, logSequenceNo); result.attachment = attachment; result.checksum = checksum; return result; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return "LSN(" + viewId + ":" + logSequenceNo + ") with " + payload.remaining() + " bytes of payload."; } }