oracle.nosql.driver.ops.serde.BinaryProtocol Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nosqldriver Show documentation
Show all versions of nosqldriver Show documentation
Java examples for Oracle NoSQL Database
/*-
* Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* https://oss.oracle.com/licenses/upl/
*/
package oracle.nosql.driver.ops.serde;
import static oracle.nosql.driver.http.Client.trace;
import static oracle.nosql.driver.util.BinaryProtocol.ABSOLUTE;
import static oracle.nosql.driver.util.BinaryProtocol.ACTIVE;
import static oracle.nosql.driver.util.BinaryProtocol.BAD_PROTOCOL_MESSAGE;
import static oracle.nosql.driver.util.BinaryProtocol.BATCH_OP_NUMBER_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.BATCH_REQUEST_SIZE_LIMIT;
import static oracle.nosql.driver.util.BinaryProtocol.CREATING;
import static oracle.nosql.driver.util.BinaryProtocol.COMPLETE;
import static oracle.nosql.driver.util.BinaryProtocol.DROPPED;
import static oracle.nosql.driver.util.BinaryProtocol.DROPPING;
import static oracle.nosql.driver.util.BinaryProtocol.EVENTUAL;
import static oracle.nosql.driver.util.BinaryProtocol.EVOLUTION_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.ILLEGAL_ARGUMENT;
import static oracle.nosql.driver.util.BinaryProtocol.ILLEGAL_STATE;
import static oracle.nosql.driver.util.BinaryProtocol.INDEX_EXISTS;
import static oracle.nosql.driver.util.BinaryProtocol.INDEX_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.INDEX_NOT_FOUND;
import static oracle.nosql.driver.util.BinaryProtocol.INSUFFICIENT_PERMISSION;
import static oracle.nosql.driver.util.BinaryProtocol.INVALID_AUTHORIZATION;
import static oracle.nosql.driver.util.BinaryProtocol.KEY_SIZE_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.OPERATION_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.OPERATION_NOT_SUPPORTED;
import static oracle.nosql.driver.util.BinaryProtocol.READ_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.REQUEST_SIZE_LIMIT;
import static oracle.nosql.driver.util.BinaryProtocol.REQUEST_SIZE_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.REQUEST_TIMEOUT;
import static oracle.nosql.driver.util.BinaryProtocol.RESOURCE_EXISTS;
import static oracle.nosql.driver.util.BinaryProtocol.RESOURCE_NOT_FOUND;
import static oracle.nosql.driver.util.BinaryProtocol.RETRY_AUTHENTICATION;
import static oracle.nosql.driver.util.BinaryProtocol.ROW_SIZE_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.SECURITY_INFO_UNAVAILABLE;
import static oracle.nosql.driver.util.BinaryProtocol.SERIAL_VERSION;
import static oracle.nosql.driver.util.BinaryProtocol.SERVER_ERROR;
import static oracle.nosql.driver.util.BinaryProtocol.SERVICE_UNAVAILABLE;
import static oracle.nosql.driver.util.BinaryProtocol.SIZE_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.TABLE_DEPLOYMENT_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.TABLE_EXISTS;
import static oracle.nosql.driver.util.BinaryProtocol.TABLE_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.TABLE_NOT_FOUND;
import static oracle.nosql.driver.util.BinaryProtocol.TENANT_DEPLOYMENT_LIMIT_EXCEEDED;
import static oracle.nosql.driver.util.BinaryProtocol.TTL_DAYS;
import static oracle.nosql.driver.util.BinaryProtocol.TTL_HOURS;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_ARRAY;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_BINARY;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_BOOLEAN;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_DOUBLE;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_EMPTY;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_INTEGER;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_JSON_NULL;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_LONG;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_MAP;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_NULL;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_NUMBER;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_STRING;
import static oracle.nosql.driver.util.BinaryProtocol.TYPE_TIMESTAMP;
import static oracle.nosql.driver.util.BinaryProtocol.UNKNOWN_ERROR;
import static oracle.nosql.driver.util.BinaryProtocol.UNKNOWN_OPERATION;
import static oracle.nosql.driver.util.BinaryProtocol.UPDATING;
import static oracle.nosql.driver.util.BinaryProtocol.WORKING;
import static oracle.nosql.driver.util.BinaryProtocol.WRITE_LIMIT_EXCEEDED;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Stack;
import oracle.nosql.driver.BatchOperationNumberLimitException;
import oracle.nosql.driver.Consistency;
import oracle.nosql.driver.DeploymentException;
import oracle.nosql.driver.EvolutionLimitException;
import oracle.nosql.driver.FieldRange;
import oracle.nosql.driver.IndexExistsException;
import oracle.nosql.driver.IndexLimitException;
import oracle.nosql.driver.IndexNotFoundException;
import oracle.nosql.driver.InvalidAuthorizationException;
import oracle.nosql.driver.KeySizeLimitException;
import oracle.nosql.driver.NoSQLException;
import oracle.nosql.driver.OperationNotSupportedException;
import oracle.nosql.driver.OperationThrottlingException;
import oracle.nosql.driver.ReadThrottlingException;
import oracle.nosql.driver.RequestSizeLimitException;
import oracle.nosql.driver.RequestTimeoutException;
import oracle.nosql.driver.ResourceExistsException;
import oracle.nosql.driver.ResourceNotFoundException;
import oracle.nosql.driver.RowSizeLimitException;
import oracle.nosql.driver.SecurityInfoNotReadyException;
import oracle.nosql.driver.SystemException;
import oracle.nosql.driver.TableExistsException;
import oracle.nosql.driver.TableLimitException;
import oracle.nosql.driver.TableNotFoundException;
import oracle.nosql.driver.TableSizeException;
import oracle.nosql.driver.TimeToLive;
import oracle.nosql.driver.UnauthorizedException;
import oracle.nosql.driver.Version;
import oracle.nosql.driver.WriteThrottlingException;
import oracle.nosql.driver.kv.AuthenticationException;
import oracle.nosql.driver.ops.SystemResult;
import oracle.nosql.driver.ops.PutResult;
import oracle.nosql.driver.ops.ReadRequest;
import oracle.nosql.driver.ops.Request;
import oracle.nosql.driver.ops.Result;
import oracle.nosql.driver.ops.TableLimits;
import oracle.nosql.driver.ops.TableResult;
import oracle.nosql.driver.ops.WriteMultipleRequest;
import oracle.nosql.driver.ops.WriteRequest;
import oracle.nosql.driver.ops.WriteResult;
import oracle.nosql.driver.query.TopologyInfo;
import oracle.nosql.driver.values.ArrayValue;
import oracle.nosql.driver.values.BinaryValue;
import oracle.nosql.driver.values.BooleanValue;
import oracle.nosql.driver.values.DoubleValue;
import oracle.nosql.driver.values.EmptyValue;
import oracle.nosql.driver.values.FieldValue;
import oracle.nosql.driver.values.FieldValueEventHandler;
import oracle.nosql.driver.values.IntegerValue;
import oracle.nosql.driver.values.JsonNullValue;
import oracle.nosql.driver.values.LongValue;
import oracle.nosql.driver.values.MapValue;
import oracle.nosql.driver.values.NullValue;
import oracle.nosql.driver.values.NumberValue;
import oracle.nosql.driver.values.StringValue;
import oracle.nosql.driver.values.TimestampValue;
import oracle.nosql.driver.util.BinaryProtocol.OpCode;
import oracle.nosql.driver.util.ByteInputStream;
import oracle.nosql.driver.util.ByteOutputStream;
import oracle.nosql.driver.util.SerializationUtil;
/**
* @hidden
* A base class for binary protocol serialization and constant protocol values.
* Constants are used instead of relying on ordering of values in enumerations
* or other derived protocol state.
*/
public class BinaryProtocol {
/**
* Serial version of the protocol
* @return the version
*/
public static short getSerialVersion() {
return SERIAL_VERSION;
}
/*
* Serialization
*/
/*
* Primitives
*/
static void writeInt(ByteOutputStream out,
int value) throws IOException {
SerializationUtil.writePackedInt(out, value);
}
static void writeLong(ByteOutputStream out,
long value) throws IOException {
SerializationUtil.writePackedLong(out, value);
}
static void writeDouble(ByteOutputStream out,
double value) throws IOException {
out.writeDouble(value);
}
static void writeTimestamp(ByteOutputStream out,
TimestampValue value) throws IOException {
/* TODO, temporary */
writeString(out, value.getString());
}
static void writeString(ByteOutputStream out,
String s) throws IOException {
SerializationUtil.writeString(out, s);
}
/**
* Writes a char array as a UTF8 byte array. This is used for
* system queries that may contain a password.
*/
static void writeCharArrayAsUTF8(ByteOutputStream out,
char [] chars) throws IOException {
ByteBuffer buf = StandardCharsets.UTF_8.encode(CharBuffer.wrap(chars));
byte[] array = new byte[buf.limit()];
buf.get(array);
writeByteArray(out, array);
}
static void writeTimeout(ByteOutputStream out, int timeout)
throws IOException {
SerializationUtil.writePackedInt(out, timeout);
}
static void writeConsistency(ByteOutputStream out,
Consistency consistency)
throws IOException {
out.writeByte(getConsistency(consistency));
}
static void writeTTL(ByteOutputStream out, TimeToLive ttl)
throws IOException {
if (ttl == null) {
writeLong(out, -1);
return;
}
writeLong(out, ttl.getValue());
if (ttl.unitIsDays()) {
out.writeByte(TTL_DAYS);
} else if (ttl.unitIsHours()) {
out.writeByte(TTL_HOURS);
} else {
throw new IllegalStateException("Invalid TTL unit in ttl " + ttl);
}
}
public static int getSerializedSize(FieldValue value) {
ByteOutputStream out = null;
try {
out = ByteOutputStream.createByteOutputStream();
writeFieldValue(out, value);
int ret = out.getOffset();
return ret;
} catch (IOException ioe) {
throw new IllegalArgumentException(
"Can't serialize field value: " + ioe.getMessage());
} finally {
if (out != null) {
out.releaseByteBuf();
}
}
}
/**
* Writes the (short) serial version
* @param out output
* @throws IOException if exception
*/
public static void writeSerialVersion(ByteOutputStream out)
throws IOException {
out.writeShort(getSerialVersion());
}
/**
* Writes the opcode for the operation.
* @param out output
* @param op opCode
* @throws IOException if exception
*/
static void writeOpCode(ByteOutputStream out, OpCode op)
throws IOException {
out.writeByte(op.ordinal());
}
static void writeFieldRange(ByteOutputStream out,
FieldRange range)
throws IOException {
if (range == null) {
out.writeBoolean(false);
return;
}
out.writeBoolean(true);
writeString(out, range.getFieldPath());
if (range.getStart() != null) {
out.writeBoolean(true);
writeFieldValue(out, range.getStart());
out.writeBoolean(range.getStartInclusive());
} else {
out.writeBoolean(false);
}
if (range.getEnd() != null) {
out.writeBoolean(true);
writeFieldValue(out, range.getEnd());
out.writeBoolean(range.getEndInclusive());
} else {
out.writeBoolean(false);
}
}
/*
* Serialize a generic FieldValue into the output stream
*/
public static void writeFieldValue(ByteOutputStream out,
FieldValue value)
throws IOException {
BinarySerializer bs = new BinarySerializer(out);
FieldValueEventHandler.generate(value, bs);
}
public static void writeVersion(ByteOutputStream out,
Version version) throws IOException {
SerializationUtil.writeNonNullByteArray(out, version.getBytes());
}
public static void writeByteArray(ByteOutputStream out,
byte[] array) throws IOException {
SerializationUtil.writeByteArray(out, array);
}
/*
* Writes a byte array with a full 4-byte int length
*/
public static void writeByteArrayWithInt(ByteOutputStream out,
byte[] array) throws IOException {
out.writeInt(array.length);
out.write(array);
}
/*
* Writes fields from ReadRequest
*/
static void serializeReadRequest(ReadRequest readRq,
ByteOutputStream out)
throws IOException {
serializeRequest(readRq, out);
writeString(out, readRq.getTableName());
writeConsistency(out, readRq.getConsistencyInternal());
}
/*
* Writes fields from WriteRequest
*/
static void serializeWriteRequest(WriteRequest writeRq,
ByteOutputStream out)
throws IOException {
serializeRequest(writeRq, out);
writeString(out, writeRq.getTableName());
out.writeBoolean(writeRq.getReturnRowInternal());
}
/*
* Writes fields from Request
*/
static void serializeRequest(Request rq,
ByteOutputStream out)
throws IOException {
writeTimeout(out, rq.getTimeoutInternal());
}
/*
* Deserialization
*/
/*
* Primitives
*/
static int readInt(ByteInputStream in) throws IOException {
return SerializationUtil.readPackedInt(in);
}
static long readLong(ByteInputStream in) throws IOException {
return SerializationUtil.readPackedLong(in);
}
static double readDouble(ByteInputStream in) throws IOException {
return in.readDouble();
}
static String readString(ByteInputStream in) throws IOException {
return SerializationUtil.readString(in);
}
static byte[] readByteArray(ByteInputStream in) throws IOException {
return SerializationUtil.readByteArray(in);
}
static int[] readIntArray(ByteInputStream in) throws IOException {
return SerializationUtil.readPackedIntArray(in);
}
/**
* Reads a byte array that has a not-packed integer size
*/
static byte[] readByteArrayWithInt(ByteInputStream in) throws IOException {
int length = in.readInt();
if (length <= 0) {
throw new IOException(
"Invalid length for prepared query: " + length);
}
byte[] query = new byte[length];
in.readFully(query);
return query;
}
static Version readVersion(ByteInputStream in) throws IOException {
return Version.createVersion(readByteArray(in));
}
static void deserializeConsumedCapacity(ByteInputStream in,
Result result)
throws IOException {
result.setReadUnits(SerializationUtil.readPackedInt(in));
result.setReadKB(SerializationUtil.readPackedInt(in));
result.setWriteKB(SerializationUtil.readPackedInt(in));
}
static void deserializeWriteResponse(ByteInputStream in,
WriteResult result)
throws IOException {
boolean returnInfo = in.readBoolean();
if (!returnInfo) {
return;
}
/*
* Existing info always includes both value and version
*/
result.setExistingValue(readFieldValue(in).asMap());
result.setExistingVersion(readVersion(in));
}
static void deserializeGeneratedValue(ByteInputStream in,
PutResult result)
throws IOException {
boolean hasGeneratedValue = in.readBoolean();
if (!hasGeneratedValue) {
return;
}
result.setGeneratedValue(readFieldValue(in));
}
static TableResult deserializeTableResult(ByteInputStream in)
throws IOException {
TableResult result = new TableResult();
boolean hasInfo = in.readBoolean();
if (hasInfo) {
result.setDomainId(readString(in));
result.setTableName(readString(in));
result.setState(getTableState(in.readByte()));
boolean hasStaticState = in.readBoolean();
if (hasStaticState) {
int readKb = readInt(in);
int writeKb = readInt(in);
int storageGB = readInt(in);
/*
* on-prem tables may return all 0 because of protocol
* limitations that lump the schema with limits. Return
* null to user for those cases.
*/
if (!(readKb == 0 && writeKb == 0 && storageGB == 0)) {
result.setTableLimits(
new TableLimits(readKb, writeKb, storageGB));
}
result.setSchema(readString(in));
}
result.setOperationId(readString(in));
}
return result;
}
/**
* state, operation, statement
*/
static SystemResult deserializeSystemResult(ByteInputStream in)
throws IOException {
SystemResult result = new SystemResult();
result.setState(getOperationState(in.readByte()));
result.setOperationId(readString(in));
result.setStatement(readString(in));
result.setResultString(readString(in));
return result;
}
public static TopologyInfo readTopologyInfo(ByteInputStream in)
throws IOException {
int seqNum = SerializationUtil.readPackedInt(in);
trace("readTopologyInfo: seqNum = " + seqNum, 4);
if (seqNum < -1) {
throw new IOException(
"Invalid topology sequence number: " + seqNum);
}
if (seqNum == -1) {
// No topology info sent by proxy
return null;
}
int[] shardIds = SerializationUtil.readPackedIntArray(in);
return new TopologyInfo(seqNum, shardIds);
}
/*
* Maps the error code returned from the server into a local string.
*/
public static RuntimeException mapException(int code, String msg) {
switch (code) {
case UNKNOWN_ERROR:
case UNKNOWN_OPERATION:
return new NoSQLException("Unknown error: " + msg);
case TABLE_NOT_FOUND:
return new TableNotFoundException(msg);
case INDEX_LIMIT_EXCEEDED:
return new IndexLimitException(msg);
case TABLE_LIMIT_EXCEEDED:
return new TableLimitException(msg);
case EVOLUTION_LIMIT_EXCEEDED:
return new EvolutionLimitException(msg);
case INDEX_NOT_FOUND:
return new IndexNotFoundException(msg);
case ILLEGAL_ARGUMENT:
return new IllegalArgumentException(msg);
case READ_LIMIT_EXCEEDED:
return new ReadThrottlingException(msg);
case WRITE_LIMIT_EXCEEDED:
return new WriteThrottlingException(msg);
case SIZE_LIMIT_EXCEEDED:
return new TableSizeException(msg);
case ROW_SIZE_LIMIT_EXCEEDED:
return new RowSizeLimitException(msg);
case KEY_SIZE_LIMIT_EXCEEDED:
return new KeySizeLimitException(msg);
case BATCH_OP_NUMBER_LIMIT_EXCEEDED:
return new BatchOperationNumberLimitException(msg);
case REQUEST_SIZE_LIMIT_EXCEEDED:
return new RequestSizeLimitException(msg);
case TABLE_EXISTS:
return new TableExistsException(msg);
case INDEX_EXISTS:
return new IndexExistsException(msg);
case TABLE_DEPLOYMENT_LIMIT_EXCEEDED:
case TENANT_DEPLOYMENT_LIMIT_EXCEEDED:
return new DeploymentException(msg);
case ILLEGAL_STATE:
return new IllegalStateException(msg);
case SERVICE_UNAVAILABLE:
case SERVER_ERROR:
/* treat above as retryable system errors */
return new SystemException(msg);
case BAD_PROTOCOL_MESSAGE:
return new IllegalArgumentException("Bad protocol message: " + msg);
case REQUEST_TIMEOUT:
return new RequestTimeoutException(msg);
case INVALID_AUTHORIZATION:
return new InvalidAuthorizationException(msg);
case INSUFFICIENT_PERMISSION:
return new UnauthorizedException(msg);
case SECURITY_INFO_UNAVAILABLE:
return new SecurityInfoNotReadyException(msg);
case RETRY_AUTHENTICATION:
return new AuthenticationException(msg);
case OPERATION_LIMIT_EXCEEDED:
return new OperationThrottlingException(msg);
case RESOURCE_EXISTS:
return new ResourceExistsException(msg);
case RESOURCE_NOT_FOUND:
return new ResourceNotFoundException(msg);
case OPERATION_NOT_SUPPORTED:
return new OperationNotSupportedException(msg);
default:
return new NoSQLException("Unknown error code " + code + ": " +
msg);
}
}
private static int getConsistency(Consistency consistency) {
if (consistency == Consistency.ABSOLUTE) {
return ABSOLUTE;
}
return EVENTUAL;
}
static TableResult.State getTableState(int state) {
switch (state) {
case ACTIVE:
return TableResult.State.ACTIVE;
case CREATING:
return TableResult.State.CREATING;
case DROPPED:
return TableResult.State.DROPPED;
case DROPPING:
return TableResult.State.DROPPING;
case UPDATING:
return TableResult.State.UPDATING;
default:
throw new IllegalStateException("Unknown table state " + state);
}
}
static SystemResult.State getOperationState(int state) {
switch (state) {
case COMPLETE:
return SystemResult.State.COMPLETE;
case WORKING:
return SystemResult.State.WORKING;
default:
throw new IllegalStateException("Unknown operation state " + state);
}
}
/*
* Checks if the request size exceeds the limit.
*/
public static void checkRequestSizeLimit(Request request, int requestSize) {
if (!request.getCheckRequestSize()) {
return;
}
final int requestSizeLimit =
(request instanceof WriteMultipleRequest) ?
BATCH_REQUEST_SIZE_LIMIT : REQUEST_SIZE_LIMIT;
if (requestSize > requestSizeLimit) {
throw new RequestSizeLimitException("The request size of " +
requestSize + " exceeded the limit of " + requestSizeLimit);
}
}
/**
* An instance of FieldValueEventHandler that accepts events and adds them
* to the protocol output stream.
*/
private static class BinarySerializer implements FieldValueEventHandler {
private final ByteOutputStream out;
/*
* Stack used to store offsets for maps and arrays for tracking
* number of bytes used for a map or array.
*/
private final Stack offsetStack;
/*
* Stack used to store sizes for maps and arrays for tracking number of
* elements in a map or array.
*/
private final Stack sizeStack;
public BinarySerializer(ByteOutputStream out) {
this.out = out;
offsetStack = new Stack();
sizeStack = new Stack();
}
/*
* Maps and Arrays. These objects start with their total length,
* allowing them to be optionally skipped on deserialization.
* 1. start:
* make a 4-byte space for the ultimate length of the serialized
* object.
* 2. save the offset on a stack
* 3. start counting elements on a stack
* 4. ... entries are written
* 5. end:
* a. pop the offset stack to get the original length offset
* write the real length into the spot that was held
* b. pop the size stack to get the number of elements
* write the real number of elements the spot that was held
* NOTE: a full 4-byte integer is used to avoid the variable-length
* encoding used by compressed integers.
*
* It would be more efficient and avoid an extra stack with pop/push
* for each map/array element to rely on the size from the caller
* but counting elements here is safer and doesn't rely on the caller
* having access to the size information. For example, a caller may be
* turning a List (via iterator) into an array. That is less likely
* for a Map but it's simplest to keep them the same. Alternatively
* the caller could track number of elements and send it correctly in
* the end* calls but again, that relies on the caller.
*/
@Override
public void startMap(int size) throws IOException {
out.writeByte(TYPE_MAP);
int lengthOffset = out.getOffset();
out.writeInt(0); // size in bytes
out.writeInt(0); // number of elements
offsetStack.push(lengthOffset);
sizeStack.push(0);
}
@Override
public void startArray(int size) throws IOException {
out.writeByte(TYPE_ARRAY);
int lengthOffset = out.getOffset();
out.writeInt(0); // size in bytes
out.writeInt(0); // number of elements
offsetStack.push(lengthOffset);
sizeStack.push(0);
}
@Override
public void endMap(int size) throws IOException {
int lengthOffset = offsetStack.pop();
int numElems = sizeStack.pop();
int start = lengthOffset + 4;
/*
* write size in bytes, then number of elements into the space
* reserved
*/
out.writeIntAtOffset(lengthOffset, out.getOffset() - start);
out.writeIntAtOffset(lengthOffset + 4, numElems);
}
@Override
public void endArray(int size) throws IOException {
int lengthOffset = offsetStack.pop();
int numElems = sizeStack.pop();
int start = lengthOffset + 4;
/*
* write size in bytes, then number of elements into the space
* reserved
*/
out.writeIntAtOffset(lengthOffset, out.getOffset() - start);
out.writeIntAtOffset(lengthOffset + 4, numElems);
}
@Override
public void startMapField(String key) throws IOException {
writeString(out, key);
}
@Override
public void endMapField() throws IOException {
/* add one to number of elements */
incrSize();
}
@Override
public void endArrayField() throws IOException {
/* add one to number of elements */
incrSize();
}
@Override
public void booleanValue(boolean value) throws IOException {
out.writeByte(TYPE_BOOLEAN);
out.writeBoolean(value);
}
@Override
public void binaryValue(byte[] byteArray) throws IOException {
out.writeByte(TYPE_BINARY);
writeByteArray(out, byteArray);
}
@Override
public void stringValue(String value) throws IOException {
out.writeByte(TYPE_STRING);
writeString(out, value);
}
@Override
public void integerValue(int value) throws IOException {
out.writeByte(TYPE_INTEGER);
writeInt(out, value);
}
@Override
public void longValue(long value) throws IOException {
out.writeByte(TYPE_LONG);
writeLong(out, value);
}
@Override
public void doubleValue(double value) throws IOException {
out.writeByte(TYPE_DOUBLE);
writeDouble(out, value);
}
@Override
public void numberValue(BigDecimal value) throws IOException {
out.writeByte(TYPE_NUMBER);
writeString(out, value.toString());
}
@Override
public void timestampValue(TimestampValue timestamp)
throws IOException {
out.writeByte(TYPE_TIMESTAMP);
writeTimestamp(out, timestamp);
}
@Override
public void jsonNullValue() throws IOException {
out.writeByte(TYPE_JSON_NULL);
}
@Override
public void nullValue() throws IOException {
out.writeByte(TYPE_NULL);
}
@Override
public void emptyValue() throws IOException {
out.writeByte(TYPE_EMPTY);
}
private void incrSize() {
int value = sizeStack.pop();
sizeStack.push(value + 1);
}
}
/**
* @hidden
*
* An instance of FieldValueEventHandler that accepts events and constructs
* a {@link FieldValue} instance. This is used for creating instances
* from the wire protocol.
*
* In order to handle creation of nested complex types such as maps and
* arrays stacks are maintained.
* The current FieldValue instance is available using the
* getCurrentValue() method.
*
* This class is public only so it can be tested.
*/
public static class FieldValueCreator implements FieldValueEventHandler {
private Stack mapStack;
private Stack arrayStack;
/*
* A stack of map keys is needed to handle the situation where maps
* are nested.
*/
private Stack keyStack;
MapValue currentMap;
ArrayValue currentArray;
String currentKey;
FieldValue currentValue;
private void pushMap(MapValue map) {
if (currentMap != null) {
if (mapStack == null) {
mapStack = new Stack();
}
mapStack.push(currentMap);
}
currentMap = map;
currentValue = map;
}
private void pushArray(ArrayValue array) {
if (currentArray != null) {
if (arrayStack == null) {
arrayStack = new Stack();
}
arrayStack.push(currentArray);
}
currentArray = array;
currentValue = array;
}
private void pushKey(String key) {
if (currentKey != null) {
if (keyStack == null) {
keyStack = new Stack();
}
keyStack.push(currentKey);
}
currentKey = key;
}
/**
* Returns the current FieldValue if available
*
* @return the current value
*/
public FieldValue getCurrentValue() {
return currentValue;
}
@Override
public void startMap(int size) throws IOException {
/* maintain insertion order */
pushMap(new MapValue(true, size));
}
@Override
public void startArray(int size) throws IOException {
pushArray(new ArrayValue(size));
}
@Override
public void endMap(int size) throws IOException {
/*
* The in-process map becomes the currentValue
*/
currentValue = currentMap;
if (mapStack != null && !mapStack.empty()) {
currentMap = mapStack.pop();
} else {
currentMap = null;
}
}
@Override
public void endArray(int size) throws IOException {
/*
* The in-process array becomes the currentValue
*/
currentValue = currentArray;
if (arrayStack != null && !arrayStack.empty()) {
currentArray = arrayStack.pop();
} else {
currentArray = null;
}
}
@Override
public void startMapField(String key) throws IOException {
pushKey(key);
}
@Override
public void endMapField() throws IOException {
currentMap.put(currentKey, currentValue);
if (keyStack != null && !keyStack.empty()) {
currentKey = keyStack.pop();
} else {
currentKey = null;
}
// currentValue undefined right now...
}
@Override
public void endArrayField() throws IOException {
currentArray.add(currentValue);
}
@Override
public void booleanValue(boolean value) throws IOException {
currentValue = BooleanValue.getInstance(value);
}
@Override
public void binaryValue(byte[] byteArray) throws IOException {
currentValue = new BinaryValue(byteArray);
}
@Override
public void stringValue(String value) throws IOException {
currentValue = new StringValue(value);
}
@Override
public void integerValue(int value) throws IOException {
currentValue = new IntegerValue(value);
}
@Override
public void longValue(long value) throws IOException {
currentValue = new LongValue(value);
}
@Override
public void doubleValue(double value) throws IOException {
currentValue = new DoubleValue(value);
}
@Override
public void numberValue(BigDecimal value) throws IOException {
currentValue = new NumberValue(value);
}
@Override
public void timestampValue(TimestampValue timestamp) {
currentValue = timestamp;
}
@Override
public void jsonNullValue() throws IOException {
currentValue = JsonNullValue.getInstance();
}
@Override
public void nullValue() throws IOException {
currentValue = NullValue.getInstance();
}
@Override
public void emptyValue() throws IOException {
currentValue = EmptyValue.getInstance();
}
}
/*
* Read the protocol input stream and send events to a handler that
* creates a FieldValue.
*/
public static FieldValue readFieldValue(ByteInputStream in)
throws IOException {
FieldValueCreator handler = new FieldValueCreator();
readFieldValueInternal(handler, in);
/*
* Results accumulated in the handler
*/
return handler.getCurrentValue();
}
/*
* Internal implementation that uses the same handler to maintain state
* while creating a FieldValue.
*/
private static void readFieldValueInternal(
FieldValueEventHandler handler, ByteInputStream in)
throws IOException {
int t = in.readByte();
switch (t) {
case TYPE_ARRAY:
in.readInt(); // length of serialized bytes
int length = in.readInt();
handler.startArray(length);
for (int i = 0; i < length; i++) {
readFieldValueInternal(handler, in);
handler.endArrayField();
}
handler.endArray(length);
break;
case TYPE_BINARY:
handler.binaryValue(readByteArray(in));
break;
case TYPE_BOOLEAN:
handler.booleanValue(in.readBoolean());
break;
case TYPE_DOUBLE:
handler.doubleValue(readDouble(in));
break;
case TYPE_INTEGER:
handler.integerValue(readInt(in));
break;
case TYPE_LONG:
handler.longValue(readLong(in));
break;
case TYPE_MAP:
in.readInt(); // length of serialized bytes
length = in.readInt(); // size of map
handler.startMap(length);
for (int i = 0; i < length; i++) {
String key = readString(in);
handler.startMapField(key);
readFieldValueInternal(handler, in); // read value
handler.endMapField();
}
handler.endMap(length);
break;
case TYPE_STRING:
handler.stringValue(readString(in));
break;
case TYPE_TIMESTAMP:
handler.timestampValue(new TimestampValue(readString(in), 1));
break;
case TYPE_NUMBER:
handler.numberValue(new BigDecimal(readString(in)));
break;
case TYPE_JSON_NULL:
handler.jsonNullValue();
break;
case TYPE_NULL:
handler.nullValue();
break;
case TYPE_EMPTY :
handler.emptyValue();
break;
default:
throw new IllegalStateException("Unknown value type code: " + t);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy