You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ag...@apache.org on 2018/06/30 13:24:37 UTC
[15/50] [abbrv] ignite git commit: IGNITE-7777: Implemented Ignite
NodeJs thin client
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryUtils.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/BinaryUtils.js b/modules/platforms/nodejs/lib/internal/BinaryUtils.js
new file mode 100644
index 0000000..ab2d40c
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/BinaryUtils.js
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+const Decimal = require('decimal.js');
+const ObjectType = require('../ObjectType').ObjectType;
+const CompositeType = require('../ObjectType').CompositeType;
+const MapObjectType = require('../ObjectType').MapObjectType;
+const CollectionObjectType = require('../ObjectType').CollectionObjectType;
+const ComplexObjectType = require('../ObjectType').ComplexObjectType;
+const ObjectArrayType = require('../ObjectType').ObjectArrayType;
+const Timestamp = require('../Timestamp');
+const EnumItem = require('../EnumItem');
+const Errors = require('../Errors');
+const ArgumentChecker = require('./ArgumentChecker');
+
+// Operation codes
+const OPERATION = Object.freeze({
+ // Key-Value Queries
+ CACHE_GET : 1000,
+ CACHE_PUT : 1001,
+ CACHE_PUT_IF_ABSENT : 1002,
+ CACHE_GET_ALL : 1003,
+ CACHE_PUT_ALL : 1004,
+ CACHE_GET_AND_PUT : 1005,
+ CACHE_GET_AND_REPLACE : 1006,
+ CACHE_GET_AND_REMOVE : 1007,
+ CACHE_GET_AND_PUT_IF_ABSENT : 1008,
+ CACHE_REPLACE : 1009,
+ CACHE_REPLACE_IF_EQUALS : 1010,
+ CACHE_CONTAINS_KEY : 1011,
+ CACHE_CONTAINS_KEYS : 1012,
+ CACHE_CLEAR : 1013,
+ CACHE_CLEAR_KEY : 1014,
+ CACHE_CLEAR_KEYS : 1015,
+ CACHE_REMOVE_KEY : 1016,
+ CACHE_REMOVE_IF_EQUALS : 1017,
+ CACHE_REMOVE_KEYS : 1018,
+ CACHE_REMOVE_ALL : 1019,
+ CACHE_GET_SIZE : 1020,
+ // Cache Configuration
+ CACHE_GET_NAMES : 1050,
+ CACHE_CREATE_WITH_NAME : 1051,
+ CACHE_GET_OR_CREATE_WITH_NAME : 1052,
+ CACHE_CREATE_WITH_CONFIGURATION : 1053,
+ CACHE_GET_OR_CREATE_WITH_CONFIGURATION : 1054,
+ CACHE_GET_CONFIGURATION : 1055,
+ CACHE_DESTROY : 1056,
+ // SQL and Scan Queries
+ QUERY_SCAN : 2000,
+ QUERY_SCAN_CURSOR_GET_PAGE : 2001,
+ QUERY_SQL : 2002,
+ QUERY_SQL_CURSOR_GET_PAGE : 2003,
+ QUERY_SQL_FIELDS : 2004,
+ QUERY_SQL_FIELDS_CURSOR_GET_PAGE : 2005,
+ RESOURCE_CLOSE : 0,
+ // Binary Types
+ GET_BINARY_TYPE : 3002,
+ PUT_BINARY_TYPE : 3003
+});
+
+const TYPE_CODE = Object.assign({
+ BINARY_OBJECT : 27,
+ BINARY_ENUM : 38
+ },
+ ObjectType.PRIMITIVE_TYPE,
+ ObjectType.COMPOSITE_TYPE);
+
+
+const TYPE_INFO = Object.freeze({
+ [TYPE_CODE.BYTE] : {
+ NAME : 'byte',
+ SIZE : 1
+ },
+ [TYPE_CODE.SHORT] : {
+ NAME : 'short',
+ SIZE : 2
+ },
+ [TYPE_CODE.INTEGER] : {
+ NAME : 'integer',
+ SIZE : 4
+ },
+ [TYPE_CODE.LONG] : {
+ NAME : 'long',
+ SIZE : 8
+ },
+ [TYPE_CODE.FLOAT] : {
+ NAME : 'float',
+ SIZE : 4
+ },
+ [TYPE_CODE.DOUBLE] : {
+ NAME : 'double',
+ SIZE : 8
+ },
+ [TYPE_CODE.CHAR] : {
+ NAME : 'char',
+ SIZE : 2
+ },
+ [TYPE_CODE.BOOLEAN] : {
+ NAME : 'boolean',
+ SIZE : 1
+ },
+ [TYPE_CODE.STRING] : {
+ NAME : 'string',
+ NULLABLE : true
+ },
+ [TYPE_CODE.UUID] : {
+ NAME : 'UUID',
+ SIZE : 16,
+ NULLABLE : true
+ },
+ [TYPE_CODE.DATE] : {
+ NAME : 'date',
+ SIZE : 8,
+ NULLABLE : true
+ },
+ [TYPE_CODE.BYTE_ARRAY] : {
+ NAME : 'byte array',
+ ELEMENT_TYPE : TYPE_CODE.BYTE,
+ NULLABLE : true
+ },
+ [TYPE_CODE.SHORT_ARRAY] : {
+ NAME : 'short array',
+ ELEMENT_TYPE : TYPE_CODE.SHORT,
+ NULLABLE : true
+ },
+ [TYPE_CODE.INTEGER_ARRAY] : {
+ NAME : 'integer array',
+ ELEMENT_TYPE : TYPE_CODE.INTEGER,
+ NULLABLE : true
+ },
+ [TYPE_CODE.LONG_ARRAY] : {
+ NAME : 'long array',
+ ELEMENT_TYPE : TYPE_CODE.LONG,
+ NULLABLE : true
+ },
+ [TYPE_CODE.FLOAT_ARRAY] : {
+ NAME : 'float array',
+ ELEMENT_TYPE : TYPE_CODE.FLOAT,
+ NULLABLE : true
+ },
+ [TYPE_CODE.DOUBLE_ARRAY] : {
+ NAME : 'double array',
+ ELEMENT_TYPE : TYPE_CODE.DOUBLE,
+ NULLABLE : true
+ },
+ [TYPE_CODE.CHAR_ARRAY] : {
+ NAME : 'char array',
+ ELEMENT_TYPE : TYPE_CODE.CHAR,
+ NULLABLE : true
+ },
+ [TYPE_CODE.BOOLEAN_ARRAY] : {
+ NAME : 'boolean array',
+ ELEMENT_TYPE : TYPE_CODE.BOOLEAN,
+ NULLABLE : true
+ },
+ [TYPE_CODE.STRING_ARRAY] : {
+ NAME : 'string array',
+ ELEMENT_TYPE : TYPE_CODE.STRING,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.DATE_ARRAY] : {
+ NAME : 'date array',
+ ELEMENT_TYPE : TYPE_CODE.DATE,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.UUID_ARRAY] : {
+ NAME : 'UUID array',
+ ELEMENT_TYPE : TYPE_CODE.UUID,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.OBJECT_ARRAY] : {
+ NAME : 'object array',
+ ELEMENT_TYPE : TYPE_CODE.COMPLEX_OBJECT,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.COLLECTION] : {
+ NAME : 'collection',
+ NULLABLE : true
+ },
+ [TYPE_CODE.MAP] : {
+ NAME : 'map',
+ NULLABLE : true
+ },
+ [TYPE_CODE.BINARY_OBJECT] : {
+ NAME : 'BinaryObject',
+ NULLABLE : true
+ },
+ [TYPE_CODE.ENUM] : {
+ NAME : 'enum',
+ NULLABLE : true
+ },
+ [TYPE_CODE.ENUM_ARRAY] : {
+ NAME : 'enum array',
+ ELEMENT_TYPE : TYPE_CODE.ENUM,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.DECIMAL] : {
+ NAME : 'decimal',
+ NULLABLE : true
+ },
+ [TYPE_CODE.DECIMAL_ARRAY] : {
+ NAME : 'decimal array',
+ ELEMENT_TYPE : TYPE_CODE.DECIMAL,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.TIMESTAMP] : {
+ NAME : 'timestamp',
+ NULLABLE : true
+ },
+ [TYPE_CODE.TIMESTAMP_ARRAY] : {
+ NAME : 'timestamp array',
+ ELEMENT_TYPE : TYPE_CODE.TIMESTAMP,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.TIME] : {
+ NAME : 'time',
+ NULLABLE : true
+ },
+ [TYPE_CODE.TIME_ARRAY] : {
+ NAME : 'time array',
+ ELEMENT_TYPE : TYPE_CODE.TIME,
+ KEEP_ELEMENT_TYPE : true,
+ NULLABLE : true
+ },
+ [TYPE_CODE.NULL] : {
+ NAME : 'null',
+ NULLABLE : true
+ },
+ [TYPE_CODE.COMPLEX_OBJECT] : {
+ NAME : 'complex object',
+ NULLABLE : true
+ }
+});
+
+const UTF8_ENCODING = 'utf8';
+
+class BinaryUtils {
+ static get OPERATION() {
+ return OPERATION;
+ }
+
+ static get TYPE_CODE() {
+ return TYPE_CODE;
+ }
+
+ static get TYPE_INFO() {
+ return TYPE_INFO;
+ }
+
+ static getSize(typeCode) {
+ const size = TYPE_INFO[typeCode].SIZE;
+ return size ? size : 0;
+ }
+
+ static get ENCODING() {
+ return UTF8_ENCODING;
+ }
+
+ static getTypeName(type) {
+ if (typeof type === 'string') {
+ return type;
+ }
+ const typeCode = BinaryUtils.getTypeCode(type);
+ return TYPE_INFO[typeCode] ? TYPE_INFO[typeCode].NAME : 'type code ' + typeCode;
+ }
+
+ static isNullable(type) {
+ return TYPE_INFO[BinaryUtils.getTypeCode(type)].NULLABLE === true;
+ }
+
+ static getTypeCode(type) {
+ return type instanceof CompositeType ? type._typeCode : type;
+ }
+
+ static checkObjectType(type, argName) {
+ if (type === null || type instanceof CompositeType) {
+ return;
+ }
+ ArgumentChecker.hasValueFrom(type, argName, false, ObjectType.PRIMITIVE_TYPE);
+ }
+
+ static calcObjectType(object) {
+ const BinaryObject = require('../BinaryObject');
+ const objectType = typeof object;
+ if (object === null) {
+ return BinaryUtils.TYPE_CODE.NULL;
+ }
+ else if (objectType === 'number') {
+ return BinaryUtils.TYPE_CODE.DOUBLE;
+ }
+ else if (objectType === 'string') {
+ return BinaryUtils.TYPE_CODE.STRING;
+ }
+ else if (objectType === 'boolean') {
+ return BinaryUtils.TYPE_CODE.BOOLEAN;
+ }
+ else if (object instanceof Timestamp) {
+ return BinaryUtils.TYPE_CODE.TIMESTAMP;
+ }
+ else if (object instanceof Date) {
+ return BinaryUtils.TYPE_CODE.DATE;
+ }
+ else if (object instanceof EnumItem) {
+ return BinaryUtils.TYPE_CODE.ENUM;
+ }
+ else if (object instanceof Decimal) {
+ return BinaryUtils.TYPE_CODE.DECIMAL;
+ }
+ else if (object instanceof Array) {
+ if (object.length > 0 && object[0] !== null) {
+ return BinaryUtils.getArrayType(BinaryUtils.calcObjectType(object[0]));
+ }
+ }
+ else if (object instanceof Set) {
+ return new CollectionObjectType(CollectionObjectType.COLLECTION_SUBTYPE.HASH_SET);
+ }
+ else if (object instanceof Map) {
+ return new MapObjectType();
+ }
+ else if (object instanceof BinaryObject) {
+ return BinaryUtils.TYPE_CODE.BINARY_OBJECT;
+ }
+ else if (objectType === 'object') {
+ return new ComplexObjectType(object);
+ }
+ throw Errors.IgniteClientError.unsupportedTypeError(objectType);
+ }
+
+ static checkCompatibility(value, type) {
+ if (!type) {
+ return;
+ }
+ const typeCode = BinaryUtils.getTypeCode(type);
+ if (value === null) {
+ if (!BinaryUtils.isNullable(typeCode)) {
+ throw Errors.IgniteClientError.typeCastError(BinaryUtils.TYPE_CODE.NULL, typeCode);
+ }
+ return;
+ }
+ else if (BinaryUtils.isStandardType(typeCode)) {
+ BinaryUtils.checkStandardTypeCompatibility(value, typeCode, type);
+ return;
+ }
+ const valueTypeCode = BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(value));
+ if (typeCode !== valueTypeCode) {
+ throw Errors.IgniteClientError.typeCastError(valueTypeCode, typeCode);
+ }
+ }
+
+ static isStandardType(typeCode) {
+ return typeCode !== BinaryUtils.TYPE_CODE.BINARY_OBJECT &&
+ typeCode !== BinaryUtils.TYPE_CODE.COMPLEX_OBJECT;
+ }
+
+ static checkStandardTypeCompatibility(value, typeCode, type = null) {
+ const valueType = typeof value;
+ switch (typeCode) {
+ case BinaryUtils.TYPE_CODE.BYTE:
+ case BinaryUtils.TYPE_CODE.SHORT:
+ case BinaryUtils.TYPE_CODE.INTEGER:
+ case BinaryUtils.TYPE_CODE.LONG:
+ if (!Number.isInteger(value)) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.FLOAT:
+ case BinaryUtils.TYPE_CODE.DOUBLE:
+ if (valueType !== 'number') {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.CHAR:
+ if (valueType !== 'string' || value.length !== 1) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.BOOLEAN:
+ if (valueType !== 'boolean') {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.STRING:
+ if (valueType !== 'string') {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.UUID:
+ if (!value instanceof Array ||
+ value.length !== BinaryUtils.getSize(BinaryUtils.TYPE_CODE.UUID)) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ value.forEach(element =>
+ BinaryUtils.checkStandardTypeCompatibility(element, BinaryUtils.TYPE_CODE.BYTE));
+ return;
+ case BinaryUtils.TYPE_CODE.DATE:
+ if (!value instanceof Date) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.ENUM:
+ if (!value instanceof EnumItem) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.DECIMAL:
+ if (!value instanceof Decimal) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.TIMESTAMP:
+ if (!value instanceof Timestamp) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.TIME:
+ if (!value instanceof Date) {
+ throw Errors.IgniteClientError.valueCastError(value, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.BYTE_ARRAY:
+ case BinaryUtils.TYPE_CODE.SHORT_ARRAY:
+ case BinaryUtils.TYPE_CODE.INTEGER_ARRAY:
+ case BinaryUtils.TYPE_CODE.LONG_ARRAY:
+ case BinaryUtils.TYPE_CODE.FLOAT_ARRAY:
+ case BinaryUtils.TYPE_CODE.DOUBLE_ARRAY:
+ case BinaryUtils.TYPE_CODE.CHAR_ARRAY:
+ case BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY:
+ case BinaryUtils.TYPE_CODE.STRING_ARRAY:
+ case BinaryUtils.TYPE_CODE.UUID_ARRAY:
+ case BinaryUtils.TYPE_CODE.DATE_ARRAY:
+ case BinaryUtils.TYPE_CODE.OBJECT_ARRAY:
+ case BinaryUtils.TYPE_CODE.ENUM_ARRAY:
+ case BinaryUtils.TYPE_CODE.DECIMAL_ARRAY:
+ case BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY:
+ case BinaryUtils.TYPE_CODE.TIME_ARRAY:
+ if (!value instanceof Array) {
+ throw Errors.IgniteClientError.typeCastError(valueType, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.MAP:
+ if (!value instanceof Map) {
+ throw Errors.IgniteClientError.typeCastError(valueType, typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.COLLECTION:
+ if (!(type && type._isSet() && value instanceof Set || value instanceof Array)) {
+ throw Errors.IgniteClientError.typeCastError(valueType, type && type._isSet() ? 'set' : typeCode);
+ }
+ return;
+ case BinaryUtils.TYPE_CODE.NULL:
+ if (value !== null) {
+ throw Errors.IgniteClientError.typeCastError('not null', typeCode);
+ }
+ return;
+ default:
+ const valueTypeCode = BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(value));
+ if (valueTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT) {
+ throw Errors.IgniteClientError.typeCastError(valueTypeCode, typeCode);
+ }
+ return;
+ }
+ }
+
+ static checkTypesComatibility(expectedType, actualTypeCode) {
+ if (expectedType === null) {
+ return;
+ }
+ const expectedTypeCode = BinaryUtils.getTypeCode(expectedType);
+ if (actualTypeCode === BinaryUtils.TYPE_CODE.NULL) {
+ return;
+ }
+ else if (expectedTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT ||
+ actualTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT &&
+ expectedTypeCode === BinaryUtils.TYPE_CODE.COMPLEX_OBJECT) {
+ return;
+ }
+ else if (actualTypeCode !== expectedTypeCode) {
+ throw Errors.IgniteClientError.typeCastError(actualTypeCode, expectedTypeCode);
+ }
+ }
+
+ static getArrayElementType(arrayType) {
+ if (arrayType instanceof ObjectArrayType) {
+ return arrayType._elementType;
+ }
+ else if (arrayType === BinaryUtils.TYPE_CODE.OBJECT_ARRAY) {
+ return null;
+ }
+ const elementTypeCode = TYPE_INFO[arrayType].ELEMENT_TYPE;
+ if (!elementTypeCode) {
+ throw Errors.IgniteClientError.internalError();
+ }
+ return elementTypeCode;
+ }
+
+ static getArrayType(elementType) {
+ switch (BinaryUtils.getTypeCode(elementType)) {
+ case BinaryUtils.TYPE_CODE.BYTE:
+ return BinaryUtils.TYPE_CODE.BYTE_ARRAY;
+ case BinaryUtils.TYPE_CODE.SHORT:
+ return BinaryUtils.TYPE_CODE.SHORT_ARRAY;
+ case BinaryUtils.TYPE_CODE.INTEGER:
+ return BinaryUtils.TYPE_CODE.INTEGER_ARRAY;
+ case BinaryUtils.TYPE_CODE.LONG:
+ return BinaryUtils.TYPE_CODE.LONG_ARRAY;
+ case BinaryUtils.TYPE_CODE.FLOAT:
+ return BinaryUtils.TYPE_CODE.FLOAT_ARRAY;
+ case BinaryUtils.TYPE_CODE.DOUBLE:
+ return BinaryUtils.TYPE_CODE.DOUBLE_ARRAY;
+ case BinaryUtils.TYPE_CODE.CHAR:
+ return BinaryUtils.TYPE_CODE.CHAR_ARRAY;
+ case BinaryUtils.TYPE_CODE.BOOLEAN:
+ return BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY;
+ case BinaryUtils.TYPE_CODE.STRING:
+ return BinaryUtils.TYPE_CODE.STRING_ARRAY;
+ case BinaryUtils.TYPE_CODE.UUID:
+ return BinaryUtils.TYPE_CODE.UUID_ARRAY;
+ case BinaryUtils.TYPE_CODE.DATE:
+ return BinaryUtils.TYPE_CODE.DATE_ARRAY;
+ case BinaryUtils.TYPE_CODE.ENUM:
+ return BinaryUtils.TYPE_CODE.ENUM_ARRAY;
+ case BinaryUtils.TYPE_CODE.DECIMAL:
+ return BinaryUtils.TYPE_CODE.DECIMAL_ARRAY;
+ case BinaryUtils.TYPE_CODE.TIMESTAMP:
+ return BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY;
+ case BinaryUtils.TYPE_CODE.TIME:
+ return BinaryUtils.TYPE_CODE.TIME_ARRAY;
+ case BinaryUtils.TYPE_CODE.BINARY_OBJECT:
+ return new ObjectArrayType();
+ default:
+ return new ObjectArrayType(elementType);
+ }
+ }
+
+ static keepArrayElementType(arrayTypeCode) {
+ return TYPE_INFO[arrayTypeCode].KEEP_ELEMENT_TYPE === true;
+ }
+
+ static getJsObjectFieldNames(jsObject) {
+ var fields = new Array();
+ for (let field in jsObject) {
+ if (typeof jsObject[field] !== 'function') {
+ fields.push(field);
+ }
+ }
+ return fields;
+ }
+
+ static hashCode(str) {
+ let hash = 0, char;
+ if (str && str.length > 0) {
+ for (let i = 0; i < str.length; i++) {
+ char = str.charCodeAt(i);
+ hash = ((hash << 5) - hash) + char;
+ hash |= 0; // Convert to 32bit integer
+ }
+ }
+ return hash;
+ }
+
+ static hashCodeLowerCase(str) {
+ return BinaryUtils.hashCode(str ? str.toLowerCase() : str);
+ }
+
+ static contentHashCode(buffer, startPos, endPos) {
+ let hash = 1;
+ for (let i = startPos; i <= endPos; i++) {
+ hash = 31 * hash + buffer._buffer[i];
+ hash |= 0; // Convert to 32bit integer
+ }
+ return hash;
+ }
+}
+
+module.exports = BinaryUtils;
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryWriter.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/BinaryWriter.js b/modules/platforms/nodejs/lib/internal/BinaryWriter.js
new file mode 100644
index 0000000..3686bb4
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/BinaryWriter.js
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+const Decimal = require('decimal.js');
+const Errors = require('../Errors');
+const ComplexObjectType = require('../ObjectType').ComplexObjectType;
+const BinaryUtils = require('./BinaryUtils');
+
+class BinaryWriter {
+
+ static async writeString(buffer, value) {
+ await BinaryWriter.writeObject(buffer, value, BinaryUtils.TYPE_CODE.STRING);
+ }
+
+ static async writeObject(buffer, object, objectType = null, writeObjectType = true) {
+ BinaryUtils.checkCompatibility(object, objectType);
+ if (object === null) {
+ buffer.writeByte(BinaryUtils.TYPE_CODE.NULL);
+ return;
+ }
+
+ objectType = objectType ? objectType : BinaryUtils.calcObjectType(object);
+ const objectTypeCode = BinaryUtils.getTypeCode(objectType);
+
+ if (writeObjectType) {
+ buffer.writeByte(objectTypeCode);
+ }
+ switch (objectTypeCode) {
+ case BinaryUtils.TYPE_CODE.BYTE:
+ case BinaryUtils.TYPE_CODE.SHORT:
+ case BinaryUtils.TYPE_CODE.INTEGER:
+ case BinaryUtils.TYPE_CODE.FLOAT:
+ case BinaryUtils.TYPE_CODE.DOUBLE:
+ buffer.writeNumber(object, objectTypeCode);
+ break;
+ case BinaryUtils.TYPE_CODE.LONG:
+ buffer.writeLong(object);
+ break;
+ case BinaryUtils.TYPE_CODE.CHAR:
+ buffer.writeChar(object);
+ break;
+ case BinaryUtils.TYPE_CODE.BOOLEAN:
+ buffer.writeBoolean(object);
+ break;
+ case BinaryUtils.TYPE_CODE.STRING:
+ buffer.writeString(object);
+ break;
+ case BinaryUtils.TYPE_CODE.UUID:
+ BinaryWriter._writeUUID(buffer, object);
+ break;
+ case BinaryUtils.TYPE_CODE.DATE:
+ buffer.writeDate(object);
+ break;
+ case BinaryUtils.TYPE_CODE.ENUM:
+ await BinaryWriter._writeEnum(buffer, object);
+ break;
+ case BinaryUtils.TYPE_CODE.DECIMAL:
+ BinaryWriter._writeDecimal(buffer, object);
+ break;
+ case BinaryUtils.TYPE_CODE.TIMESTAMP:
+ BinaryWriter._writeTimestamp(buffer, object);
+ break;
+ case BinaryUtils.TYPE_CODE.TIME:
+ BinaryWriter._writeTime(buffer, object);
+ break;
+ case BinaryUtils.TYPE_CODE.BYTE_ARRAY:
+ case BinaryUtils.TYPE_CODE.SHORT_ARRAY:
+ case BinaryUtils.TYPE_CODE.INTEGER_ARRAY:
+ case BinaryUtils.TYPE_CODE.LONG_ARRAY:
+ case BinaryUtils.TYPE_CODE.FLOAT_ARRAY:
+ case BinaryUtils.TYPE_CODE.DOUBLE_ARRAY:
+ case BinaryUtils.TYPE_CODE.CHAR_ARRAY:
+ case BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY:
+ case BinaryUtils.TYPE_CODE.STRING_ARRAY:
+ case BinaryUtils.TYPE_CODE.UUID_ARRAY:
+ case BinaryUtils.TYPE_CODE.DATE_ARRAY:
+ case BinaryUtils.TYPE_CODE.OBJECT_ARRAY:
+ case BinaryUtils.TYPE_CODE.ENUM_ARRAY:
+ case BinaryUtils.TYPE_CODE.DECIMAL_ARRAY:
+ case BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY:
+ case BinaryUtils.TYPE_CODE.TIME_ARRAY:
+ await BinaryWriter._writeArray(buffer, object, objectType, objectTypeCode);
+ break;
+ case BinaryUtils.TYPE_CODE.COLLECTION:
+ await BinaryWriter._writeCollection(buffer, object, objectType);
+ break;
+ case BinaryUtils.TYPE_CODE.MAP:
+ await BinaryWriter._writeMap(buffer, object, objectType);
+ break;
+ case BinaryUtils.TYPE_CODE.BINARY_OBJECT:
+ await BinaryWriter._writeBinaryObject(buffer, object, objectType);
+ break;
+ case BinaryUtils.TYPE_CODE.COMPLEX_OBJECT:
+ await BinaryWriter._writeComplexObject(buffer, object, objectType);
+ break;
+ default:
+ throw Errors.IgniteClientError.unsupportedTypeError(objectType);
+ }
+ }
+
+ static _writeUUID(buffer, value) {
+ buffer.writeBuffer(Buffer.from(value));
+ }
+
+ static async _writeEnum(buffer, enumValue) {
+ await enumValue._write(buffer);
+ }
+
+ static _writeDecimal(buffer, decimal) {
+ let strValue = decimal.toExponential();
+ let expIndex = strValue.indexOf('e');
+ if (expIndex < 0) {
+ expIndex = strValue.indexOf('E');
+ }
+ let scale = 0;
+ if (expIndex >= 0) {
+ scale = parseInt(strValue.substring(expIndex + 1));
+ strValue = strValue.substring(0, expIndex);
+ }
+ const isNegative = strValue.startsWith('-');
+ if (isNegative) {
+ strValue = strValue.substring(1);
+ }
+ const dotIndex = strValue.indexOf('.');
+ if (dotIndex >= 0) {
+ scale -= strValue.length - dotIndex - 1;
+ strValue = strValue.substring(0, dotIndex) + strValue.substring(dotIndex + 1);
+ }
+ scale = -scale;
+ let hexValue = new Decimal(strValue).toHexadecimal().substring(2);
+ hexValue = ((hexValue.length % 2 !== 0) ? '000' : '00') + hexValue;
+ const valueBuffer = Buffer.from(hexValue, 'hex');
+ if (isNegative) {
+ valueBuffer[0] |= 0x80;
+ }
+ buffer.writeInteger(scale);
+ buffer.writeInteger(valueBuffer.length);
+ buffer.writeBuffer(valueBuffer);
+ }
+
+ static _writeTimestamp(buffer, timestamp) {
+ buffer.writeDate(timestamp);
+ buffer.writeInteger(timestamp.getNanos());
+ }
+
+ static _writeTime(buffer, time) {
+ const midnight = new Date(time);
+ midnight.setHours(0, 0, 0, 0);
+ buffer.writeLong(time.getTime() - midnight.getTime());
+ }
+
+ static async _writeArray(buffer, array, arrayType, arrayTypeCode) {
+ const BinaryType = require('./BinaryType');
+ const elementType = BinaryUtils.getArrayElementType(arrayType);
+ const keepElementType = BinaryUtils.keepArrayElementType(arrayTypeCode);
+ if (arrayTypeCode === BinaryUtils.TYPE_CODE.OBJECT_ARRAY) {
+ buffer.writeInteger(elementType instanceof ComplexObjectType ?
+ BinaryType._calculateId(elementType._typeName) : -1);
+ }
+ buffer.writeInteger(array.length);
+ for (let elem of array) {
+ await BinaryWriter.writeObject(buffer, elem, elementType, keepElementType);
+ }
+ }
+
+ static async _writeCollection(buffer, collection, collectionType) {
+ buffer.writeInteger(collection instanceof Set ? collection.size : collection.length);
+ buffer.writeByte(collectionType._subType);
+ for (let element of collection) {
+ await BinaryWriter.writeObject(buffer, element, collectionType._elementType);
+ }
+ }
+
+ static async _writeMap(buffer, map, mapType) {
+ buffer.writeInteger(map.size);
+ buffer.writeByte(mapType._subType);
+ for (let [key, value] of map.entries()) {
+ await BinaryWriter.writeObject(buffer, key, mapType._keyType);
+ await BinaryWriter.writeObject(buffer, value, mapType._valueType);
+ }
+ }
+
+ static async _writeBinaryObject(buffer, binaryObject) {
+ buffer.position = buffer.position - 1;
+ await binaryObject._write(buffer);
+ }
+
+ static async _writeComplexObject(buffer, object, objectType) {
+ const BinaryObject = require('../BinaryObject');
+ await BinaryWriter._writeBinaryObject(buffer, await BinaryObject.fromObject(object, objectType));
+ }
+}
+
+module.exports = BinaryWriter;
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js b/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js
new file mode 100644
index 0000000..3ef9c76
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+const Util = require('util');
+const Errors = require('../Errors');
+const IgniteClient = require('../IgniteClient');
+const ClientSocket = require('./ClientSocket');
+const Logger = require('./Logger');
+
+/** Socket wrapper with failover functionality: reconnects on failure. */
+class ClientFailoverSocket {
+
+ constructor(onStateChanged) {
+ this._socket = null;
+ this._state = IgniteClient.STATE.DISCONNECTED;
+ this._onStateChanged = onStateChanged;
+ }
+
+ async connect(config) {
+ if (this._state !== IgniteClient.STATE.DISCONNECTED) {
+ throw new Errors.IllegalStateError();
+ }
+ this._config = config;
+ this._endpointsNumber = this._config._endpoints.length;
+ this._endpointIndex = this._getRandomInt(this._endpointsNumber - 1);
+ await this._connect();
+ }
+
+ async send(opCode, payloadWriter, payloadReader = null) {
+ if (this._state !== IgniteClient.STATE.CONNECTED) {
+ throw new Errors.IllegalStateError();
+ }
+ await this._socket.sendRequest(opCode, payloadWriter, payloadReader);
+ }
+
+ disconnect() {
+ if (this._state !== IgniteClient.STATE.DISCONNECTED) {
+ this._changeState(IgniteClient.STATE.DISCONNECTED);
+ if (this._socket) {
+ this._socket.disconnect();
+ this._socket = null;
+ }
+ }
+ }
+
+ async _onSocketDisconnect(error = null) {
+ this._changeState(IgniteClient.STATE.CONNECTING, null, error);
+ this._socket = null;
+ this._endpointIndex++;
+ try {
+ await this._connect();
+ }
+ catch (err) {
+ }
+ }
+
+ async _connect() {
+ const errors = new Array();
+ let index, endpoint;
+ for (let i = 0; i < this._endpointsNumber; i++) {
+ index = (this._endpointIndex + i) % this._endpointsNumber;
+ endpoint = this._config._endpoints[index];
+ try {
+ this._changeState(IgniteClient.STATE.CONNECTING, endpoint);
+ this._socket = new ClientSocket(
+ endpoint, this._config, this._onSocketDisconnect.bind(this));
+ await this._socket.connect();
+ this._changeState(IgniteClient.STATE.CONNECTED, endpoint);
+ return;
+ }
+ catch (err) {
+ errors.push(Util.format('[%s] %s', endpoint, err.message));
+ }
+ }
+ const error = errors.join('; ');
+ this._changeState(IgniteClient.STATE.DISCONNECTED, endpoint, error);
+ this._socket = null;
+ throw new Errors.IgniteClientError(error);
+ }
+
+ _changeState(state, endpoint = null, reason = null) {
+ if (Logger.debug) {
+ Logger.logDebug(Util.format('Socket %s: %s -> %s'),
+ endpoint ? endpoint : this._socket ? this._socket._endpoint : '',
+ this._getState(this._state),
+ this._getState(state));
+ }
+ if (this._state !== state) {
+ this._state = state;
+ if (this._onStateChanged) {
+ this._onStateChanged(state, reason);
+ }
+ }
+ }
+
+ _getState(state) {
+ switch (state) {
+ case IgniteClient.STATE.DISCONNECTED:
+ return 'DISCONNECTED';
+ case IgniteClient.STATE.CONNECTING:
+ return 'CONNECTING';
+ case IgniteClient.STATE.CONNECTED:
+ return 'CONNECTED';
+ default:
+ return 'UNKNOWN';
+ }
+ }
+
+ // returns a random integer between 0 and max
+ _getRandomInt(max) {
+ if (max === 0) {
+ return 0;
+ }
+ return Math.floor(Math.random() * (max + 1));
+ }
+}
+
+module.exports = ClientFailoverSocket;
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/ClientSocket.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/ClientSocket.js b/modules/platforms/nodejs/lib/internal/ClientSocket.js
new file mode 100644
index 0000000..1f12040
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/ClientSocket.js
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+const net = require('net');
+const tls = require('tls');
+const URL = require('url');
+const Long = require('long');
+const Util = require('util');
+const Errors = require('../Errors');
+const IgniteClientConfiguration = require('../IgniteClientConfiguration');
+const MessageBuffer = require('./MessageBuffer');
+const BinaryUtils = require('./BinaryUtils');
+const BinaryReader = require('./BinaryReader');
+const BinaryWriter = require('./BinaryWriter');
+const ArgumentChecker = require('./ArgumentChecker');
+const Logger = require('./Logger');
+
+const HANDSHAKE_SUCCESS_STATUS_CODE = 1;
+const REQUEST_SUCCESS_STATUS_CODE = 0;
+const PORT_DEFAULT = 10800;
+
+class ProtocolVersion {
+
+ constructor(major = null, minor = null, patch = null) {
+ this._major = major;
+ this._minor = minor;
+ this._patch = patch;
+ }
+
+ compareTo(other) {
+ let diff = this._major - other._major;
+ if (diff !== 0) {
+ return diff;
+ }
+ diff = this._minor - other._minor;
+ if (diff !== 0) {
+ return diff;
+ }
+ return this._patch - other._patch;
+ }
+
+ equals(other) {
+ return this.compareTo(other) === 0;
+ }
+
+ toString() {
+ return Util.format('%d.%d.%d', this._major, this._minor, this._patch);
+ }
+
+ read(buffer) {
+ this._major = buffer.readShort();
+ this._minor = buffer.readShort();
+ this._patch = buffer.readShort();
+ }
+
+ write(buffer) {
+ buffer.writeShort(this._major);
+ buffer.writeShort(this._minor);
+ buffer.writeShort(this._patch);
+ }
+}
+
+const PROTOCOL_VERSION_1_0_0 = new ProtocolVersion(1, 0, 0);
+const PROTOCOL_VERSION_1_1_0 = new ProtocolVersion(1, 1, 0);
+
+const SUPPORTED_VERSIONS = [
+ // PROTOCOL_VERSION_1_0_0, // Support for QueryField precision/scale fields breaks 1.0.0 compatibility
+ PROTOCOL_VERSION_1_1_0
+];
+
+const STATE = Object.freeze({
+ INITIAL : 0,
+ HANDSHAKE : 1,
+ CONNECTED : 2,
+ DISCONNECTED : 3
+});
+
+class ClientSocket {
+
+ constructor(endpoint, config, onSocketDisconnect) {
+ ArgumentChecker.notEmpty(endpoint, 'endpoints');
+ this._endpoint = endpoint;
+ this._parseEndpoint(endpoint);
+ this._config = config;
+ this._state = STATE.INITIAL;
+ this._socket = null;
+ this._requestId = Long.ZERO;
+ this._handshakeRequestId = null;
+ this._protocolVersion = null;
+ this._requests = new Map();
+ this._onSocketDisconnect = onSocketDisconnect;
+ this._error = null;
+ this._wasConnected = false;
+ }
+
+ async connect() {
+ return new Promise((resolve, reject) => {
+ this._connectSocket(
+ this._getHandshake(PROTOCOL_VERSION_1_1_0, resolve, reject));
+ });
+ }
+
+ disconnect() {
+ this._disconnect(true, false);
+ }
+
+ get requestId() {
+ const id = this._requestId;
+ this._requestId = this._requestId.add(1);
+ return id;
+ }
+
+ async sendRequest(opCode, payloadWriter, payloadReader = null) {
+ if (this._state === STATE.CONNECTED) {
+ return new Promise(async (resolve, reject) => {
+ const request = new Request(this.requestId, opCode, payloadWriter, payloadReader, resolve, reject);
+ this._addRequest(request);
+ await this._sendRequest(request);
+ });
+ }
+ else {
+ throw new Errors.IllegalStateError();
+ }
+ }
+
+ _connectSocket(handshakeRequest) {
+ const onConnected = async () => {
+ this._state = STATE.HANDSHAKE;
+ // send handshake
+ await this._sendRequest(handshakeRequest);
+ };
+
+ const options = Object.assign({},
+ this._config._options,
+ { host : this._host, port : this._port, version : this._version });
+ if (this._config._useTLS) {
+ this._socket = tls.connect(options, onConnected);
+ }
+ else {
+ this._socket = net.createConnection(options, onConnected);
+ }
+
+ this._socket.on('data', async (data) => {
+ try {
+ await this._processResponse(data);
+ }
+ catch (err) {
+ this._error = err.message;
+ this._disconnect();
+ }
+ });
+ this._socket.on('close', () => {
+ this._disconnect(false);
+ });
+ this._socket.on('error', (error) => {
+ this._error = this._state === STATE.INITIAL ?
+ 'Connection failed: ' + error : error;
+ this._disconnect();
+ });
+ }
+
+ _addRequest(request) {
+ this._requests.set(request.id.toString(), request);
+ }
+
+ async _sendRequest(request) {
+ try {
+ const message = await request.getMessage();
+ this._logMessage(request.id.toString(), true, message);
+ this._socket.write(message);
+ }
+ catch (err) {
+ this._requests.delete(request.id);
+ request.reject(err);
+ }
+ }
+
+ async _processResponse(message) {
+ if (this._state === STATE.DISCONNECTED) {
+ return;
+ }
+ let offset = 0;
+ while (offset < message.length) {
+ let buffer = MessageBuffer.from(message, offset);
+ // Response length
+ const length = buffer.readInteger();
+ offset += length + BinaryUtils.getSize(BinaryUtils.TYPE_CODE.INTEGER);
+ let requestId, isSuccess;
+ const isHandshake = this._state === STATE.HANDSHAKE;
+
+ if (isHandshake) {
+ // Handshake status
+ isSuccess = (buffer.readByte() === HANDSHAKE_SUCCESS_STATUS_CODE)
+ requestId = this._handshakeRequestId.toString();
+ }
+ else {
+ // Request id
+ requestId = buffer.readLong().toString();
+ // Status code
+ isSuccess = (buffer.readInteger() === REQUEST_SUCCESS_STATUS_CODE);
+ }
+
+ this._logMessage(requestId, false, buffer.data);
+
+ if (this._requests.has(requestId)) {
+ const request = this._requests.get(requestId);
+ this._requests.delete(requestId);
+ if (isHandshake) {
+ await this._finalizeHandshake(buffer, request, isSuccess);
+ }
+ else {
+ await this._finalizeResponse(buffer, request, isSuccess);
+ }
+ }
+ else {
+ throw Errors.IgniteClientError.internalError('Invalid response id: ' + requestId);
+ }
+ }
+ }
+
+ async _finalizeHandshake(buffer, request, isSuccess) {
+ if (!isSuccess) {
+ // Server protocol version
+ const serverVersion = new ProtocolVersion();
+ serverVersion.read(buffer);
+ // Error message
+ const errMessage = await BinaryReader.readObject(buffer);
+
+ if (!this._protocolVersion.equals(serverVersion)) {
+ if (!this._isSupportedVersion(serverVersion) ||
+ serverVersion.compareTo(PROTOCOL_VERSION_1_1_0) < 0 && this._config._userName) {
+ request.reject(new Errors.OperationError(
+ Util.format('Protocol version mismatch: client %s / server %s. Server details: %s',
+ this._protocolVersion.toString(), serverVersion.toString(), errMessage)));
+ this._disconnect();
+ }
+ else {
+ // retry handshake with server version
+ const handshakeRequest = this._getHandshake(serverVersion, request.resolve, request.reject);
+ await this._sendRequest(handshakeRequest);
+ }
+ }
+ else {
+ request.reject(new Errors.OperationError(errMessage));
+ this._disconnect();
+ }
+ }
+ else {
+ this._state = STATE.CONNECTED;
+ this._wasConnected = true;
+ request.resolve();
+ }
+ }
+
+ async _finalizeResponse(buffer, request, isSuccess) {
+ if (!isSuccess) {
+ // Error message
+ const errMessage = await BinaryReader.readObject(buffer);
+ request.reject(new Errors.OperationError(errMessage));
+ }
+ else {
+ try {
+ if (request.payloadReader) {
+ await request.payloadReader(buffer);
+ }
+ request.resolve();
+ }
+ catch (err) {
+ request.reject(err);
+ }
+ }
+ }
+
+ async _handshakePayloadWriter(payload) {
+ // Handshake code
+ payload.writeByte(1);
+ // Protocol version
+ this._protocolVersion.write(payload);
+ // Client code
+ payload.writeByte(2);
+ if (this._config._userName) {
+ await BinaryWriter.writeString(payload, this._config._userName);
+ await BinaryWriter.writeString(payload, this._config._password);
+ }
+ }
+
+ _getHandshake(version, resolve, reject) {
+ this._protocolVersion = version;
+ const handshakeRequest = new Request(
+ this.requestId, null, this._handshakePayloadWriter.bind(this), null, resolve, reject);
+ this._addRequest(handshakeRequest);
+ this._handshakeRequestId = handshakeRequest.id;
+ return handshakeRequest;
+ }
+
+ _isSupportedVersion(protocolVersion) {
+ for (let version of SUPPORTED_VERSIONS) {
+ if (version.equals(protocolVersion)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ _disconnect(close = true, callOnDisconnect = true) {
+ this._state = STATE.DISCONNECTED;
+ this._requests.forEach((request, id) => {
+ request.reject(new Errors.LostConnectionError(this._error));
+ this._requests.delete(id);
+ });
+ if (this._wasConnected && callOnDisconnect && this._onSocketDisconnect) {
+ this._onSocketDisconnect(this._error);
+ }
+ if (close) {
+ this._onSocketDisconnect = null;
+ this._socket.end();
+ }
+ }
+
+ _parseEndpoint(endpoint) {
+ endpoint = endpoint.trim();
+ this._host = endpoint;
+ this._port = null;
+ const colonCnt = endpoint.split(':').length - 1;
+ if (colonCnt > 1) {
+ // IPv6 address
+ this._version = 6;
+ const index = endpoint.lastIndexOf(']:');
+ if (index >= 0) {
+ this._host = endpoint.substring(0, index + 1);
+ this._port = endpoint.substring(index + 2);
+ }
+ if (this._host.startsWith('[') || this._host.endsWith(']')) {
+ if (this._host.startsWith('[') && this._host.endsWith(']')) {
+ this._host = this._host.substring(1, this._host.length - 1);
+ }
+ else {
+ throw Errors.IgniteClientError.illegalArgumentError('Incorrect endpoint format: ' + endpoint);
+ }
+ }
+ }
+ else {
+ // IPv4 address
+ this._version = 4;
+ const index = endpoint.lastIndexOf(':');
+ if (index >= 0) {
+ this._host = endpoint.substring(0, index);
+ this._port = endpoint.substring(index + 1);
+ }
+ }
+ if (!this._port) {
+ this._port = PORT_DEFAULT;
+ }
+ else {
+ this._port = parseInt(this._port);
+ if (isNaN(this._port)) {
+ throw Errors.IgniteClientError.illegalArgumentError('Incorrect endpoint format: ' + endpoint);
+ }
+ }
+ }
+
+ _logMessage(requestId, isRequest, message) {
+ if (Logger.debug) {
+ Logger.logDebug((isRequest ? 'Request: ' : 'Response: ') + requestId);
+ Logger.logDebug('[' + [...message] + ']');
+ }
+ }
+}
+
+class Request {
+ constructor(id, opCode, payloadWriter, payloadReader, resolve, reject) {
+ this._id = id;
+ this._opCode = opCode;
+ this._payloadWriter = payloadWriter;
+ this._payloadReader = payloadReader;
+ this._resolve = resolve;
+ this._reject = reject;
+ }
+
+ get id() {
+ return this._id;
+ }
+
+ get payloadReader() {
+ return this._payloadReader;
+ }
+
+ get resolve() {
+ return this._resolve;
+ }
+
+ get reject() {
+ return this._reject;
+ }
+
+ async getMessage() {
+ const message = new MessageBuffer();
+ // Skip message length
+ const messageStartPos = BinaryUtils.getSize(BinaryUtils.TYPE_CODE.INTEGER);
+ message.position = messageStartPos;
+ if (this._opCode !== null) {
+ // Op code
+ message.writeShort(this._opCode);
+ // Request id
+ message.writeLong(this._id);
+ }
+ if (this._payloadWriter) {
+ // Payload
+ await this._payloadWriter(message);
+ }
+ // Message length
+ message.position = 0;
+ message.writeInteger(message.length - messageStartPos);
+ return message.data;
+ }
+}
+
+module.exports = ClientSocket;
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/Logger.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/Logger.js b/modules/platforms/nodejs/lib/internal/Logger.js
new file mode 100644
index 0000000..628c70e
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/Logger.js
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+/** Utility class for logging errors and debug messages. */
+class Logger {
+ static get debug() {
+ return Logger._debug;
+ }
+
+ static set debug(value) {
+ Logger._debug = value;
+ }
+
+ static logDebug(data, ...args) {
+ if (Logger._debug) {
+ console.log(data, ...args);
+ }
+ }
+
+ static logError(data, ...args) {
+ if (Logger._debug) {
+ console.log('ERROR: ' + data, ...args);
+ }
+ }
+}
+
+Logger._debug = false;
+
+module.exports = Logger;
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/MessageBuffer.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/MessageBuffer.js b/modules/platforms/nodejs/lib/internal/MessageBuffer.js
new file mode 100644
index 0000000..f1407bf
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/MessageBuffer.js
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+const Long = require('long');
+const BinaryUtils = require('./BinaryUtils');
+const Errors = require('../Errors');
+
+const BUFFER_CAPACITY_DEFAULT = 256;
+const BYTE_ZERO = 0;
+const BYTE_ONE = 1;
+
+class MessageBuffer {
+ constructor(capacity = BUFFER_CAPACITY_DEFAULT) {
+ this._buffer = Buffer.allocUnsafe(capacity);
+ this._capacity = capacity;
+ this._length = 0;
+ this._position = 0;
+ }
+
+ static from(source, position) {
+ const buf = new MessageBuffer();
+ buf._buffer = Buffer.from(source);
+ buf._position = position;
+ buf._length = buf._buffer.length;
+ buf._capacity = buf._length;
+ return buf;
+ }
+
+ get position() {
+ return this._position;
+ }
+
+ set position(position) {
+ this._position = position;
+ }
+
+ get length() {
+ return this._length;
+ }
+
+ get data() {
+ return this.getSlice(0, this.length);
+ }
+
+ get buffer() {
+ return this._buffer;
+ }
+
+ getSlice(start, end) {
+ return this._buffer.slice(start, end);
+ }
+
+ writeByte(value) {
+ this.writeNumber(value, BinaryUtils.TYPE_CODE.BYTE);
+ }
+
+ writeShort(value) {
+ this.writeNumber(value, BinaryUtils.TYPE_CODE.SHORT);
+ }
+
+ writeInteger(value) {
+ this.writeNumber(value, BinaryUtils.TYPE_CODE.INTEGER);
+ }
+
+ writeLong(value) {
+ try {
+ if (!Long.isLong(value)) {
+ value = Long.fromValue(value);
+ }
+ }
+ catch (err) {
+ throw Errors.IgniteClientError.valueCastError(value, BinaryUtils.TYPE_CODE.LONG);
+ }
+ const buffer = Buffer.from(value.toBytesLE());
+ this.writeBuffer(buffer);
+ }
+
+ writeFloat(value) {
+ this.writeNumber(value, BinaryUtils.TYPE_CODE.FLOAT);
+ }
+
+ writeDouble(value) {
+ this.writeNumber(value, BinaryUtils.TYPE_CODE.DOUBLE);
+ }
+
+ writeNumber(value, type) {
+ const size = BinaryUtils.getSize(type);
+ this._ensureCapacity(size);
+ try {
+ switch (type) {
+ case BinaryUtils.TYPE_CODE.BYTE:
+ this._buffer.writeInt8(value, this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.SHORT:
+ this._buffer.writeInt16LE(value, this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.INTEGER:
+ this._buffer.writeInt32LE(value, this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.FLOAT:
+ this._buffer.writeFloatLE(value, this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.DOUBLE:
+ this._buffer.writeDoubleLE(value, this._position);
+ break;
+ default:
+ throw Errors.IgniteClientError.internalError();
+ }
+ }
+ catch (err) {
+ throw Errors.IgniteClientError.valueCastError(value, type);
+ }
+ this._position += size;
+ }
+
+ writeBoolean(value) {
+ this.writeByte(value ? BYTE_ONE : BYTE_ZERO);
+ }
+
+ writeChar(value) {
+ this.writeShort(value.charCodeAt(0));
+ }
+
+ writeString(value) {
+ const buffer = Buffer.from(value, BinaryUtils.ENCODING);
+ const length = buffer.length;
+ this.writeInteger(length);
+ if (length > 0) {
+ this.writeBuffer(buffer);
+ }
+ }
+
+ writeDate(value) {
+ this.writeLong(value.getTime());
+ }
+
+ readByte() {
+ return this.readNumber(BinaryUtils.TYPE_CODE.BYTE);
+ }
+
+ readShort() {
+ return this.readNumber(BinaryUtils.TYPE_CODE.SHORT);
+ }
+
+ readInteger() {
+ return this.readNumber(BinaryUtils.TYPE_CODE.INTEGER);
+ }
+
+ readLong() {
+ const size = BinaryUtils.getSize(BinaryUtils.TYPE_CODE.LONG)
+ this._ensureSize(size);
+ const value = Long.fromBytesLE([...this._buffer.slice(this._position, this._position + size)]);
+ this._position += size;
+ return value;
+ }
+
+ readFloat() {
+ return this.readNumber(BinaryUtils.TYPE_CODE.FLOAT);
+ }
+
+ readDouble() {
+ return this.readNumber(BinaryUtils.TYPE_CODE.DOUBLE);
+ }
+
+ readNumber(type) {
+ const size = BinaryUtils.getSize(type);
+ this._ensureSize(size);
+ let value;
+ switch (type) {
+ case BinaryUtils.TYPE_CODE.BYTE:
+ value = this._buffer.readInt8(this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.SHORT:
+ value = this._buffer.readInt16LE(this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.INTEGER:
+ value = this._buffer.readInt32LE(this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.FLOAT:
+ value = this._buffer.readFloatLE(this._position);
+ break;
+ case BinaryUtils.TYPE_CODE.DOUBLE:
+ value = this._buffer.readDoubleLE(this._position);
+ break;
+ default:
+ throw Errors.IgniteClientError.internalError();
+ }
+ this._position += size;
+ return value;
+ }
+
+ readBoolean() {
+ return this.readByte() === BYTE_ONE;
+ }
+
+ readChar() {
+ return String.fromCharCode(this.readShort());
+ }
+
+ readString() {
+ const bytesCount = this.readInteger();
+ this._ensureSize(bytesCount);
+ const result = this._buffer.toString(BinaryUtils.ENCODING, this._position, this._position + bytesCount);
+ this._position += bytesCount;
+ return result;
+ }
+
+ readBuffer(length) {
+ this._ensureSize(length);
+ const result = this._buffer.slice(this._position, this._position + length);
+ this._position += length;
+ return result;
+ }
+
+ readDate() {
+ return new Date(this.readLong().toNumber());
+ }
+
+ writeBuffer(buffer, start = undefined, end = undefined) {
+ if (start === undefined) {
+ start = 0;
+ }
+ if (end === undefined) {
+ end = buffer.length;
+ }
+ const size = end - start;
+ this._ensureCapacity(size);
+ buffer.copy(this._buffer, this._position, start, end);
+ this._position += size;
+ }
+
+ _ensureSize(size) {
+ if (this._position + size > this._length) {
+ throw Errors.IgniteClientError.internalError('Unexpected format of response');
+ }
+ }
+
+ _ensureCapacity(valueSize) {
+ if (valueSize <= 0) {
+ throw Errors.IgniteClientError.internalError();
+ }
+ let newCapacity = this._capacity;
+ while (this._position + valueSize > newCapacity) {
+ newCapacity = newCapacity * 2;
+ }
+ if (this._capacity < newCapacity) {
+ this._buffer = Buffer.concat([this._buffer, Buffer.allocUnsafe(newCapacity - this._capacity)], newCapacity);
+ this._capacity = newCapacity;
+ }
+ if (this._position + valueSize > this._length) {
+ this._length = this._position + valueSize;
+ }
+ }
+}
+
+module.exports = MessageBuffer;
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/package-lock.json
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/package-lock.json b/modules/platforms/nodejs/package-lock.json
new file mode 100644
index 0000000..e62bdbb
--- /dev/null
+++ b/modules/platforms/nodejs/package-lock.json
@@ -0,0 +1,168 @@
+{
+ "name": "apache-ignite-client",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "add-matchers": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/add-matchers/-/add-matchers-0.5.0.tgz",
+ "integrity": "sha1-UCGQ5HUM1XIWGDkyaLYaFXNm52U=",
+ "dev": true
+ },
+ "balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "dev": true
+ },
+ "decimal.js": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.0.tgz",
+ "integrity": "sha512-oTJ6FdBXq28TSDB/e0CfuXWxZE1+Jj3vV9+6eL73d+YpryRqD/+R4O5kAmZjNHiL2zw9lXrnZR8NhJtualzufg=="
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "dev": true
+ },
+ "glob": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
+ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "1.0.0",
+ "inflight": "1.0.6",
+ "inherits": "2.0.3",
+ "minimatch": "3.0.4",
+ "once": "1.4.0",
+ "path-is-absolute": "1.0.1"
+ }
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dev": true,
+ "requires": {
+ "once": "1.4.0",
+ "wrappy": "1.0.2"
+ }
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+ "dev": true
+ },
+ "jasmine": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.1.0.tgz",
+ "integrity": "sha1-K9Wf1+xuwOistk4J9Fpo7SrRlSo=",
+ "dev": true,
+ "requires": {
+ "glob": "7.1.2",
+ "jasmine-core": "3.1.0"
+ }
+ },
+ "jasmine-core": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.1.0.tgz",
+ "integrity": "sha1-pHheE11d9lAk38kiSVPfWFvSdmw=",
+ "dev": true
+ },
+ "jasmine-expect": {
+ "version": "3.8.3",
+ "resolved": "https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-3.8.3.tgz",
+ "integrity": "sha512-VZu4V0O/YWBfUa2dgb9cz/WcQdWRFtdM4SV0Iy+abORLEkqP6vUrMvoFtxP9TJ+Rtor+z6857Vz5ySOaTFEt2A==",
+ "dev": true,
+ "requires": {
+ "add-matchers": "0.5.0"
+ }
+ },
+ "jasmine-reporters": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.1.tgz",
+ "integrity": "sha1-9C1XjplmlhY0MdkRwxZ5cZ+0Ozs=",
+ "dev": true,
+ "requires": {
+ "mkdirp": "0.5.1",
+ "xmldom": "0.1.27"
+ }
+ },
+ "long": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
+ "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+ },
+ "minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "1.1.11"
+ }
+ },
+ "minimist": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+ "dev": true
+ },
+ "mkdirp": {
+ "version": "0.5.1",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+ "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+ "dev": true,
+ "requires": {
+ "minimist": "0.0.8"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dev": true,
+ "requires": {
+ "wrappy": "1.0.2"
+ }
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "dev": true
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
+ },
+ "xmldom": {
+ "version": "0.1.27",
+ "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz",
+ "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=",
+ "dev": true
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/package.json
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/package.json b/modules/platforms/nodejs/package.json
new file mode 100644
index 0000000..4ffb809
--- /dev/null
+++ b/modules/platforms/nodejs/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "apache-ignite-client",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "author": "",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "dependencies": {
+ "long": "latest",
+ "decimal.js": "latest"
+ },
+ "scripts": {
+ "test": "jasmine",
+ "test:examples": "node ./spec/ExamplesExecutor.js Examples",
+ "test:auth_example": "node ./spec/ExamplesExecutor.js AuthExample"
+ },
+ "devDependencies": {
+ "jasmine": "latest",
+ "jasmine-expect": "latest",
+ "jasmine-reporters": "latest"
+ }
+}
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/spec/ExamplesExecutor.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/spec/ExamplesExecutor.js b/modules/platforms/nodejs/spec/ExamplesExecutor.js
new file mode 100644
index 0000000..7f263d9
--- /dev/null
+++ b/modules/platforms/nodejs/spec/ExamplesExecutor.js
@@ -0,0 +1,11 @@
+const Jasmine = require('jasmine');
+
+const jasmine = new Jasmine();
+jasmine.loadConfig({
+ 'spec_dir': 'spec',
+ 'spec_files': [
+ `examples/${process.argv[2]}.spec.js`
+ ],
+ "random": false
+});
+jasmine.execute();
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/spec/README.md
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/spec/README.md b/modules/platforms/nodejs/spec/README.md
new file mode 100644
index 0000000..4b947c4
--- /dev/null
+++ b/modules/platforms/nodejs/spec/README.md
@@ -0,0 +1,42 @@
+# Tests #
+
+NodeJS Client for Apache Ignite contains [Jasmine](https://www.npmjs.com/package/jasmine) tests to check the behavior of the client. the tests include:
+- functional tests which cover all API methods of the client
+- examples executors which run all examples except AuthTlsExample
+- AuthTlsExample executor
+
+## Tests Installation ##
+
+(temporary, while the NPM module is not released on [npmjs](https://www.npmjs.com))
+
+Tests are installed along with the client.
+Follow the [instructions in the main readme](../README.md#installation).
+
+## Tests Running ##
+
+1. Run Apache Ignite server locally or remotely with default configuration.
+2. Set the environment variable:
+ - **APACHE_IGNITE_CLIENT_ENDPOINTS** - comma separated list of Ignite node endpoints.
+ - **APACHE_IGNITE_CLIENT_DEBUG** - (optional) if *true*, tests will display additional output (default: *false*).
+3. Alternatively, instead of the environment variables setting, you can directly specify the values of the corresponding variables in [local_ignite_path/modules/platforms/nodejspec/config.js](./config.js) file.
+4. Run the tests:
+
+### Run Functional Tests ###
+
+Call `npm test` command from `local_ignite_path/modules/platforms/nodejs` folder.
+
+### Run Examples Executors ###
+
+Call `npm run test:examples` command from `local_ignite_path/modules/platforms/nodejs` folder.
+
+### Run AuthTlsExample Executor ###
+
+It requires running Apache Ignite server with non-default configuration (authentication and TLS switched on).
+
+If the server runs locally:
+- setup the server to accept TLS. During the setup use `keystore.jks` and `truststore.jks` certificates from `local_ignite_path/modules/platforms/nodejs/examples/certs/` folder. Password for the files: `123456`
+- switch on the authentication on the server. Use the default username/password.
+
+If the server runs remotely, and/or other certificates are required, and/or non-default username/password is required - see this [instruction](../examples/README.md#additional-setup-for-authtlsexample).
+
+Call `npm run test:auth_example` command from `local_ignite_path/modules/platforms/nodejs` folder.
http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/spec/TestingHelper.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/spec/TestingHelper.js b/modules/platforms/nodejs/spec/TestingHelper.js
new file mode 100644
index 0000000..79a5336
--- /dev/null
+++ b/modules/platforms/nodejs/spec/TestingHelper.js
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+'use strict';
+
+require('jasmine-expect');
+const JasmineReporters = require('jasmine-reporters');
+
+const Util = require('util');
+const exec = require('child_process').exec;
+const config = require('./config');
+const IgniteClient = require('apache-ignite-client');
+const IgniteClientConfiguration = IgniteClient.IgniteClientConfiguration;
+const Errors = IgniteClient.Errors;
+const EnumItem = IgniteClient.EnumItem;
+const Timestamp = IgniteClient.Timestamp;
+const Decimal = IgniteClient.Decimal;
+const BinaryObject = IgniteClient.BinaryObject;
+const ObjectType = IgniteClient.ObjectType;
+
+const TIMEOUT_MS = 60000;
+
+jasmine.getEnv().addReporter(new JasmineReporters.TeamCityReporter());
+
+const dateComparator = (date1, date2) => { return !date1 && !date2 || date1.value === date2.value; };
+const floatComparator = (date1, date2) => { return Math.abs(date1 - date2) < 0.00001; };
+const defaultComparator = (value1, value2) => { return value1 === value2; };
+const enumComparator = (value1, value2) => {
+ return value1.getTypeId() === value2.getTypeId() &&
+ value1.getOrdinal() === value2.getOrdinal(); };
+const decimalComparator = (value1, value2) => {
+ return value1 === null && value2 === null ||
+ value1.equals(value2);
+};
+const timestampComparator = (value1, value2) => {
+ return value1 === null && value2 === null ||
+ dateComparator(value1.getTime(), value2.getTime()) &&
+ value1.getNanos() === value2.getNanos(); };
+
+const numericValueModificator = (data) => { return data > 0 ? data - 10 : data + 10; };
+const charValueModificator = (data) => { return String.fromCharCode(data.charCodeAt(0) + 5); };
+const booleanValueModificator = (data) => { return !data; };
+const stringValueModificator = (data) => { return data + 'xxx'; };
+const dateValueModificator = (data) => { return new Date(data.getTime() + 12345); };
+const UUIDValueModificator = (data) => { return data.reverse(); };
+const enumValueModificator = (data) => { return new EnumItem(data.getTypeId(), data.getOrdinal() + 1); };
+const decimalValueModificator = (data) => { return data.add(12345); };
+const timestampValueModificator = (data) => { return new Timestamp(new Date(data.getTime() + 12345), data.getNanos() + 123); };
+
+const primitiveValues = {
+ [ObjectType.PRIMITIVE_TYPE.BYTE] : {
+ values : [-128, 0, 127],
+ isMapKey : true,
+ modificator : numericValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.SHORT] : {
+ values : [-32768, 0, 32767],
+ isMapKey : true,
+ modificator : numericValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.INTEGER] : {
+ values : [12345, 0, -54321],
+ isMapKey : true,
+ modificator : numericValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.LONG] : {
+ values : [12345678912345, 0, -98765432112345],
+ isMapKey : true,
+ modificator : numericValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.FLOAT] : {
+ values : [-1.155, 0, 123e-5],
+ isMapKey : false,
+ modificator : numericValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.DOUBLE] : {
+ values : [-123e5, 0, 0.0001],
+ typeOptional : true,
+ isMapKey : false,
+ modificator : numericValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.CHAR] : {
+ values : ['a', String.fromCharCode(0x1234)],
+ isMapKey : true,
+ modificator : charValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.BOOLEAN] : {
+ values : [true, false],
+ isMapKey : true,
+ typeOptional : true,
+ modificator : booleanValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.STRING] : {
+ values : ['abc', ''],
+ isMapKey : true,
+ typeOptional : true,
+ modificator : stringValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.UUID] : {
+ values : [
+ [ 18, 70, 2, 119, 154, 254, 198, 254, 195, 146, 33, 60, 116, 230, 0, 146 ],
+ [ 141, 77, 31, 194, 127, 36, 184, 255, 192, 4, 118, 57, 253, 209, 111, 147 ]
+ ],
+ isMapKey : false,
+ modificator : UUIDValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.DATE] : {
+ values : [new Date(), new Date('1995-12-17T03:24:00'), new Date(0)],
+ typeOptional : true,
+ isMapKey : false,
+ modificator : dateValueModificator
+ },
+ // [ObjectType.PRIMITIVE_TYPE.ENUM] : {
+ // values : [new EnumItem(12345, 7), new EnumItem(0, 0)],
+ // typeOptional : true,
+ // isMapKey : false,
+ // modificator : enumValueModificator
+ // },
+ [ObjectType.PRIMITIVE_TYPE.DECIMAL] : {
+ values : [new Decimal('123456789.6789345'), new Decimal(0), new Decimal('-98765.4321e15')],
+ typeOptional : true,
+ isMapKey : false,
+ modificator : decimalValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.TIMESTAMP] : {
+ values : [new Timestamp(new Date().getTime(), 12345), new Timestamp(new Date('1995-12-17T03:24:00').getTime(), 543), new Timestamp(0, 0)],
+ typeOptional : true,
+ isMapKey : false,
+ modificator : timestampValueModificator
+ },
+ [ObjectType.PRIMITIVE_TYPE.TIME] : {
+ values : [new Date(), new Date('1995-12-17T03:24:00'), new Date(123)],
+ isMapKey : false,
+ modificator : dateValueModificator
+ }
+};
+
+const arrayValues = {
+ [ObjectType.PRIMITIVE_TYPE.BYTE_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.BYTE },
+ [ObjectType.PRIMITIVE_TYPE.SHORT_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.SHORT },
+ [ObjectType.PRIMITIVE_TYPE.INTEGER_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.INTEGER },
+ [ObjectType.PRIMITIVE_TYPE.LONG_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.LONG },
+ [ObjectType.PRIMITIVE_TYPE.FLOAT_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.FLOAT },
+ [ObjectType.PRIMITIVE_TYPE.DOUBLE_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.DOUBLE, typeOptional : true },
+ [ObjectType.PRIMITIVE_TYPE.CHAR_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.CHAR },
+ [ObjectType.PRIMITIVE_TYPE.BOOLEAN_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.BOOLEAN, typeOptional : true },
+ [ObjectType.PRIMITIVE_TYPE.STRING_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.STRING, typeOptional : true },
+ [ObjectType.PRIMITIVE_TYPE.UUID_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.UUID },
+ [ObjectType.PRIMITIVE_TYPE.DATE_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.DATE, typeOptional : true },
+ //[ObjectType.PRIMITIVE_TYPE.ENUM_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.ENUM, typeOptional : true },
+ [ObjectType.PRIMITIVE_TYPE.DECIMAL_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.DECIMAL, typeOptional : true },
+ [ObjectType.PRIMITIVE_TYPE.TIMESTAMP_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.TIMESTAMP, typeOptional : true },
+ [ObjectType.PRIMITIVE_TYPE.TIME_ARRAY] : { elemType : ObjectType.PRIMITIVE_TYPE.TIME }
+};
+
+// Helper class for testing apache-ignite-client library.
+// Contains common methods for testing environment initialization and cleanup.
+class TestingHelper {
+ static get TIMEOUT() {
+ return TIMEOUT_MS;
+ }
+
+ static get primitiveValues() {
+ return primitiveValues;
+ }
+
+ static get arrayValues() {
+ return arrayValues;
+ }
+
+ // Initializes testing environment: creates and starts the library client, sets default jasmine test timeout.
+ // Should be called from any test suite beforeAll method.
+ static async init() {
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = TIMEOUT_MS;
+
+ TestingHelper._igniteClient = new IgniteClient();
+ TestingHelper._igniteClient.setDebug(config.debug);
+ await TestingHelper._igniteClient.connect(new IgniteClientConfiguration(...config.endpoints));
+ }
+
+ // Cleans up testing environment.
+ // Should be called from any test suite afterAll method.
+ static async cleanUp() {
+ await TestingHelper.igniteClient.disconnect();
+ }
+
+ static get igniteClient() {
+ return TestingHelper._igniteClient;
+ }
+
+ static async destroyCache(cacheName, done) {
+ try {
+ await TestingHelper.igniteClient.destroyCache(cacheName);
+ }
+ catch (err) {
+ TestingHelper.checkOperationError(err, done);
+ }
+ }
+
+ static executeExample(name, outputChecker) {
+ return new Promise((resolve, reject) => {
+ exec('node ' + name, (error, stdout, stderr) => {
+ TestingHelper.logDebug(stdout);
+ resolve(stdout);
+ })
+ }).
+ then(output => {
+ expect(output).not.toMatch('ERROR:');
+ expect(output).toMatch('Client is started');
+ });
+ }
+
+ static checkOperationError(error, done) {
+ TestingHelper.checkError(error, Errors.OperationError, done)
+ }
+
+ static checkIllegalArgumentError(error, done) {
+ TestingHelper.checkError(error, Errors.IgniteClientError, done)
+ }
+
+ static checkError(error, errorType, done) {
+ if (!(error instanceof errorType)) {
+ done.fail('unexpected error: ' + error);
+ }
+ }
+
+ static logDebug(message) {
+ if (config.debug) {
+ console.log(message);
+ }
+ }
+
+ static printValue(value) {
+ const val = Util.inspect(value, false, null);
+ const length = 500;
+ return val.length > length ? val.substr(0, length) + '...' : val;
+ }
+
+ static async compare(value1, value2) {
+ TestingHelper.logDebug(Util.format('compare: %s and %s', TestingHelper.printValue(value1), TestingHelper.printValue(value2)));
+ if (value1 === undefined || value2 === undefined) {
+ TestingHelper.logDebug(Util.format('compare: unexpected "undefined" value'));
+ return false;
+ }
+ if (value1 === null && value2 === null) {
+ return true;
+ }
+ if (value1 === null && value2 !== null || value1 !== null && value2 === null) {
+ return false;
+ }
+ if (typeof value1 !== typeof value2) {
+ TestingHelper.logDebug(Util.format('compare: value types are different: %s and %s',
+ typeof value1, typeof value2));
+ return false;
+ }
+ if (typeof value1 === 'number') {
+ return floatComparator(value1, value2);
+ }
+ else if (typeof value1 !== 'object') {
+ return defaultComparator(value1, value2);
+ }
+ else if (value1.constructor.name !== value2.constructor.name && !value2 instanceof BinaryObject) {
+ TestingHelper.logDebug(Util.format('compare: value types are different: %s and %s',
+ value1.constructor.name, value2.constructor.name));
+ return false;
+ }
+ else if (value1 instanceof Date && value2 instanceof Date) {
+ return dateComparator(value1, value2);
+ }
+ else if (value1 instanceof EnumItem && value2 instanceof EnumItem) {
+ return enumComparator(value1, value2);
+ }
+ else if (value1 instanceof Decimal && value2 instanceof Decimal) {
+ return decimalComparator(value1, value2);
+ }
+ else if (value1 instanceof Timestamp && value2 instanceof Timestamp) {
+ return timestampComparator(value1, value2);
+ }
+ else if (value1 instanceof Array && value2 instanceof Array) {
+ if (value1.length !== value2.length) {
+ TestingHelper.logDebug(Util.format('compare: array lengths are different'));
+ return false;
+ }
+ for (var i = 0; i < value1.length; i++) {
+ if (!await TestingHelper.compare(value1[i], value2[i])) {
+ TestingHelper.logDebug(Util.format('compare: array elements are different: %s, %s',
+ TestingHelper.printValue(value1[i]), TestingHelper.printValue(value2[i])));
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (value1 instanceof Map && value2 instanceof Map) {
+ if (value1.size !== value2.size) {
+ TestingHelper.logDebug(Util.format('compare: map sizes are different'));
+ return false;
+ }
+ for (var [key, val] of value1) {
+ if (!value2.has(key)) {
+ TestingHelper.logDebug(Util.format('compare: maps are different: %s key is absent', TestingHelper.printValue(key)));
+ return false;
+ }
+ if (!(await TestingHelper.compare(val, value2.get(key)))) {
+ TestingHelper.logDebug(Util.format('compare: map values are different: %s, %s',
+ TestingHelper.printValue(val), TestingHelper.printValue(value2.get(key))));
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (value1 instanceof Set && value2 instanceof Set) {
+ if (value1.size !== value2.size) {
+ TestingHelper.logDebug(Util.format('compare: set sizes are different'));
+ return false;
+ }
+ const value1Arr = [...value1].sort();
+ const value2Arr = [...value2].sort();
+ if (!await TestingHelper.compare(value1Arr, value2Arr)) {
+ TestingHelper.logDebug(Util.format('compare: sets are different: %s and %s',
+ TestingHelper.printValue(value1Arr), TestingHelper.printValue(value2Arr)));
+ return false;
+ }
+ return true;
+ }
+ else if (value2 instanceof BinaryObject) {
+ if (value1 instanceof BinaryObject) {
+ if (value1.getTypeName() !== value2.getTypeName()) {
+ TestingHelper.logDebug(Util.format('compare: binary object type names are different'));
+ return false;
+ }
+ if (!await TestingHelper.compare(value1.getFieldNames(), value2.getFieldNames())) {
+ TestingHelper.logDebug(Util.format('compare: binary object field names are different'));
+ return false;
+ }
+ for (let fieldName of value1.getFieldNames()) {
+ if (!value1.hasField(fieldName) || !value2.hasField(fieldName) ||
+ !await TestingHelper.compare(await value1.getField(fieldName), await value1.getField(fieldName))) {
+ TestingHelper.logDebug(Util.format('compare: binary objects field "%s" values are different', fieldName));
+ return false;
+ }
+ }
+ return true;
+ }
+ else {
+ let value;
+ for (let key of Object.keys(value1)) {
+ value = await value2.getField(key);
+ if (!(await TestingHelper.compare(value1[key], value))) {
+ TestingHelper.logDebug(Util.format('compare: binary object values for key %s are different: %s and %s',
+ TestingHelper.printValue(key), TestingHelper.printValue(value1[key]), TestingHelper.printValue(value)));
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ else {
+ for (let key of Object.keys(value1)) {
+ if (!(await TestingHelper.compare(value1[key], value2[key]))) {
+ TestingHelper.logDebug(Util.format('compare: object values for key %s are different: %s and %s',
+ TestingHelper.printValue(key), TestingHelper.printValue(value1[key]), TestingHelper.printValue(value2[key])));
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
+
+module.exports = TestingHelper;