com.linkedin.databus.core.DbusEventV1.java Source code

Java tutorial

Introduction

Here is the source code for com.linkedin.databus.core.DbusEventV1.java

Source

package com.linkedin.databus.core;
/*
 *
 * Copyright 2013 LinkedIn Corp. All rights reserved
 *
 * Licensed 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.
 *
*/

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import org.apache.commons.codec.binary.Hex;
import org.apache.log4j.Logger;

import com.linkedin.databus.core.util.ByteBufferCRC32;
import com.linkedin.databus.core.util.StringUtils;
import com.linkedin.databus.core.util.TimeUtils;
import com.linkedin.databus.core.util.Utils;

/**
 * This class represents a Databus event stored in a {@link java.nio.ByteBuffer}.
 *
 * <h3>Binary Serialization Format </h3>
 *
 * The table below summarizes the serialization format of Databus events as stored into memory and
 * sent over the wire.
 *
 * <table border="1" >
 * <tr> <th>Field</th> <th> Offset </th> <th>Type</th> <th>Size</th> <th>Description</th> </tr>
 * <tr>
 *    <th colspan="5">Header (61 bytes for events with long keys, 57 bytes + key length)</th>
 * </tr>
 * <tr> <td>Version</td> <td>0</td> <td>byte</td> <td>1</td> <td> A value denoting the
 * version of the message format</td> </tr>
 * <tr> <td>HeaderCrc</td> <td>1</td> <td>int</td> <td>4</td> <td>CRC computed over the header</td> </tr>
 * <tr> <td>Length</td> <td>5</td> <td>int</td> <td>4</td> <td>Total length of the event in bytes
 * (fixed-length header + variable-length payload) </td> </tr>
 * <tr> <td>Attributes</td> <td>9</td> <td>short</td> <td>2</td> <td>Event attributes bitmap (see
 * below)</td> </tr>
 * <tr> <td>Sequence</td> <td>11</td> <td>long</td> <td>8</td> <td>Sequence number for the event
 * window in which this event was generated</td> </tr>
 * <tr> <td>PhysicalPartitionId</td> <td>19</td> <td>short</td> <td>2</td> <td>physical partition-id
 * -> represents a sequence generator</td> </tr>
 * <tr> <td>LogicalPartitionId</td> <td>21</td> <td>short</td> <td>2</td> <td>logical partition-id
 * -> represents a logical partition of the physical stream</td> </tr>
 * <tr> <td>NanoTimestamp</td> <td>23</td> <td>long</td> <td>8</td> <td>Time (in nanoseconds) at
 * which the event was generated</td> </tr>
 * <tr> <td>srcId</td> <td>31</td> <td>short</td> <td>2</td> <td>Databus source id for the event</td> </tr>
 * <tr> <td>SchemaId*</td> <td>33</td> <td>byte[]</td> <td>16</td> <td>hash for the schema used to
 * generate the event</td> </tr>
 * <tr> <td>ValueCrc</td> <td>49</td> <td>int</td> <td>4</td> <td>a CRC computed over the
 * variable-length payload of the event</td> </tr>
 * <tr> <td>Key</td> <td>53</td> <td>long</td> <td>8</td> <td>key value for events with long keys
 * <tr> <td>KeySize</td> <td>53</td> <td>int</td> <td>4</td> <td>key length for byte[]-valued keys </td> </tr>
 * <tr>
 *    <th colspan="5">Variable-size payload</th>
 * </tr>
 * <tr> <td>Key</td> <td>57</td> <td>byte[]</td> <td> 4 or KeySize</td>
 * <td>32 LSB from key value for long-typed keys or key value for byte[] keys</td> </tr>
 * <tr> <td>Value</td> <td>61 or 57 + KeySize</td> <td>byte[]</td> <td>Length - Offset(Value)</td>
 * <td>Serialized event payload</td> </tr>
 * </table>
 * <p>
 * &#42 For REPL_DBUS we are using schemaid field to pass Schema Version. So in this case SchemaId
 * row of the table should be replaced with: <p>
 *  <table border="1">
 * <tr><td>SchemaVersion </td> <td>33</td> <td>short</td> <td>2</td> <td> schema version for the event</td></tr>
 * <tr><td>SchemaId(not valid)</td> <td>35</td> <td>byte[]</td> <td>14</td> <td>hash for the schema used to
 * generate the event</td> </tr>
 * </table>
 *
 * <h3>JSON serialization format </h3>
 *
 * The table below summarizes the JSON serialization format.
 *
 * <table border="1">
 * <tr> <th>Attribute</th> <th>Type</th> <th>Description</th> <th>Optional</th> </tr>
 * <tr> <td>opcode</td> <td>String</td> <td>UPSERT or DELETE</td> <td>Yes</td> </tr>
 * <tr> <td>keyBytes</td> <td>String</td> <td>Base-64 encoding of the key byte sequence for string
 * keys</td> <td rowspan="2">One of the two needs to be present</td> </tr>
 * <tr> <td>key</td> <td>Long</td> <td>key value for numeric keys</td> </tr>
 * <tr> <td>sequence</td> <td>Long</td> <td>event sequence number</td> <td>No</td> </tr>
 * <tr> <td>logicalPartitionId</td> <td>Short</td> <td>logical partition id</td> <td>No</td> </tr>
 * <tr> <td>physicalPartitionId</td> <td>Short</td> <td>physical partition id</td> <td>No</td> </tr>
 * <tr> <td>timestampInNanos</td> <td>Long</td> <td>creation timestamp in nanoseconds since the Unix
 * Epoch</td> <td>No</td> </tr>
 * <tr> <td>srcId</td> <td>Short</td> <td>source id</td> <td>No</td> </tr>
 * <tr> <td>schemaId</td> <td>String</td> <td>Base-64 encoding of the event serialization schema
 * hash id</td> <td>No</td> </tr>
 * <tr> <td>valueEnc</td> <td>String</td> <td>value encoding format: JSON or JSON_PLAIN</td>
 * <td>No</td> </tr>
 * <tr> <td>endOfPeriod</td> <td>Boolean</td> <td>true iff the event denotes end of event window</td>
 * <td>Yes; default is false</td> </tr>
 * <tr> <td>value</td> <td>String</td> <td>Literal value string for JSON_PLAIN encoding or Base-64
 * encoding of the value byte sequence for JSON encoding</td> <td>Yes; default is false</td> </tr>
 * </table>
 *
 * <h3>Event attributes</h3>
 *
 * The table below summarizes the Databus event attribute bits
 *
 * <table border="1">
 * <tr> <th>Attribute</th> <th>Bit N</th> <th>Description</th> </tr>
 * <tr> <td>OpCode0</td> <td>0</td> <td>Bit 0 of event opcode</td> </tr>
 * <tr> <td>OpCode1</td> <td>1</td> <td>bit 1 of event opcode</td> </tr>
 * <tr> <td>Trace</td> <td>2</td> <td>The event is a trace event</td> </tr>
 * <tr> <td>ByteKey</td> <td>3</td> <td>The event has a byte[] key</td> </tr>
 * <tr> <td>EoP</td> <td>4</td> <td>The event is the last event in a event window</td> </tr>
 * <tr> <td>ExtReplEvent</td> <td>8</td> <td>The event was generated through external replication
 *          (e.g. originating in a different data center)</td> </tr>
 * </table>
 *
 * <h3>Event opcodes</h3>
 *
 * Currently, Databus supports two choices of event opcodes
 *
 * <ul>
 * <li>1 - UPSERT</li>
 * <li>2 - DELETE</li>
 * </ul>
 *
 * <h3>Databus source ids</h3>
 *
 * The possible values for Databus source ids are partitioned into several ranges. In general, all
 * positive source ids are used to uniquely represent a Databus source. The source ids are used in
 * Databus data messages. All non-positive values are reserved for Databus system use. These source
 * ids are used in Databus control messages.
 *
 * <ul>
 * <li> [1, {@link java.lang.Short.MAX_VALUE}] - data source ids</li>
 * <li> [{@link java.lang.Short.MIN_VALUE}, 0] - system source ids
 *   <ul>
 *   <li> [{@link com.linkedin.databus.core.DbusEventInternalWritable#PRIVATE_RANGE_MAX_SRCID} + 1, 0] - global system source ids. These control
 *   messages will be transmitted to other Databus components over the network
 *     <ul>
 *     <li>-3 - Checkpoint event</li>
 *     </ul>
 *   </li>
 *   <li> [{@link java.lang.Short.MIN_VALUE}, {@link com.linkedin.databus.core.DbusEventInternalWritable#PRIVATE_RANGE_MAX_SRCID}] - private system
 *   source ids. These messages are used for internal communication inside a Databus component and
 *   are not transmitted to other Databus components.
 *   </ul>
 * </li>
 * </ul>
 *
 * </table>
 */
