All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.je.rep.utilint.BinaryProtocol Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.rep.utilint;

import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_BYTES_READ;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_BYTES_WRITTEN;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_ENTRIES_WRITTEN_OLD_VERSION;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_MESSAGES_BATCHED;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_MESSAGES_READ;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_MESSAGES_WRITTEN;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_MESSAGE_BATCHES;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_READ_NANOS;
import static com.sleepycat.je.rep.utilint.BinaryProtocolStatDefinition.N_WRITE_NANOS;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.StatsConfig;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.dbi.EnvironmentImpl;
import com.sleepycat.je.log.LogUtils;
import com.sleepycat.je.rep.ReplicationConfig;
import com.sleepycat.je.rep.impl.RepParams;
import com.sleepycat.je.rep.impl.node.NameIdPair;
import com.sleepycat.je.utilint.InternalException;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.LongStat;
import com.sleepycat.je.utilint.StatGroup;
import com.sleepycat.je.utilint.VLSN;
import com.sleepycat.utilint.StringUtils;

/**
 * Supplies the basic infrastructure for BinaryProtocols used to exchange
 * messages by the replications stream and during network based restore
 * operations.
 *
 * Note that this class and its subclasses are not synchronized. There must be
 * one instance of this class per thread of control.
 *
 * IMPORTANT: Please avoid all uses of ByteBuffer.get/put when serializing
 * message fields of types: long, int and short to avoid byte order issues.
 * Use LogUtils.read/write methods instead, since they use a single canonical
 * byte-independent representation.
 */
public abstract class BinaryProtocol {

    protected static final int MESSAGE_HEADER_SIZE =
        2 /* Message op id (short) */ +
        4 /* message size (int) */;

    /* Buffer reused to process the header of every message. */
    protected final ByteBuffer header =
        ByteBuffer.allocate((MESSAGE_HEADER_SIZE));

    /* The version as implemented by the actual code. */
    protected final int codeVersion;

    /* The version that this instance is actually configured to use. */
    /* It's not final to facilitate testing */
    protected int configuredVersion;

    /* Identifies the node using this protocol. */
    protected final NameIdPair nameIdPair;

    /* Maps the message op id to its canonical descriptor instance. */
    private final Map ops = new HashMap();
    private final int predefinedMessageCount;

    /* The max message size which will be accepted. */
    private final long maxMessageSize;

    /* Whether to use UTF8 or default encoding for Strings. */
    private final boolean useStringDefaultEncoding;

    /*
     * The predefined messages. IMPORTANT: Note that predefined message ops
     * start at 1000. to stay out of the way of subtype ops.
     */
    public final MessageOp CLIENT_VERSION =
        new MessageOp((short) 1001, ClientVersion.class);

    public final MessageOp SERVER_VERSION =
        new MessageOp((short) 1002, ServerVersion.class);

    public final MessageOp INCOMPATIBLE_VERSION =
        new MessageOp((short) 1003, IncompatibleVersion.class);

    public final MessageOp PROTOCOL_ERROR =
        new MessageOp((short) 1004, ProtocolError.class);

    /* Statistics definition. */
    protected final StatGroup stats;
    protected final LongStat nReadNanos;
    protected final LongStat nWriteNanos;
    protected final LongStat nBytesRead;
    protected final LongStat nMessagesRead;
    protected final LongStat nBytesWritten;
    protected final LongStat nMessagesWritten;
    protected final LongStat nMessagesBatched;
    protected final LongStat nMessageBatches;
    protected final LongStat nEntriesWrittenOldVersion;

    protected final Logger logger;
    protected final Formatter formatter;
    protected final EnvironmentImpl envImpl;

