package org.corfudb.protocols.wireprotocol; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Getter; import org.corfudb.protocols.logprotocol.LogEntry; import org.corfudb.runtime.CorfuRuntime; import org.corfudb.util.serializer.Serializers; import java.util.EnumMap; import java.util.concurrent.atomic.AtomicReference; /** * Created by mwei on 8/15/16. */ public class LogData implements ICorfuPayload<LogData>, IMetadata, ILogData { public static final LogData EMPTY = new LogData(DataType.EMPTY); public static final LogData HOLE = new LogData(DataType.HOLE); @Getter final DataType type; @Getter byte[] data; private ByteBuf serializedCache = null; private transient final AtomicReference<Object> payload = new AtomicReference<>(); public Object getPayload(CorfuRuntime runtime) { Object value = payload.get(); if (value == null) { synchronized (this.payload) { value = this.payload.get(); if (value == null) { if (data == null) { this.payload.set(null); } else { ByteBuf copyBuf = Unpooled.wrappedBuffer(data); final Object actualValue = Serializers.CORFU.deserialize(copyBuf, runtime); // TODO: Remove circular dependency on logentry. if (actualValue instanceof LogEntry) { ((LogEntry) actualValue).setEntry(this); ((LogEntry) actualValue).setRuntime(runtime); } value = actualValue == null ? this.payload : actualValue; this.payload.set(value); copyBuf.release(); data = null; } } } } data = null; return value; } @Override public synchronized void releaseBuffer() { if (serializedCache != null) { serializedCache.release(); if (serializedCache.refCnt() == 0) { serializedCache = null; } } } @Override public synchronized void acquireBuffer() { if (serializedCache == null) { serializedCache = Unpooled.buffer(); doSerializeInternal(serializedCache); } else { serializedCache.retain(); } } @Override public int getSizeEstimate() { if (data != null) { return data.length; } return 1; } @Getter final EnumMap<LogUnitMetadataType, Object> metadataMap; public LogData(ByteBuf buf) { type = ICorfuPayload.fromBuffer(buf, DataType.class); if (type == DataType.DATA) { data = ICorfuPayload.fromBuffer(buf, byte[].class); } else { data = null; } if (type.isMetadataAware()) { metadataMap = ICorfuPayload.enumMapFromBuffer(buf, IMetadata.LogUnitMetadataType.class, Object.class); } else { metadataMap = new EnumMap<>(IMetadata.LogUnitMetadataType.class); } } public LogData(DataType type) { this.type = type; this.data = null; this.metadataMap = new EnumMap<>(IMetadata.LogUnitMetadataType.class); } public LogData(final Object object) { this.type = DataType.DATA; this.data = null; this.payload.set(object); if (object instanceof LogEntry) { ((LogEntry) object).setEntry(this); } this.metadataMap = new EnumMap<>(IMetadata.LogUnitMetadataType.class); } public LogData(final DataType type, final ByteBuf buf) { this.type = type; this.data = byteArrayFromBuf(buf); this.metadataMap = new EnumMap<>(IMetadata.LogUnitMetadataType.class); } public byte[] byteArrayFromBuf(final ByteBuf buf) { ByteBuf readOnlyCopy = buf.asReadOnly(); readOnlyCopy.resetReaderIndex(); byte[] outArray = new byte[readOnlyCopy.readableBytes()]; readOnlyCopy.readBytes(outArray); return outArray; } @Override public void doSerialize(ByteBuf buf) { if (serializedCache != null) { serializedCache.resetReaderIndex(); buf.writeBytes(serializedCache); } else { doSerializeInternal(buf); } } void doSerializeInternal(ByteBuf buf) { ICorfuPayload.serialize(buf, type); if (type == DataType.DATA) { if (data == null) { int lengthIndex = buf.writerIndex(); buf.writeInt(0); Serializers.CORFU.serialize(payload.get(), buf); int size = buf.writerIndex() - (lengthIndex + 4); buf.writerIndex(lengthIndex); buf.writeInt(size); buf.writerIndex(lengthIndex + size + 4); } else { ICorfuPayload.serialize(buf, data); } } if (type.isMetadataAware()) { ICorfuPayload.serialize(buf, metadataMap); } } @Override public String toString() { return "LogData[" + getGlobalAddress() + "]"; } }