public class DbusEventV1 extends DbusEventSerializable implements Cloneable {
    public static final String MODULE = DbusEventV1.class.getName();
    public static final Logger LOG = Logger.getLogger(MODULE);

    /** Serialization Format is :
     * <pre>
     *   Version (1 byte)               // 0 for DbusEventV1 (historical), 2 for DbusEventV2
     *   HeaderCrc (4 bytes)            // CRC to protect the header from being corrupted
     *   Length (4 bytes)
     *   Attributes (2 bytes)           // Key-type, Trace marker, Event-opcode
     *   Sequence (8 bytes)             // Sequence number for the event window in which this event was generated
     *   Physical PartitionId (2 bytes) // Short physical partition-id -> represents a sequence generator
     *   Logical PartitionId (2 bytes)  // Short logical partition-id -> represents a logical partition of the physical stream
     *   NanoTimestamp (8 bytes)        // Time (in nanoseconds) at which the event was generated
     *   SrcId (short)                  // SourceId for the event
     *   SchemaId (16 bytes)            // 16-byte hash (digest) of the schema used to generate the event
     *   ValueCrc (4 bytes)             // CRC to protect the value from being corrupted
     *   Key (8 bytes long key)  or KeySize (4 bytes for byte[] key)
     *   Key Bytes (k bytes for byte[] key)
     *   Value (N bytes)                // Serialized Event
     * </pre>
     */

    private static final int VersionOffset = 0;
    private static final int VersionLength = 1;
    private static final int HeaderCrcOffset = VersionOffset + VersionLength;
    private static final int HeaderCrcLength = 4;
    private static final int HeaderCrcDefault = 0;
    //used by the SendEvents command to determine the size of the incoming event
    private static final int LengthOffset = HeaderCrcOffset + HeaderCrcLength;
    private static final int LengthLength = 4;
    private static final int AttributesOffset = LengthOffset + LengthLength;
    private static final int AttributesLength = 2;
    private static final int SequenceOffset = AttributesOffset + AttributesLength;
    private static final int SequenceLength = 8;
    private static final int PhysicalPartitionIdOffset = SequenceOffset + SequenceLength;
    private static final int PhysicalPartitionIdLength = 2;
    private static final int LogicalPartitionIdOffset = PhysicalPartitionIdOffset + PhysicalPartitionIdLength;
    private static final int LogicalPartitionIdLength = 2;
    private static final int TimestampOffset = LogicalPartitionIdOffset + LogicalPartitionIdLength;
    private static final int TimestampLength = 8;
    private static final int SrcIdOffset = TimestampOffset + TimestampLength;
    private static final int SrcIdLength = 2;
    private static final int SchemaIdOffset = SrcIdOffset + SrcIdLength;
    private static final int SchemaIdLength = 16;
    private static final int ValueCrcOffset = SchemaIdOffset + SchemaIdLength;
    private static final int ValueCrcLength = 4;
    private static final int LongKeyOffset = ValueCrcOffset + ValueCrcLength;
    private static final int LongKeyLength = 8;
    private static final int LongKeyValueOffset = LongKeyOffset + LongKeyLength;
    private static final int StringKeyLengthOffset = ValueCrcOffset + ValueCrcLength;
    private static final int StringKeyLengthLength = 4;
    private static final int StringKeyOffset = StringKeyLengthOffset + StringKeyLengthLength;

    private static int LongKeyHeaderSize = LongKeyValueOffset - LengthOffset;
    private static int StringKeyHeaderSize = StringKeyOffset - LengthOffset; // everything until the key length field is part of the header, for byte[] keys, we crc the key and value blobs together as part of the value crc

    // Attribute masks follow.  Note that AttributesLength forever limits us to 16 bits for V1 events.
    private static final int UPSERT_MASK = 0x0001;
    private static final int DELETE_MASK = 0x0002;
    private static final int TRACE_FLAG_MASK = 0x0004;
    private static final int KEY_TYPE_MASK = 0x0008;
    private static final int EXT_REPLICATED_EVENT_MASK = 0x0100; // why is this not 0x0010?  (DbusEvent.pdf concurs, sigh)

    private static final int MinHeaderSize = LongKeyOffset;
    private static final int MaxHeaderSize = Math.max(LongKeyValueOffset, StringKeyOffset);