    /**
     * Returns a Protocol object configured that implements the specified
     * (supported) version.
     *
     * @param codeVersion the version actually implemented by the protocol.
     * @param configuredVersion the version of the protocol that must be
     * implemented/simulated by this protocol when communicating with the
     * recipient.
     */
    protected BinaryProtocol(NameIdPair nameIdPair,
                             int codeVersion,
                             int configuredVersion,
                             EnvironmentImpl envImpl) {
        this.nameIdPair = nameIdPair;
        this.codeVersion = codeVersion;
        this.configuredVersion = configuredVersion;
        this.envImpl = envImpl;

        if (envImpl != null) {
            this.logger = LoggerUtils.getLogger(getClass());
        } else {
            this.logger = LoggerUtils.getLoggerFormatterNeeded(getClass());
        }
        this.formatter = new ReplicationFormatter(nameIdPair);

        stats = new StatGroup(BinaryProtocolStatDefinition.GROUP_NAME,
                              BinaryProtocolStatDefinition.GROUP_DESC);
        nReadNanos = new LongStat(stats, N_READ_NANOS);
        nWriteNanos = new LongStat(stats, N_WRITE_NANOS);
        nBytesRead = new LongStat(stats, N_BYTES_READ);
        nMessagesRead = new LongStat(stats, N_MESSAGES_READ);
        nBytesWritten = new LongStat(stats, N_BYTES_WRITTEN);
        nMessagesWritten = new LongStat(stats, N_MESSAGES_WRITTEN);
        nMessagesBatched = new LongStat(stats, N_MESSAGES_BATCHED);
        nMessageBatches = new LongStat(stats, N_MESSAGE_BATCHES);
        nEntriesWrittenOldVersion =
            new LongStat(stats, N_ENTRIES_WRITTEN_OLD_VERSION);

        /* Initialize with the pre-defined protocol messages. */
        for (MessageOp op :
            new MessageOp[] { CLIENT_VERSION,
                              SERVER_VERSION,
                              INCOMPATIBLE_VERSION,
                              PROTOCOL_ERROR }) {

            if (ops.put(op.opId, op) != null) {
                throw EnvironmentFailureException.unexpectedState
                    ("Duplicate op: " + op.opId);
            }
        }
        predefinedMessageCount = ops.size();
        if (envImpl != null) {
            DbConfigManager configManager = envImpl.getConfigManager();
            long mMSz =
                configManager.getLong(RepParams.MAX_MESSAGE_SIZE);
            maxMessageSize = (mMSz == 0) ?
                (envImpl.getMemoryBudget().getMaxMemory() >> 1) :
                mMSz;
            useStringDefaultEncoding = configManager.getBoolean
                (RepParams.PROTOCOL_OLD_STRING_ENCODING);
        } else {
            /* Some unit tests pass in null EnvImpl. */
            maxMessageSize = 1 << 20;
            useStringDefaultEncoding = true;
        }
    }

    /*
     * Must be invoked after the constructor has completed, to get around
     * base/subclass initialization dependences; MessageOps are instances of
     * nested classes declared in the subclass.
     */
    protected void initializeMessageOps(MessageOp[] protocolOps) {

        for (MessageOp op : protocolOps) {
            if (ops.put(op.opId, op) != null) {
                throw EnvironmentFailureException.unexpectedState
                    ("Duplicate op: " + op.opId);
            }
        }
    }

    /*
     * The total number of messages defined by the protocol. Includes messages
     * defined by the subclass.
     */
    public int messageCount() {
        return ops.size();
    }

    /* The messages defined in this class. */
    final public int getPredefinedMessageCount() {
        return predefinedMessageCount;
    }

    /**
     * Returns the version associated with this protocol instance. Request
     * message generated by this instance conform to this version and responses
     * are expected to conform to this version as well.
     *
     * @return the version that is actually being used.
     */
    public int getVersion() {
        return configuredVersion;
    }

    public StatGroup getStats(StatsConfig config) {
        StatGroup ret = stats.cloneGroup(config.getClear());

        return ret;
    }

    public void resetStats() {
        stats.clear();
    }

    /* Messages <= this size will use the shared buffer. */
    private static int CACHED_BUFFER_SIZE = 0x4000;

    /*
     * The shared read and write buffers that are reused. There are
     * two buffers, so that reading and writing can proceed in parallel.
     */
    private final ByteBuffer cachedReadBuffer =
        ByteBuffer.allocate(CACHED_BUFFER_SIZE);

    private final ByteBuffer cachedWriteBuffer =
            ByteBuffer.allocate(CACHED_BUFFER_SIZE);

    /**
     * Returns a read buffer of the requested size.
     *
     * @param size the size of the requested buffer in bytes
     * @return the requested
     */
    private ByteBuffer allocateReadBuffer(int size) {
        if (size <= CACHED_BUFFER_SIZE ) {
            cachedReadBuffer.rewind();
            cachedReadBuffer.limit(size);
            return cachedReadBuffer;
        }
        return ByteBuffer.allocate(size);
    }

