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

Java tutorial

Introduction

Here is the source code for com.linkedin.databus.core.DbusEventPart.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.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.apache.commons.codec.binary.Hex;
import org.codehaus.jackson.JsonGenerator;
import com.linkedin.databus.core.DbusEvent.SchemaDigestType;
import com.linkedin.databus.core.util.Base64;
import com.linkedin.databus.core.util.Utils;

/**
 * This class holds fields from a portion of the DbusEvent.
 * DbusEventV2 carries key, metadata, and payload components, each having
 * the following attributes:
 * <pre>
 * - Schema digest type (MD5 or CRC-32)
 * - Schema digest
 * - Schema version
 * - Data blob (meaning the actual key, metadata, or payload)
 * </pre>
 * See wiki: Databus+Event+enhancement+%28Multi-Colo%29 for event layout.
 */
public class DbusEventPart {
    private static final short SCHEMA_DIGEST_TYPE_MD5 = 0;
    private static final short SCHEMA_DIGEST_TYPE_CRC32 = 1;
    private static final short VERSION_SHIFT = 2;
    private static final short DIGEST_MASK = 0x3;
    private static final int AttributesOffset = 4;
    private static final int AttributesLen = 2;
    private static final int MAX_DATA_BYTES_PRINTED = 64;

    private final SchemaDigestType _schemaDigestType;
    private final byte[] _schemaDigest;
    private final short _schemaVersion;
    private ByteBuffer _data = null;

    public DbusEventPart(SchemaDigestType schemaDigestType, byte[] schemaDigest, short schemaVersion,
            ByteBuffer data) {
        _schemaDigestType = schemaDigestType;
        _schemaDigest = schemaDigest.clone();
        _schemaVersion = schemaVersion;
        _data = data;
        switch (_schemaDigestType) {
        case MD5:
            if (_schemaDigest.length != DbusEvent.MD5_DIGEST_LEN) {
                throw new DatabusRuntimeException("Invalid MD5 schema digest length:" + _schemaDigest.length);
            }
            break;
        case CRC32:
            if (_schemaDigest.length != DbusEvent.CRC32_DIGEST_LEN) {
                throw new DatabusRuntimeException("Invalid CRC-32 schema digest length:" + _schemaDigest.length);
            }
            break;
        default:
            throw new UnsupportedOperationException("Unsupported schema digest type:" + _schemaDigestType);
        }
    }

    public SchemaDigestType getSchemaDigestType() {
        return _schemaDigestType;
    }

    public byte[] getSchemaDigest() {
        return _schemaDigest.clone();
    }

    public short getSchemaVersion() {
        return _schemaVersion;
    }

    /**
     * Returns the actual data blob of the event-part.  The returned ByteBuffer is
     * read-only, and its position and limit may be freely modified.
     * <b>NOTE: The data may be subsequently overwritten; if you need it beyond the
     * onDataEvent() call, save your own copy before returning.</b>
     */
    public ByteBuffer getData() {
        return _data.asReadOnlyBuffer().slice().order(_data.order());
    }

    /** Returns number of bytes remaining. */ // TODO:  make this public?
    private int getDataLength() {
        return _data.limit() - _data.position(); // why not just _data.remaining() ?  ("elements" here are always bytes)
    }

    public void encode(ByteBuffer buf) {
        int curPos = _data.position();
        buf.putInt(getDataLength());
        short attributes = 0;
        switch (_schemaDigestType) {
        case MD5:
            attributes = SCHEMA_DIGEST_TYPE_MD5;
            break;
        case CRC32:
            attributes = SCHEMA_DIGEST_TYPE_CRC32;
            break;
        default:
            throw new UnsupportedOperationException("Unsupported schema digest type:" + _schemaDigestType);
        }
        attributes |= (_schemaVersion << VERSION_SHIFT);
        buf.putShort(attributes);
        buf.put(_schemaDigest);
        buf.put(_data);
        _data.position(curPos);
    }

    private static SchemaDigestType digestType(short attributes) {
        short digestType = (short) (attributes & DIGEST_MASK);
        switch (digestType) {
        case SCHEMA_DIGEST_TYPE_CRC32:
            return SchemaDigestType.CRC32;
        case SCHEMA_DIGEST_TYPE_MD5:
            return SchemaDigestType.MD5;
        default:
            throw new UnsupportedOperationException("Digest type " + digestType + " not supported");
        }
    }

    private static int digestLen(SchemaDigestType digestType) {
        switch (digestType) {
        case MD5:
            return DbusEvent.MD5_DIGEST_LEN;
        case CRC32:
            return DbusEvent.CRC32_DIGEST_LEN;
        default:
            throw new UnsupportedOperationException("Digest type " + digestType + " not supported");
        }
    }

    /**
     * Decodes a bytebuffer and returns DbusEventPart. Preserves the ByteBuffer position.
     * @param buf
     * @return
     */
    public static DbusEventPart decode(ByteBuffer buf) {
        int pos = buf.position();
        int dataLen = buf.getInt(pos);
        if (dataLen < 0) {
            throw new UnsupportedOperationException("Data length " + dataLen + " not supported");
        }
        short attributes = buf.getShort(pos + AttributesOffset);
        short schemaVersion = (short) (attributes >> VERSION_SHIFT);
        SchemaDigestType schemaDigestType = digestType(attributes);
        int digestLen = digestLen(schemaDigestType);
        byte[] digest = new byte[digestLen];
        for (int i = 0; i < digestLen; i++) {
            digest[i] = buf.get(pos + AttributesOffset + AttributesLen + i);
        }

        // NOTE - this will create a new ByteBuffer object pointing to the
        // same memory. So the position of this new BB is beginning of the data
        // and limit is set to the end of the data.
        ByteBuffer dataBuf = buf.asReadOnlyBuffer();
        dataBuf.position(pos + AttributesOffset + AttributesLen + digestLen);
        dataBuf.limit(dataBuf.position() + dataLen);

        return new DbusEventPart(schemaDigestType, digest, schemaVersion, dataBuf);
    }

    public static int computePartLength(SchemaDigestType digestType, int dataLen) {
        return AttributesOffset + AttributesLen + dataLen + digestLen(digestType);
    }

    /**
     * Replace the schema-digest in a serialized DbusEventPart.
     *
     * @param buf The buffer that contains the serialized DbusEventPart.
     * @param position the position in the buffer where the DbusEventPart starts.
     * @param schemaDigest The digest value to substitute. The value must match in length to the existing value.
     */
    public static void replaceSchemaDigest(ByteBuffer buf, int position, byte[] schemaDigest) {
        DbusEvent.SchemaDigestType digestType = digestType(buf.getShort(position + AttributesOffset));
        int digestLen = digestLen(digestType);
        if (schemaDigest.length != digestLen) {
            throw new RuntimeException(
                    "Expecting length " + digestLen + " for type " + digestType + ", found " + schemaDigest.length);
        }
        for (int i = 0; i < digestLen; i++) {
            buf.put(position + AttributesOffset + AttributesLen + i, schemaDigest[i]);
        }
    }

    public void printString(String prefix, JsonGenerator g, Encoding encoding) throws IOException {
        g.writeNumberField(prefix + "Length", getDataLength());
        g.writeNumberField(prefix + "SchemaVersion", getSchemaVersion());
        //This is really the payload schema digest, but for legacy reasons we have to call it schemaid
        g.writeStringField(prefix + "SchemaId", Base64.encodeBytes(getSchemaDigest()));
        //This is really the payload but for historical reasons we have to call it value

        if (encoding.equals(Encoding.JSON)) {
            g.writeStringField(prefix + "Value", Base64.encodeBytes(Utils.byteBufferToBytes(getData())));
        } else {
            g.writeStringField(prefix + "Value", Utils.byteBufferToString(getData()));
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(64);
        sb.append("Length=").append(getDataLength()).append(";SchemaVersion=").append(getSchemaVersion())
                .append(";SchemaId=").append("0x").append(Hex.encodeHexString(getSchemaDigest()));

        ByteBuffer dataBB = getData();
        if (dataBB.remaining() > MAX_DATA_BYTES_PRINTED) {
            dataBB.limit(MAX_DATA_BYTES_PRINTED);
        }
        byte[] data = new byte[dataBB.remaining()];
        dataBB.get(data);
        sb.append(";Value=").append("0x").append(Hex.encodeHexString(data));
        return sb.toString();
    }

    /**
     * @return The number of bytes that this DbusEventPart would take up, if it were to be serialized.
     */
    public int computePartLength() {
        return AttributesOffset + AttributesLen + _data.remaining() + _schemaDigest.length;
    }

    /**
     * @return the length of the DbusEventPart that is encoded in 'buf' at position 'position'.
     * Callers can use this method to advance across the DbusEventPart in a serialized V2 event.
     */
    public static int partLength(ByteBuffer buf, int position) {
        DbusEvent.SchemaDigestType digestType = digestType(buf.getShort(position + AttributesOffset));
        int digestLen = digestLen(digestType);
        return AttributesOffset + AttributesLen + digestLen + buf.getInt(position);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        DbusEventPart other = (DbusEventPart) obj;
        if (_schemaVersion != other._schemaVersion) {
            return false;
        }
        if (_schemaDigestType != other._schemaDigestType) {
            return false;
        }
        if (_schemaDigest == null && other._schemaDigest != null
                || _schemaDigest != null && other._schemaDigest == null) {
            return false;
        }
        if (!Arrays.equals(_schemaDigest, other._schemaDigest)) {
            return false;
        }

        if (_data == null && other._data != null || _data != null && other._data == null) {
            return false;
        }
        if (!_data.equals(other._data)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + (_schemaDigest == null ? 0 : Arrays.hashCode(_schemaDigest));
        result = prime * result + (_data == null ? 0 : _data.hashCode());
        return result;
    }
}