    private static final byte[] EmptyAttributesBigEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] UpsertLongKeyAttributesBigEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] UpsertStringKeyAttributesBigEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] DeleteLongKeyAttributesBigEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] DeleteStringKeyAttributesBigEndian = new byte[DbusEventV1.AttributesLength];

    private static final byte[] EmptyAttributesLittleEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] UpsertLongKeyAttributesLittleEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] UpsertStringKeyAttributesLittleEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] DeleteLongKeyAttributesLittleEndian = new byte[DbusEventV1.AttributesLength];
    private static final byte[] DeleteStringKeyAttributesLittleEndian = new byte[DbusEventV1.AttributesLength];

    static {
        // array elements automatically initialized to default value for type (byte:  0)

        setOpcode(UpsertLongKeyAttributesBigEndian, DbusOpcode.UPSERT, ByteOrder.BIG_ENDIAN);
        setOpcode(UpsertStringKeyAttributesBigEndian, DbusOpcode.UPSERT, ByteOrder.BIG_ENDIAN);
        setKeyTypeString(UpsertStringKeyAttributesBigEndian, ByteOrder.BIG_ENDIAN);

        setOpcode(DeleteLongKeyAttributesBigEndian, DbusOpcode.DELETE, ByteOrder.BIG_ENDIAN);
        setOpcode(DeleteStringKeyAttributesBigEndian, DbusOpcode.DELETE, ByteOrder.BIG_ENDIAN);
        setKeyTypeString(DeleteStringKeyAttributesBigEndian, ByteOrder.BIG_ENDIAN);

        setOpcode(UpsertLongKeyAttributesLittleEndian, DbusOpcode.UPSERT, ByteOrder.LITTLE_ENDIAN);
        setOpcode(UpsertStringKeyAttributesLittleEndian, DbusOpcode.UPSERT, ByteOrder.LITTLE_ENDIAN);
        setKeyTypeString(UpsertStringKeyAttributesLittleEndian, ByteOrder.LITTLE_ENDIAN);

        setOpcode(DeleteLongKeyAttributesLittleEndian, DbusOpcode.DELETE, ByteOrder.LITTLE_ENDIAN);
        setOpcode(DeleteStringKeyAttributesLittleEndian, DbusOpcode.DELETE, ByteOrder.LITTLE_ENDIAN);
        setKeyTypeString(DeleteStringKeyAttributesLittleEndian, ByteOrder.LITTLE_ENDIAN);
    }

    // near-empty constructor that doesn't create a useful event
    public DbusEventV1() {
        _inited = false;
    }

    public DbusEventV1(ByteBuffer buf, int position) {
        resetInternal(buf, position);
    }

    private int serializedKeyLength() {
        if (isKeyString()) {
            return (StringKeyLengthLength + _buf.getInt(_position + StringKeyLengthOffset));
        } else {
            return LongKeyLength;
        }
    }

    private static int KeyLength(byte[] key) {
        return (key.length + StringKeyLengthLength);
    } // length of the string + 4 bytes to write the length

    private static int LongKeyValueOffset() {
        return (LongKeyOffset + LongKeyLength);
    }

    private static int StringValueOffset(int keyLength) {
        return (StringKeyOffset + keyLength);
    }

    public static int getLengthOffset() {
        return LengthOffset;
    }

    public static int getLengthLength() {
        return LengthLength;
    }

    /**
     * Serializes an End-Of-Period Marker onto the ByteBuffer passed in.
     * @param serializationBuffer - The ByteBuffer to serialize the event in. The buffer must have enough space to accommodate
     *                              the event. (76 bytes)
     * @param eventInfo - The timestamp to use for the EOP marker
     * @return the number of bytes written
     */
    public static int serializeEndOfPeriodMarker(ByteBuffer serializationBuffer, DbusEventInfo eventInfo) {
        byte[] attributes = (serializationBuffer.order() == ByteOrder.BIG_ENDIAN) ? EmptyAttributesBigEndian
                : EmptyAttributesLittleEndian;
        return serializeFullEvent(DbusEventInternalWritable.EOPMarkerKey.getLongKey(), serializationBuffer,
                eventInfo, attributes);
    }

    /**
     * Creates an EOP event with a given sequence and partition number
     * @return a ByteBuffer containing the event
     */
    public static ByteBuffer serializeEndOfPeriodMarker(long seq, short partN, ByteOrder byteOrder) {
        DbusEventInfo eventInfo = new DbusEventInfo(DbusOpcode.UPSERT, seq, partN, partN,
                TimeUtils.currentNanoTime(), DbusEventInternalWritable.EOPMarkerSrcId, new byte[16], new byte[0],
                false, true);
        ByteBuffer buf = ByteBuffer.allocate(DbusEventV1.MaxHeaderSize).order(byteOrder);
        DbusEventV1.serializeEndOfPeriodMarker(buf, eventInfo);
        return buf;
    }

    /**
     * non-threadsafe : serializationBuffer needs to be protected if multiple threads are writing to it concurrently
     */
    public static int serializeEvent(DbusEventKey key, short pPartitionId, short lPartitionId,
            long timeStampInNanos, short srcId, byte[] schemaId, byte[] value, boolean enableTracing,
            ByteBuffer serializationBuffer) throws KeyTypeNotImplementedException {
        DbusEventInfo dbusEventInfo = new DbusEventInfo(DbusOpcode.UPSERT, 0L, pPartitionId, lPartitionId,
                timeStampInNanos, srcId, schemaId, value, enableTracing, false /*autocommit*/);
        return serializeEvent(key, serializationBuffer, dbusEventInfo);
    }

    public static int serializeEvent(DbusEventKey key, ByteBuffer serializationBuffer, DbusEventInfo dbusEventInfo)
            throws KeyTypeNotImplementedException {
        switch (key.getKeyType()) {
        case LONG:
            return serializeLongKeyEvent(key.getLongKey(), serializationBuffer, dbusEventInfo);
        case STRING:
            return serializeStringKeyEvent(key.getStringKeyInBytes(), serializationBuffer, dbusEventInfo);
        default:
            throw new KeyTypeNotImplementedException();
        }
    }

    private static int serializeLongKeyEvent(long key, ByteBuffer serializationBuffer, DbusEventInfo eventInfo) {
        byte[] attributes = null;

        // Event without explicit opcode specified should always be considered UPSERT or existing code will break
        if (eventInfo.getOpCode() == DbusOpcode.DELETE) {
            if (serializationBuffer.order() == ByteOrder.BIG_ENDIAN)
                attributes = DeleteLongKeyAttributesBigEndian.clone();
            else
                attributes = DeleteLongKeyAttributesLittleEndian.clone();
        } else {
            if (serializationBuffer.order() == ByteOrder.BIG_ENDIAN)
                attributes = UpsertLongKeyAttributesBigEndian.clone();
            else
                attributes = UpsertLongKeyAttributesLittleEndian.clone();
        }

        if (eventInfo.isEnableTracing()) {
            setTraceFlag(attributes, serializationBuffer.order());
        }
        if (eventInfo.isReplicated())
            setExtReplicationFlag(attributes, serializationBuffer.order());

        return serializeFullEvent(key, serializationBuffer, eventInfo, attributes);
    }

    /**
     * Exposing this method only until we fix DDSDBUS-2282 is fixed.
     * DO NOT USE THIS METHOD EVEN IN TESTS.
     */
    protected static int serializeFullEventWithEmptyAttributes(long key, ByteBuffer buf, DbusEventInfo eventInfo) {
        return serializeFullEvent(key, buf, eventInfo,
                (buf.order() == ByteOrder.BIG_ENDIAN) ? EmptyAttributesBigEndian : EmptyAttributesLittleEndian);
    }

    private static int serializeFullEvent(long key, ByteBuffer serializationBuffer, DbusEventInfo eventInfo,
            byte[] attributes) {

        ByteBuffer valueBuffer = eventInfo.getValueByteBuffer();
        int payloadLen = (valueBuffer == null) ? eventInfo.getValueLength() : valueBuffer.remaining();

        int startPosition = serializationBuffer.position();
        serializationBuffer.put(DbusEventFactory.DBUS_EVENT_V1).putInt(HeaderCrcDefault)
                .putInt(LongKeyValueOffset + payloadLen).put(attributes).putLong(eventInfo.getSequenceId())
                .putShort(eventInfo.getpPartitionId()).putShort(eventInfo.getlPartitionId())
                .putLong(eventInfo.getTimeStampInNanos()).putShort(eventInfo.getSrcId())
                .put(eventInfo.getSchemaId(), 0, 16).putInt(HeaderCrcDefault).putLong(key);
        if (valueBuffer != null) {
            // note. put will advance position. In the case of wrapped byte[] it is ok, in the case of
            // ByteBuffer this is actually a read only copy of the buffer passed in.
            serializationBuffer.put(valueBuffer);
        }

        int stopPosition = serializationBuffer.position();

        long valueCrc = ByteBufferCRC32.getChecksum(serializationBuffer, startPosition + LongKeyValueOffset,
                payloadLen);
        Utils.putUnsignedInt(serializationBuffer, startPosition + ValueCrcOffset, valueCrc);

        if (eventInfo.isAutocommit()) {
            //TODO (DDSDBUS-60): Medium : can avoid new here
            DbusEventV1 e = new DbusEventV1(serializationBuffer, startPosition);
            e.applyCrc();
        }

        serializationBuffer.position(stopPosition);
        return (stopPosition - startPosition);
    }

    private static int serializeStringKeyEvent(byte[] key, ByteBuffer serializationBuffer,
            DbusEventInfo eventInfo) {
        ByteBuffer valueBuffer = eventInfo.getValueByteBuffer();
        int payloadLen = (valueBuffer == null) ? eventInfo.getValueLength() : valueBuffer.remaining();

        int startPosition = serializationBuffer.position();
        byte[] attributes = null;

        // Event without explicit opcode specified should always be considered UPSERT or existing code will break
        if (eventInfo.getOpCode() == DbusOpcode.DELETE) {
            if (serializationBuffer.order() == ByteOrder.BIG_ENDIAN)
                attributes = DeleteStringKeyAttributesBigEndian.clone();
            else
                attributes = DeleteStringKeyAttributesLittleEndian.clone();
        } else {
            if (serializationBuffer.order() == ByteOrder.BIG_ENDIAN)
                attributes = UpsertStringKeyAttributesBigEndian.clone();
            else
                attributes = UpsertStringKeyAttributesLittleEndian.clone();
        }

        if (eventInfo.isEnableTracing()) {
            setTraceFlag(attributes, serializationBuffer.order());
        }

        if (eventInfo.isReplicated())
            setExtReplicationFlag(attributes, serializationBuffer.order());

        serializationBuffer.put(DbusEventFactory.DBUS_EVENT_V1).putInt(HeaderCrcDefault)
                .putInt(StringValueOffset(key.length) + payloadLen).put(attributes)
                .putLong(eventInfo.getSequenceId()).putShort(eventInfo.getpPartitionId())
                .putShort(eventInfo.getlPartitionId()).putLong(eventInfo.getTimeStampInNanos())
                .putShort(eventInfo.getSrcId()).put(eventInfo.getSchemaId(), 0, 16).putInt(HeaderCrcDefault)
                .putInt(key.length).put(key);
        if (valueBuffer != null) {
            // note. put will advance position. In the case of wrapped byte[] it is ok, in the case of
            // ByteBuffer this is actually a read only copy of the buffer passed in.
            serializationBuffer.put(valueBuffer);
        }

        int stopPosition = serializationBuffer.position();
        long valueCrc = ByteBufferCRC32.getChecksum(serializationBuffer, startPosition + StringKeyOffset,
                key.length + payloadLen);
        Utils.putUnsignedInt(serializationBuffer, startPosition + ValueCrcOffset, valueCrc);
        if (eventInfo.isAutocommit()) {
            //TODO (DDSDBUS-61): Medium : can avoid new here
            DbusEventV1 e = new DbusEventV1(serializationBuffer, startPosition);
            e.applyCrc();
        }

        serializationBuffer.position(stopPosition);
        return (stopPosition - startPosition);
    }

    private static void setAttributeBit(byte[] attribute, int mask, ByteOrder byteOrder) {
        if (mask > 0xffff) // constrained by AttributesLength, which is exactly 2 for V1 events
        {
            throw new DatabusRuntimeException("attribute mask exceeds max size of attributes"); // or EventCreationException?  InvalidConfigException?
        }
        byte msByte = (byte) ((mask & 0xff00) >> 8);
        byte lsByte = (byte) (mask & 0xff);
        if (byteOrder == ByteOrder.BIG_ENDIAN) {
            attribute[0] |= msByte;
            attribute[1] |= lsByte;
        } else {
            attribute[0] |= lsByte;
            attribute[1] |= msByte;
        }
    }

    private boolean isAttributeBitSet(int mask) {
        return ((_buf.getShort(_position + AttributesOffset) & mask) == mask);
    }

    private static void setOpcode(byte[] attribute, DbusOpcode opcode, ByteOrder byteOrder) {
        switch (opcode) {
        case UPSERT:
            setAttributeBit(attribute, UPSERT_MASK, byteOrder);
            break;
        case DELETE:
            setAttributeBit(attribute, DELETE_MASK, byteOrder);
            break;
        default:
            throw new RuntimeException("Unknown DbusOpcode " + opcode.name());
        }
    }

    @Override
    public DbusOpcode getOpcode() {
        if (isAttributeBitSet(UPSERT_MASK)) {
            return DbusOpcode.UPSERT;
        }
        if (isAttributeBitSet(DELETE_MASK)) {
            return DbusOpcode.DELETE;
        }
        return null;
    }

    private static void setTraceFlag(byte[] attribute, ByteOrder byteOrder) {
        setAttributeBit(attribute, TRACE_FLAG_MASK, byteOrder);
    }

    @Override
    public boolean isTraceEnabled() {
        return isAttributeBitSet(TRACE_FLAG_MASK);
    }

    private static void setKeyTypeString(byte[] attributes, ByteOrder byteOrder) {
        setAttributeBit(attributes, KEY_TYPE_MASK, byteOrder);
    }

    @Override
    public boolean isKeyString() {
        return isAttributeBitSet(KEY_TYPE_MASK);
    }

    @Override
    public boolean isKeySchema() {
        return false;
    }

    @Override
    public boolean isKeyNumber() {
        return !isAttributeBitSet(KEY_TYPE_MASK);
    }

    public static void setExtReplicationFlag(byte[] attribute, ByteOrder byteOrder) {
        setAttributeBit(attribute, EXT_REPLICATED_EVENT_MASK, byteOrder);
    }

    @Override
    public boolean isExtReplicatedEvent() {
        return isAttributeBitSet(EXT_REPLICATED_EVENT_MASK);
    }

    @Override
    public DbusEventInternalReadable reset(ByteBuffer buf, int position) {
        if (buf.get(position) != DbusEventFactory.DBUS_EVENT_V1) {
            verifyByteOrderConsistency(buf, "DbusEventV1.reset()");
            return DbusEventV2Factory.createReadOnlyDbusEventFromBufferUnchecked(buf, position);
        }
        resetInternal(buf, position); // could optionally add "where" arg here, too
        return this;
    }

    public static int length(DbusEventKey key, int valueLen) throws KeyTypeNotImplementedException {
        switch (key.getKeyType()) {
        case LONG:
            return (LongKeyOffset + LongKeyLength + valueLen);
        case STRING:
            return (StringKeyOffset + key.getStringKeyInBytes().length + valueLen);
        default:
            throw new KeyTypeNotImplementedException();
        }
    }

    @Override
    public void applyCrc() {
        //Re-compute the header crc
        int headerLength = headerLength();
        long headerCrc = ByteBufferCRC32.getChecksum(_buf, _position + LengthOffset, headerLength);
        Utils.putUnsignedInt(_buf, _position + HeaderCrcOffset, headerCrc);
    }

    // Returns the number of bytes over which the HeaderCRC should be computed.
    // For string keys, the Header CRC includes bytes starting with 'Length' field and up to the string-key-size field (inclusive),
    // but not the string key itself. The string key is included in the body CRC.
    // For long keys, the Header CRC includes bytes starting with 'Length' field and up to the long key value (inclusive).
    protected int headerLength() {
        if (!isKeyString()) {
            return LongKeyHeaderSize;
        } else {
            return StringKeyHeaderSize;
        }
    }

    @Override
    // this is different from valueLength because it includes key length for string key
    public int payloadLength() {
        int overhead = LengthOffset;
        return (size() - headerLength() - overhead);
    }

    @Override
    public void setSequence(long sequence) {
        _buf.putLong(_position + SequenceOffset, sequence);
    }

    @Override
    public long sequence() {
        return (_buf.getLong(_position + SequenceOffset));
    }

    // If string key, returns the length of the key + 4. If long key, returns 8.
    @Override
    public int keyLength() {
        if (isKeyString()) {
            return (StringKeyLengthLength + _buf.getInt(_position + StringKeyLengthOffset));
        } else {
            return LongKeyLength;
        }
    }

    @Override
    public int keyBytesLength() {
        assert isKeyString() : "Key type = " + _buf.getShort(_position + AttributesOffset);
        return _buf.getInt(_position + StringKeyLengthOffset);
    }

    // Always returns the size of the payload.
    @Override
    public int valueLength() {
        return (size() - (LongKeyOffset + keyLength()));
    }

    @Override
    protected boolean isPartial() {
        return isPartial(true);
    }

    /**
     * @return PARTIAL if the event appears to be a partial event; ERR if the header is corrupted;
     *         OK if the event header is intact and the event appears to be complete
     */
    @Override
    protected HeaderScanStatus scanHeader(boolean logErrors) {
        if (getVersion() != DbusEventFactory.DBUS_EVENT_V1) {
            if (logErrors) {
                LOG.error("unknown version byte in header: " + getVersion());
            }
            return HeaderScanStatus.ERR;
        }

        if (isHeaderPartial(logErrors)) {
            return HeaderScanStatus.PARTIAL;
        }

        int size = size();
        if (size < MinHeaderSize) {
            //size can never be smaller than min header size
            if (logErrors) {
                LOG.error("Event size too small ; size=" + size + " Header size= " + MinHeaderSize);
            }
            return HeaderScanStatus.ERR;
        }
        int headerLength = headerLength();
        long calculatedHeaderCrc = ByteBufferCRC32.getChecksum(_buf, _position + LengthOffset, headerLength);
        if (calculatedHeaderCrc != headerCrc()) {
            if (logErrors) {
                LOG.error("Header CRC mismatch: ");
                LOG.error("headerCrc() = " + headerCrc());
                LOG.error("calculatedCrc = " + calculatedHeaderCrc);
            }
            return HeaderScanStatus.ERR;
        }
        return HeaderScanStatus.OK;
    }

    /**
     * @return one of ERR / OK / PARTIAL
     *
     * TODO:  should this also check _inited?  if _inited == false, presumably we should return ERR
     */
    @Override
    protected EventScanStatus scanEvent(boolean logErrors) {
        HeaderScanStatus h = scanHeader(logErrors);

        if (h != HeaderScanStatus.OK) {
            if (logErrors) {
                LOG.error("HeaderScan error=" + h);
            }
            return (h == HeaderScanStatus.ERR ? EventScanStatus.ERR : EventScanStatus.PARTIAL);
        }

        //check if event is partial
        if (isPartial(logErrors)) {
            return EventScanStatus.PARTIAL;
        }

        int payloadLength = payloadLength();
        long calculatedValueCrc = getCalculatedValueCrc();

        long bodyCrc = bodyCrc();
        if (calculatedValueCrc != bodyCrc) {
            if (logErrors) {
                LOG.error("_buf.order() = " + _buf.order() + ", bodyCrc() = " + bodyCrc + ", crc.getValue() = "
                        + calculatedValueCrc + ", crc-ed block size = " + payloadLength + ", event sequence = "
                        + sequence() + ", timestamp = " + timestampInNanos());
            }
            return EventScanStatus.ERR;
        }

        return EventScanStatus.OK;
    }

    /**
     * @return true if the event appears to be partially read ; does not perform any header checks
     */
    private boolean isPartial(boolean logErrors) {
        int size = size();
        if (size > (_buf.limit() - _position)) {
            if (logErrors)
                LOG.error("partial event: size() = " + size + " buf_position=" + _position + " limit = "
                        + _buf.limit() + " (_buf.limit()-_position) = " + (_buf.limit() - _position));
            return true;
        }
        return false;
    }

    private boolean isHeaderPartial(boolean logErrors) {
        int limit = _buf.limit();
        int bufHeaderLen = limit - _position; // what we have in buffer

        // make sure we can at least read attributes
        if (bufHeaderLen < (AttributesOffset + AttributesLength)) {
            return true;
        }

        int headerLen = headerLength() + LengthOffset; // headerLength() ignores first LengthOffset bytes

        // we need to figure out if we got enough to calc the CRC
        // to calc CRC we are using from LengthOffset => KeyOffset
        // headerLen = the required min len of the header
        // bufHeaderLen - what we have in the buffer
        if (bufHeaderLen < headerLen) {
            if (logErrors)
                LOG.error("Partial Header: bufHeaderLen=" + bufHeaderLen + " is less then headerLen=" + headerLen);
            return true;
        }
        return false;
    }

    @Override
    public String toString() {
        if (null == _buf) {
            return "_buf=null";
        }

        boolean valid = true;

        try {
            valid = isValid(true);
        } catch (Exception ex) {
            LOG.error("DbusEventV1.toString() : Got Exception while trying to validate the event ", ex);
            valid = false;
        }

        if (!valid) {
            StringBuilder sb = new StringBuilder("Position: ");
            sb.append(_position);
            sb.append(", _buf: ");
            sb.append(null != _buf ? _buf.toString() : "null");
            sb.append(", validity: false; hexDump:");
            if (null != _buf && _position >= 0) {
                sb.append(StringUtils.hexdumpByteBufferContents(_buf, _position, 100));
            }

            return sb.toString();
        }

        StringBuilder sb = new StringBuilder(200);
        sb.append("Position=").append(_position).append(";Version=").append(getVersion())
                .append(";isEndOfPeriodMarker=").append(isEndOfPeriodMarker()).append(";HeaderCrc=")
                .append(headerCrc()).append(";Length=").append(size()).append(";Key=");
        if (isKeyString()) {
            sb.append(new String(keyBytes()));
        } else {
            sb.append(key());
        }

        sb.append(";Sequence=").append(sequence()).append(";LogicalPartitionId=").append(logicalPartitionId())
                .append(";PhysicalPartitionId=").append(physicalPartitionId()).append(";Timestamp=")
                .append(timestampInNanos()).append(";SrcId=").append(srcId()).append(";SchemaId=")
                .append(Hex.encodeHex(schemaId())).append(";ValueCrc=").append(bodyCrc());
        //Do not output value - as it's too long.
        /*
        .append(";Value = ");
            
        try {
          sb.append(Utils.byteBufferToString(value(), "UTF-8"));
        } catch (UnsupportedEncodingException e) {
          sb.append(value().toString());
        }
        */
        return sb.toString();

    }

    @Override
    public byte getVersion() {
        return (_buf.get(_position + VersionOffset));
    }

    @Override
    public int size() {
        //    int size = _buf.getInt(_position+LengthOffset);
        return (_buf.getInt(_position + LengthOffset)); // length bytes
    }

    /**
     * Setter for size
     */
    @Override
    public void setSize(int sz) {
        _buf.putInt(_position + LengthOffset, sz);
    }

    @Override
    public long headerCrc() {
        return Utils.getUnsignedInt(_buf, _position + HeaderCrcOffset);
    }

    @Override
    public void setHeaderCrc(long crc) {
        Utils.putUnsignedInt(_buf, _position + HeaderCrcOffset, crc);
    }

    @Override
    public long key() {
        assert isKeyNumber();
        return _buf.getLong(_position + LongKeyOffset);
    }

    @Override
    public byte[] keyBytes() {
        assert isKeyString();
        int keyLength = _buf.getInt(_position + LongKeyOffset);
        byte[] dst = new byte[keyLength];
        for (int i = 0; i < keyLength; ++i) {
            dst[i] = _buf.get(_position + LongKeyOffset + 4 + i);
        }
        return dst;
    }

    @Override
    public short physicalPartitionId() {
        return _buf.getShort(_position + PhysicalPartitionIdOffset);
    }

    @Override
    public short logicalPartitionId() {
        return _buf.getShort(_position + LogicalPartitionIdOffset);
    }

    @Override
    public short getPartitionId() {
        return _buf.getShort(_position + PhysicalPartitionIdOffset);
    }

    @Override
    public long timestampInNanos() {
        return _buf.getLong(_position + TimestampOffset);
    }

    // two temp method to pass schema version from RPL_DBUS
    public void setSchemaVersion(short schemaVersion) {
        _buf.putShort(_position + SchemaIdOffset, schemaVersion);
    }

    @Override
    public short schemaVersion() {
        return _buf.getShort(_position + SchemaIdOffset);
    }

    @Override
    public void setSrcId(int srcId) {
        if (srcId > Short.MAX_VALUE) {
            throw new DatabusRuntimeException("Unsupported source Id in DbusEvent V1:" + srcId);
        }
        _buf.putShort(_position + SrcIdOffset, (short) srcId);
    }

    @Override
    public short srcId() {
        return _buf.getShort(_position + SrcIdOffset);
    }

    @Override
    public int getSourceId() {
        return srcId();
    }

    /** put a byte[] schemaId into the buffer .
     * Make sure CRC is recomputed after that */

    @Override
    public void setSchemaId(byte[] schemaId) {
        for (int i = 0; i < 16; i++) {
            _buf.put(_position + SchemaIdOffset + i, schemaId[i]);
        }
    }

    @Override
    public void recomputeCrcsAfterEspressoRewrite() {
        applyCrc();
    }

    @Override
    public byte[] schemaId() {
        byte[] md5 = new byte[16];
        for (int i = 0; i < 16; i++) {
            md5[i] = _buf.get(_position + SchemaIdOffset + i);
        }
        return md5;
    }

    @Override
    public void schemaId(byte[] md5) {
        for (int i = 0; i < 16; i++) {
            md5[i] = _buf.get(_position + SchemaIdOffset + i);
        }

    }

    @Override
    public long getCalculatedValueCrc() {
        long calcValueCrc = ByteBufferCRC32.getChecksum(_buf,
                isKeyNumber() ? _position + LongKeyValueOffset : _position + StringKeyOffset, payloadLength());
        return calcValueCrc;
    }

    @Override
    public long bodyCrc() {
        return Utils.getUnsignedInt(_buf, _position + ValueCrcOffset);
    }

    @Override
    public void setValueCrc(long crc) {
        Utils.putUnsignedInt(_buf, _position + ValueCrcOffset, crc);
    }

    @Override
    public ByteBuffer value() {
        ByteBuffer value = _buf.asReadOnlyBuffer().order(_buf.order());
        value.position(_position + LongKeyOffset + keyLength());
        value = value.slice().order(_buf.order());
        int valueSize = valueLength();
        value.limit(valueSize);
        value.rewind();
        return value;
    }

    @Override
    public void setValue(byte[] bytes) {
        int offset = _position + LongKeyOffset + serializedKeyLength();
        for (int i = 0; i < bytes.length; ++i) {
            _buf.put(offset + i, bytes[i]);
        }
    }

    @Override
    public DbusEventPart getPayloadPart() {
        return null;
    }

    @Override
    public DbusEventPart getPayloadMetadataPart() {
        return null;
    }

    @Override
    public DbusEventPart getKeyPart() {
        throw new DatabusRuntimeException("V1 event does not support schema keys");
    }

    @Override
    public DbusEventInternalWritable createCopy() {
        ByteBuffer cloned = ByteBuffer.allocate(size()).order(_buf.order());
        cloned.put(getRawBytes());
        DbusEventV1 c = new DbusEventV1(cloned, 0);
        return c;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }

        if (obj instanceof DbusEventV1) {
            DbusEventV1 objEvent = (DbusEventV1) obj;
            return (headerCrc() == objEvent.headerCrc() && bodyCrc() == objEvent.bodyCrc());
        } else {
            return false;
        }
    }

    @Override
    public int hashCode() {
        return ((int) headerCrc() ^ (int) bodyCrc());
    }

    @Override
    public DbusEvent clone(DbusEvent e) {
        DbusEventV1 reuse = (DbusEventV1) e;
        if (null == reuse) {
            reuse = new DbusEventV1(_buf, _position);
        } else {
            // TODO:  option to convert (via reset()) instead of throw exception?
            if (!(e instanceof DbusEventV1)) {
                throw new UnsupportedClassVersionError("Unsupported class:" + e.getClass().getSimpleName());
            }
            reuse.resetInternal(_buf, _position);
        }

        return reuse;
    }

    @Override
    public int getMagic() {
        return 0;
    }

}