    /**
     * Returns a write buffer of the requested size.
     *
     * @param size the size of the requested buffer in bytes
     * @return the requested
     */
    private ByteBuffer allocateWriteBuffer(int size) {
        if(size <= CACHED_BUFFER_SIZE ) {
            cachedWriteBuffer.rewind();
            cachedWriteBuffer.limit(size);
            return cachedWriteBuffer;
        }
        return ByteBuffer.allocate(size);
    }

    /* Returns byte size of serialized string. */
    public int stringSize(String s) {
        return stringToBytes(s).length + 4;
    }

    /* Serialize the string into the buffer. */
    public void putString(String s, ByteBuffer buffer) {
        byte[] b = stringToBytes(s);
        LogUtils.writeInt(buffer, b.length);
        buffer.put(b);
    }

    /**
     * Reconstitutes the string serialized by the above method.
     *
     * @param buffer the buffer containing the string
     *
     * @return the de-serialized string
     */
    public String getString(ByteBuffer buffer) {
        int length = LogUtils.readInt(buffer);
        byte b[] = new byte[length];
        buffer.get(b);
        return bytesToString(b);
    }

    /**
     * Responsible for converting a String to an encoded value for all
     * protocols.
     * 

* In JE 5.0.36 and earlier, only the default encoding was supported. In * later releases, a config param was added to force UTF-8 to be used. In * JE 5.1 and later, the default will be to use UTF-8. [#20967] * * @see ReplicationConfig#PROTOCOL_OLD_STRING_ENCODING */ private byte[] stringToBytes(String s) { if (useStringDefaultEncoding) { return s.getBytes(); } return StringUtils.toUTF8(s); } /** * Responsible for converting an encoded value to a String for all * protocols. * * @see #stringToBytes */ private String bytesToString(byte[] b) { if (useStringDefaultEncoding) { return new String(b); } return StringUtils.fromUTF8(b); } /** * The Operations that are part of the protocol. */ public static class MessageOp { /* The string denoting the operation for the request message. */ private final short opId; /* The class used to represent the message. */ private final Class messageClass; /* The constructor used to create message instances. */ private Constructor constructor; /* The label is used for debugging purposes. */ private final String label; public MessageOp(short opId, Class messageClass) { this.opId = opId; this.messageClass = messageClass; this.label = messageClass.getSimpleName(); try { constructor = messageClass.getConstructor (messageClass.getEnclosingClass(), ByteBuffer.class); } catch (SecurityException e) { throw EnvironmentFailureException.unexpectedException(e); } catch (NoSuchMethodException e) { throw EnvironmentFailureException.unexpectedException(e); } } public short getOpId() { return opId; } Class getMessageClass() { return messageClass; } public Constructor getConstructor() { return constructor; } @Override public String toString() { return label; } } /** * Returns the Op from a message buffer. It's always the first item in the * buffer. Leaves the message buffer positioned after the Op. * * @param messageBuffer a message buffer the the protocol * @return the OpId */ private MessageOp getOp(ByteBuffer messageBuffer) { short opId = LogUtils.readShort(messageBuffer); final MessageOp messageOp = ops.get(opId); if (messageOp == null) { throw EnvironmentFailureException.unexpectedState (envImpl, "Unknown message op id:" + opId + " Known op ids:" + Arrays.toString(ops.keySet().toArray())); } return messageOp; } /* * Used to indicate that an entity is formatable and can be serialized and * de-serialized. */ interface WireFormatable { /* * Returns a ByteBuffer holding the message in a representation * suitable for use in a network transmission. The buffer is flipped * and ready for relative reads. */ abstract ByteBuffer wireFormat(); } /** * Fills a dedicated empty buffer with bytes read from the channel. It * flips the buffer after it has been filled, so it's ready for reading. * * @param channel the channel to be read * @param buffer the buffer to be filled * @throws IOException if the errors were encountered while reading from * the channel or the buffer could not be filled with the expected number * of bytes */ private void fillBuffer(ReadableByteChannel channel, ByteBuffer buffer) throws IOException { final long start = System.nanoTime(); assert(buffer.position() == 0); while (buffer.position() < buffer.limit()) { int numRead = channel.read(buffer); if (numRead <= 0) { throw new IOException("Expected bytes: " + buffer.limit() + " read bytes: " + buffer.position()); } } nReadNanos.add(System.nanoTime() - start); buffer.flip(); } /** * Read and parse an incoming message, specifying the incoming version. * * @param channel the channel to read from. Declared as a * ReadableByteChannel rather than the more obvious SocketChannel to * facilitate unit testing. * * @throws IOException */ public Message read(ReadableByteChannel channel) throws IOException { /* Get the message header. */ fillBuffer(channel, header); /* Use the type value to determine the message type. */ MessageOp op = getOp(header); try { Constructor cons = op.getConstructor(); /* Read the size to determine the body of the message. */ int messageBodySize = LogUtils.readInt(header); nBytesRead.add(MESSAGE_HEADER_SIZE + messageBodySize); nMessagesRead.increment(); if (messageBodySize > 0) { if (messageBodySize > maxMessageSize) { throw EnvironmentFailureException.unexpectedState ("Message op: " + op + " Body size: " + messageBodySize + " is too large. maxSizeAllowed: " + maxMessageSize + "\nIf a larger value is needed, set the " + "'je.rep.maxMessageSize' parameter."); } ByteBuffer body = allocateReadBuffer(messageBodySize); fillBuffer(channel, body); return cons.newInstance(this, body); } if (messageBodySize < 0) { throw EnvironmentFailureException.unexpectedState ("Message op: " + op + " Body size: " + messageBodySize); } /* No body */ return cons.newInstance(this, null); } catch (InstantiationException e) { throw EnvironmentFailureException.unexpectedException(e); } catch (IllegalAccessException e) { throw EnvironmentFailureException.unexpectedException(e); } catch (SecurityException e) { throw EnvironmentFailureException.unexpectedException(e); } catch (InvocationTargetException e) { throw EnvironmentFailureException.unexpectedException(e); } finally { /* The header buffer will be reused, so clear it. */ header.clear(); } } @SuppressWarnings("unchecked") public T read(ReadableByteChannel channel, Class cl) throws IOException, ProtocolException { Message message = read(channel); /* * Note: the subclassing check instead of an equality check makes it * convenient to deal with responses when there can be multiple * possible but related responses. */ if (cl.isAssignableFrom(message.getClass())) { return (T)message; } throw new ProtocolException(message, cl); } /** * Write a message out to a channel. * @throws IOException */ public void write(Message message, NamedChannel namedChannel) throws IOException { write(message, namedChannel, namedChannel.getNameIdPair()); } /** * Write a message out to a channel. * @throws IOException */ public void write(Message message, WritableByteChannel channel) throws IOException { write(message, channel, NameIdPair.NULL); } /** * Write a message out to a channel. * @throws IOException */ private void write(Message message, WritableByteChannel channel, NameIdPair destinationNameIdPair) throws IOException { final ByteBuffer messageBuffer = message.wireFormat(); nMessagesWritten.increment(); flushBuffer(channel, messageBuffer); if (logger.isLoggable(Level.FINER)) { if (destinationNameIdPair == NameIdPair.NULL) { /* No nameIdPair was supplied, so use the channel. */ LoggerUtils.logMsg(logger, envImpl, formatter, Level.FINER, "Sent " + message + " to " + channel); } else { LoggerUtils.logMsg(logger, envImpl, formatter, Level.FINER, "Sent to " + destinationNameIdPair.getName() + ": "+ message); } } /* Rewind the message buffer in case it's a reusable wire format */ messageBuffer.rewind(); } /** * Buffers the serialized form of the message (if possible) for later * writes to the network. * * If buffering the message would result in an overflow, the current * contents of the buffer are flushed before the message is added to the * buffer. If the size of the message exceeds the size of the buffer, the * message is flushed directly to the network. * * @param channel the channel to which the buffer is flushed on buffer * overflows. * * @param batchWriteBuffer the buffer accumulating the serialized messages * It's best for performance if this is a direct byte buffer, to avoid * another copy when the buffer is finally flushed. * * @param nMessages the number of messages currently in the buffer, * including this message, that is, nMessages is always > 0 upon entry. * * @param message the message to be buffered. * * @return the number of messages currently in the buffer after accounting * for potential buffer flushes. */ public int bufferWrite(WritableByteChannel channel, ByteBuffer batchWriteBuffer, int nMessages, Message message) throws IOException { assert nMessages > 0 ; final ByteBuffer messageBuffer = message.wireFormat(); if (batchWriteBuffer.remaining() < messageBuffer.limit()) { flushBufferedWrites(channel, batchWriteBuffer, nMessages - 1); /* 1 for the message we add below. */ nMessages = 1; if (batchWriteBuffer.remaining() < messageBuffer.limit()) { /* * Buffer is too small for message, so write it directly. * This write must always be preceded by a buffer flush. */ assert batchWriteBuffer.position() == 0 ; nMessagesWritten.increment(); flushBuffer(channel, messageBuffer); nMessages = 0; return nMessages; } } batchWriteBuffer.put(messageBuffer); return nMessages; } /** * Flush all the messages accumulated by earlier calls to bufferWrite * * @param channel the channel to which the buffer is flushed. * * @param batchWriteBuffer the buffer containing the accumulated messages * * @param nMessages the number of messages in the batch */ public void flushBufferedWrites(WritableByteChannel channel, ByteBuffer batchWriteBuffer, int nMessages) throws IOException { nMessagesWritten.add(nMessages); if (nMessages > 1) { nMessagesBatched.add(nMessages); nMessageBatches.increment(); } batchWriteBuffer.flip(); flushBuffer(channel, batchWriteBuffer); batchWriteBuffer.clear(); return; } /** * Writes the entire contents of the buffer to the blocking channel. */ private void flushBuffer(WritableByteChannel channel, ByteBuffer bb) throws IOException { assert bb.position() == 0; if (bb.limit() == 0) { return; } final long start = System.nanoTime(); /** * Even though the underlying channel is a blocking channel, there are * circumstances where the channel.write() returns without writing all * of the buffer, in seeming contradiction to the javadoc. One such * example is when the peer fails in the middle of a write; in this * case there is a "successful" partial write and the next write will * result in an I/O exception. There may be other such examples as * well, hence the defensive write loop below which tries to continue * writes to their logical conclusion or to an IOE. */ while (bb.remaining() > 0) { int bytes = channel.write(bb); nBytesWritten.add(bytes); if (bytes == 0) { /* * This should not happen since it's a blocking channel, but * the java doc is vague on this subject, so yield control if * we are not making progress. */ Thread.yield(); } } nWriteNanos.add(System.nanoTime() - start); } /** * Base message class for all messages exchanged in the protocol. * Serialized layout of a message: - opType (short) - size of the body of * the message (int) - body of the message - message specific fields *

* All non-abstract subclasses must implement a constructor with a * ByteBuffer argument. This constructor is used during serialization to * recreate the Message instance. It's considered good practice to declare * all attributes of a message as final. It's a simple way to ensure that * the above constructor has initialized all the attributes of the message. */ public abstract class Message implements WireFormatable { public abstract MessageOp getOp(); /** * The default message consists of the operation enum and just a 0 * length size. */ @Override public ByteBuffer wireFormat() { ByteBuffer messageBuffer = allocateInitializedBuffer(0); messageBuffer.flip(); return messageBuffer; } @Override public String toString() { return getOp().toString(); } /* * For unit test support, so we can compare a message created for * sending against a message received. Some message types need to * override. */ public boolean match(Message other) { return Arrays.equals(wireFormat().array().clone(), other.wireFormat().array().clone()); } /** * Allocate a buffer for the message with the header initialized. * * @param size size of the message contents following the buffer * * @return the initialized buffer */ public ByteBuffer allocateInitializedBuffer(int size) { ByteBuffer messageBuffer = allocateWriteBuffer(MESSAGE_HEADER_SIZE + size); LogUtils.writeShort(messageBuffer, getOp().getOpId()); LogUtils.writeInt(messageBuffer, size); return messageBuffer; } } /** * Base class for simple messages. Ones where performance is not of the * utmost importance and reflection can be used to simplify message * serialization and de-serialization. */ protected abstract class SimpleMessage extends Message { /** * Assembles a sequence of arguments into its byte based wire format. * The method does the serialization in two steps. In step 1, it * calculates the length of the buffer. In step 2, it assembles the * bytes into the allocated buffer. The interpretive approach used here * is suitable for the low performance requirements of the handshake * protocol, but not for the core data stream itself. It's for this * reason that the method is associated with the Handshake message * class and not the Message class. * * @param arguments the arguments to be passed in the message * * @return a byte buffer containing the serialized form of the message */ protected ByteBuffer wireFormat(Object... arguments) { int size = 0; for (final Object obj : arguments) { size += wireFormatSize(obj); } /* Allocate the buffer and fill it up */ final ByteBuffer buffer = allocateInitializedBuffer(size); for (final Object obj : arguments) { putWireFormat(buffer, obj); } buffer.flip(); return buffer; } /** * Put the bytes of the wire format for the object into the current * position in the buffer. * * @param buffer the buffer * @param obj the object * @throws EnvironmentFailureException if the object is not supported */ protected void putWireFormat(final ByteBuffer buffer, final Object obj) { final Class cl = obj.getClass(); if (cl == Long.class) { LogUtils.writeLong(buffer, ((Long)obj).longValue()); } else if (cl == Integer.class) { LogUtils.writeInt(buffer, ((Integer)obj).intValue()); } else if (cl == Short.class) { LogUtils.writeShort(buffer, ((Short)obj).shortValue()); } else if (cl == Byte.class) { buffer.put(((Byte)obj).byteValue()); } else if (cl == Boolean.class) { buffer.put(((Boolean)obj).booleanValue() ? (byte)1 : (byte)0); } else if (cl == VLSN.class) { LogUtils.writeLong(buffer, ((VLSN)obj).getSequence()); } else if (Enum.class.isAssignableFrom(cl)) { /* An enum is stored as it's identifier string. */ Enum e = (Enum)obj; putString(e.name(), buffer); } else if (cl == String.class) { /* * A string is stored with its length followed by its * contents. */ putString((String)obj, buffer); } else if (cl == Double.class) { /* Treat a Double as a String. */ putString(((Double) obj).toString(), buffer); } else if (cl == String[].class) { String sa[] = (String[])obj; LogUtils.writeInt(buffer, sa.length); for (String element : sa) { putString(element, buffer); } } else if (cl == byte[].class) { putByteArray(buffer, (byte[])obj); } else if (obj instanceof NameIdPair) { /* instanceof used to accomodate ReadOnlyNameIdPair. */ ((NameIdPair) obj).serialize(buffer, BinaryProtocol.this); } else { throw EnvironmentFailureException.unexpectedState( "Unknown type: " + cl); } } /** * Put the bytes for the wire format of the specified byte array into * the current position in the buffer. * * @param buffer the buffer * @param ba the byte array */ protected void putByteArray(ByteBuffer buffer, byte[] ba) { LogUtils.writeInt(buffer, ba.length); buffer.put(ba); } /** * Return the wire format size of the specified object. * * @param obj the object * @return the size * @throws EnvironmentFailureException if the object is not supported */ protected int wireFormatSize(final Object obj) { final Class cl = obj.getClass(); if (cl == Long.class) { return 8; } else if (cl == Integer.class) { return 4; } else if (cl == Short.class) { return 2; } else if (cl == Byte.class) { return 1; } else if (cl == Boolean.class) { return 1; } else if (cl == VLSN.class) { return 8; } else if (Enum.class.isAssignableFrom(cl)) { return stringSize(((Enum)obj).name()); } else if (cl == String.class) { return stringSize((String)obj); } else if (cl == Double.class) { return stringSize(((Double) obj).toString()); } else if (cl == String[].class) { int size = 4; /* array length */ final String sa[] = (String[])obj; for (String element : sa) { size += stringSize(element); } return size; } else if (cl == byte[].class) { return 4 + ((byte[])obj).length; } else if (obj instanceof NameIdPair) { /* instanceof used to accomodate ReadOnlyNameIdPair. */ return ((NameIdPair) obj).serializedSize(BinaryProtocol.this); } else { throw EnvironmentFailureException.unexpectedState ("Unknown type: " + cl); } } /** * Reconstitutes an array of strings. * @param buffer * @return */ protected String[] getStringArray(ByteBuffer buffer) { String sa[] = new String[LogUtils.readInt(buffer)]; for (int i=0; i < sa.length; i++) { sa[i] = getString(buffer); } return sa; } protected byte[] getByteArray(ByteBuffer buffer) { byte ba[] = new byte[LogUtils.readInt(buffer)]; buffer.get(ba); return ba; } protected boolean getBoolean(ByteBuffer buffer) { byte b = buffer.get(); if (b == 0) { return false; } else if (b == 1) { return true; } else { throw EnvironmentFailureException.unexpectedState ("Unknown boolean value: " + b); } } protected VLSN getVLSN(ByteBuffer buffer) { long vlsn = LogUtils.readLong(buffer); return (vlsn == VLSN.NULL_VLSN_SEQUENCE) ? VLSN.NULL_VLSN : new VLSN(vlsn); } protected > T getEnum(Class enumType, ByteBuffer buffer) { String enumName = getString(buffer); return Enum.valueOf(enumType, enumName); } protected Double getDouble(ByteBuffer buffer) { return new Double(getString(buffer)); } protected NameIdPair getNameIdPair(ByteBuffer buffer) { return NameIdPair.deserialize(buffer, BinaryProtocol.this); } } /** * The base class for reject responses to requests */ public abstract class RejectMessage extends SimpleMessage { /* Must not be null */ protected String errorMessage; protected RejectMessage(String errorMessage) { super(); /* * Replace the null message with an empty string since the Simple * Message assumes non-null contents. */ if (errorMessage == null) { this.errorMessage = " "; } else { this.errorMessage = errorMessage; } } @Override public ByteBuffer wireFormat() { return wireFormat(errorMessage); } public RejectMessage(ByteBuffer buffer) { errorMessage = getString(buffer); } public String getErrorMessage() { return errorMessage; } @Override public String toString() { return errorMessage; } } public class ProtocolError extends RejectMessage { public ProtocolError(String errorMessage) { super(errorMessage); } public ProtocolError(ByteBuffer buffer) { super(buffer); } @Override public MessageOp getOp() { return PROTOCOL_ERROR; } } /** * Version broadcasts the sending node's protocol version. */ abstract class ProtocolVersion extends SimpleMessage { private final int version; private final int nodeId; public ProtocolVersion(int version) { super(); this.version = version; this.nodeId = BinaryProtocol.this.nameIdPair.getId(); } @Override public ByteBuffer wireFormat() { return wireFormat(version, nodeId); } public ProtocolVersion(ByteBuffer buffer) { version = LogUtils.readInt(buffer); nodeId = LogUtils.readInt(buffer); } /** * @return the version */ public int getVersion() { return version; } /** * The nodeId of the sender * * @return nodeId */ public int getNodeId() { return nodeId; } } public class ClientVersion extends ProtocolVersion { public ClientVersion() { super(codeVersion); } public ClientVersion(ByteBuffer buffer) { super(buffer); } @Override public MessageOp getOp() { return CLIENT_VERSION; } } public class ServerVersion extends ProtocolVersion { public ServerVersion() { super(codeVersion); } public ServerVersion(ByteBuffer buffer) { super(buffer); } @Override public MessageOp getOp() { return SERVER_VERSION; } } public class IncompatibleVersion extends RejectMessage { public IncompatibleVersion(String message) { super(message); } public IncompatibleVersion(ByteBuffer buffer) { super(buffer); } @Override public MessageOp getOp() { return INCOMPATIBLE_VERSION; } } /** * Thrown in response to an unexpected response to a request. */ @SuppressWarnings("serial") static public class ProtocolException extends InternalException { private final Message unexpectedMessage; private final Class cl; /** * Constructor used for message sequencing errors. */ public ProtocolException(Message unexpectedMessage, Class cl) { super(); this.unexpectedMessage = unexpectedMessage; this.cl = cl; } public ProtocolException(String message) { super(message); this.unexpectedMessage = null; this.cl = null; } @Override public String getMessage() { return (unexpectedMessage != null) ? ("Expected message type: " + cl + " but found: " + unexpectedMessage.getClass() + /* * Include information about the message, which is * particularly useful for a RejectMessage */ ": " + unexpectedMessage) : super.getMessage(); } public Message getUnexpectedMessage() { return unexpectedMessage; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy