|
|
|
@ -1,293 +1,186 @@ |
|
|
|
package org.thingsboard.server.queue.util; |
|
|
|
|
|
|
|
import lombok.SneakyThrows; |
|
|
|
import org.junit.Assert; |
|
|
|
import org.junit.Ignore; |
|
|
|
import org.junit.jupiter.api.BeforeAll; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.springframework.util.SerializationUtils; |
|
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
|
import org.thingsboard.server.common.data.DeviceProfile; |
|
|
|
import org.thingsboard.server.common.data.Device; |
|
|
|
import org.thingsboard.server.common.data.FSTUtils; |
|
|
|
import org.thingsboard.server.common.data.StringUtils; |
|
|
|
import org.thingsboard.server.common.data.id.CustomerId; |
|
|
|
import org.thingsboard.server.common.data.id.DeviceId; |
|
|
|
import org.thingsboard.server.common.data.id.OtaPackageId; |
|
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
|
import org.thingsboard.server.common.data.id.UUIDBased; |
|
|
|
import org.thingsboard.server.gen.data.DeviceProto; |
|
|
|
import org.thingsboard.server.gen.data.DeviceProto2; |
|
|
|
|
|
|
|
import java.io.ByteArrayOutputStream; |
|
|
|
import java.io.IOException; |
|
|
|
import java.io.ObjectOutputStream; |
|
|
|
import java.nio.charset.StandardCharsets; |
|
|
|
import java.util.UUID; |
|
|
|
import java.util.function.Consumer; |
|
|
|
import java.util.function.Function; |
|
|
|
import java.util.function.Supplier; |
|
|
|
|
|
|
|
|
|
|
|
public class DataDecodingEncodingServiceTest { |
|
|
|
|
|
|
|
|
|
|
|
private static final int _1M = 1000 * 1000; |
|
|
|
private static String DEVICE_PROFILE_STR = "{\n" + |
|
|
|
" \"name\": \"thermostat\",\n" + |
|
|
|
" \"description\": \"Thermostat device profile\",\n" + |
|
|
|
" \"image\": null,\n" + |
|
|
|
" \"type\": \"DEFAULT\",\n" + |
|
|
|
" \"transportType\": \"DEFAULT\",\n" + |
|
|
|
" \"provisionType\": \"DISABLED\",\n" + |
|
|
|
" \"defaultRuleChainId\": {\n" + |
|
|
|
" \"entityType\": \"RULE_CHAIN\",\n" + |
|
|
|
" \"id\": \"eaaa47b0-e827-11ed-9f13-7f5975adc62f\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"defaultDashboardId\": null,\n" + |
|
|
|
" \"defaultQueueName\": null,\n" + |
|
|
|
" \"profileData\": {\n" + |
|
|
|
" \"configuration\": {\n" + |
|
|
|
" \"type\": \"DEFAULT\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"transportConfiguration\": {\n" + |
|
|
|
" \"type\": \"DEFAULT\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"provisionConfiguration\": {\n" + |
|
|
|
" \"type\": \"DISABLED\",\n" + |
|
|
|
" \"provisionDeviceSecret\": null\n" + |
|
|
|
" },\n" + |
|
|
|
" \"alarms\": [\n" + |
|
|
|
" {\n" + |
|
|
|
" \"id\": \"highTemperatureAlarmID\",\n" + |
|
|
|
" \"alarmType\": \"High Temperature\",\n" + |
|
|
|
" \"createRules\": {\n" + |
|
|
|
" \"MAJOR\": {\n" + |
|
|
|
" \"condition\": {\n" + |
|
|
|
" \"condition\": [\n" + |
|
|
|
" {\n" + |
|
|
|
" \"key\": {\n" + |
|
|
|
" \"type\": \"ATTRIBUTE\",\n" + |
|
|
|
" \"key\": \"temperatureAlarmFlag\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"valueType\": \"BOOLEAN\",\n" + |
|
|
|
" \"value\": null,\n" + |
|
|
|
" \"predicate\": {\n" + |
|
|
|
" \"type\": \"BOOLEAN\",\n" + |
|
|
|
" \"operation\": \"EQUAL\",\n" + |
|
|
|
" \"value\": {\n" + |
|
|
|
" \"defaultValue\": true,\n" + |
|
|
|
" \"userValue\": null,\n" + |
|
|
|
" \"dynamicValue\": null\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" {\n" + |
|
|
|
" \"key\": {\n" + |
|
|
|
" \"type\": \"TIME_SERIES\",\n" + |
|
|
|
" \"key\": \"temperature\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"valueType\": \"NUMERIC\",\n" + |
|
|
|
" \"value\": null,\n" + |
|
|
|
" \"predicate\": {\n" + |
|
|
|
" \"type\": \"NUMERIC\",\n" + |
|
|
|
" \"operation\": \"GREATER\",\n" + |
|
|
|
" \"value\": {\n" + |
|
|
|
" \"defaultValue\": 25,\n" + |
|
|
|
" \"userValue\": null,\n" + |
|
|
|
" \"dynamicValue\": {\n" + |
|
|
|
" \"sourceType\": \"CURRENT_DEVICE\",\n" + |
|
|
|
" \"sourceAttribute\": \"temperatureAlarmThreshold\",\n" + |
|
|
|
" \"inherit\": false\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" ],\n" + |
|
|
|
" \"spec\": {\n" + |
|
|
|
" \"type\": \"SIMPLE\"\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" \"schedule\": null,\n" + |
|
|
|
" \"alarmDetails\": \"Current temperature = ${temperature}\",\n" + |
|
|
|
" \"dashboardId\": null\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" \"clearRule\": {\n" + |
|
|
|
" \"condition\": {\n" + |
|
|
|
" \"condition\": [\n" + |
|
|
|
" {\n" + |
|
|
|
" \"key\": {\n" + |
|
|
|
" \"type\": \"TIME_SERIES\",\n" + |
|
|
|
" \"key\": \"temperature\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"valueType\": \"NUMERIC\",\n" + |
|
|
|
" \"value\": null,\n" + |
|
|
|
" \"predicate\": {\n" + |
|
|
|
" \"type\": \"NUMERIC\",\n" + |
|
|
|
" \"operation\": \"LESS_OR_EQUAL\",\n" + |
|
|
|
" \"value\": {\n" + |
|
|
|
" \"defaultValue\": 25,\n" + |
|
|
|
" \"userValue\": null,\n" + |
|
|
|
" \"dynamicValue\": {\n" + |
|
|
|
" \"sourceType\": \"CURRENT_DEVICE\",\n" + |
|
|
|
" \"sourceAttribute\": \"temperatureAlarmThreshold\",\n" + |
|
|
|
" \"inherit\": false\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" ],\n" + |
|
|
|
" \"spec\": {\n" + |
|
|
|
" \"type\": \"SIMPLE\"\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" \"schedule\": null,\n" + |
|
|
|
" \"alarmDetails\": \"Current temperature = ${temperature}\",\n" + |
|
|
|
" \"dashboardId\": null\n" + |
|
|
|
" },\n" + |
|
|
|
" \"propagate\": false,\n" + |
|
|
|
" \"propagateToOwner\": false,\n" + |
|
|
|
" \"propagateToTenant\": false,\n" + |
|
|
|
" \"propagateRelationTypes\": null\n" + |
|
|
|
" },\n" + |
|
|
|
" {\n" + |
|
|
|
" \"id\": \"lowHumidityAlarmID\",\n" + |
|
|
|
" \"alarmType\": \"Low Humidity\",\n" + |
|
|
|
" \"createRules\": {\n" + |
|
|
|
" \"MINOR\": {\n" + |
|
|
|
" \"condition\": {\n" + |
|
|
|
" \"condition\": [\n" + |
|
|
|
" {\n" + |
|
|
|
" \"key\": {\n" + |
|
|
|
" \"type\": \"ATTRIBUTE\",\n" + |
|
|
|
" \"key\": \"humidityAlarmFlag\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"valueType\": \"BOOLEAN\",\n" + |
|
|
|
" \"value\": null,\n" + |
|
|
|
" \"predicate\": {\n" + |
|
|
|
" \"type\": \"BOOLEAN\",\n" + |
|
|
|
" \"operation\": \"EQUAL\",\n" + |
|
|
|
" \"value\": {\n" + |
|
|
|
" \"defaultValue\": true,\n" + |
|
|
|
" \"userValue\": null,\n" + |
|
|
|
" \"dynamicValue\": null\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" {\n" + |
|
|
|
" \"key\": {\n" + |
|
|
|
" \"type\": \"TIME_SERIES\",\n" + |
|
|
|
" \"key\": \"humidity\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"valueType\": \"NUMERIC\",\n" + |
|
|
|
" \"value\": null,\n" + |
|
|
|
" \"predicate\": {\n" + |
|
|
|
" \"type\": \"NUMERIC\",\n" + |
|
|
|
" \"operation\": \"LESS\",\n" + |
|
|
|
" \"value\": {\n" + |
|
|
|
" \"defaultValue\": 60,\n" + |
|
|
|
" \"userValue\": null,\n" + |
|
|
|
" \"dynamicValue\": {\n" + |
|
|
|
" \"sourceType\": \"CURRENT_DEVICE\",\n" + |
|
|
|
" \"sourceAttribute\": \"humidityAlarmThreshold\",\n" + |
|
|
|
" \"inherit\": false\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" ],\n" + |
|
|
|
" \"spec\": {\n" + |
|
|
|
" \"type\": \"SIMPLE\"\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" \"schedule\": null,\n" + |
|
|
|
" \"alarmDetails\": \"Current humidity = ${humidity}\",\n" + |
|
|
|
" \"dashboardId\": null\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" \"clearRule\": {\n" + |
|
|
|
" \"condition\": {\n" + |
|
|
|
" \"condition\": [\n" + |
|
|
|
" {\n" + |
|
|
|
" \"key\": {\n" + |
|
|
|
" \"type\": \"TIME_SERIES\",\n" + |
|
|
|
" \"key\": \"humidity\"\n" + |
|
|
|
" },\n" + |
|
|
|
" \"valueType\": \"NUMERIC\",\n" + |
|
|
|
" \"value\": null,\n" + |
|
|
|
" \"predicate\": {\n" + |
|
|
|
" \"type\": \"NUMERIC\",\n" + |
|
|
|
" \"operation\": \"GREATER_OR_EQUAL\",\n" + |
|
|
|
" \"value\": {\n" + |
|
|
|
" \"defaultValue\": 60,\n" + |
|
|
|
" \"userValue\": null,\n" + |
|
|
|
" \"dynamicValue\": {\n" + |
|
|
|
" \"sourceType\": \"CURRENT_DEVICE\",\n" + |
|
|
|
" \"sourceAttribute\": \"humidityAlarmThreshold\",\n" + |
|
|
|
" \"inherit\": false\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" }\n" + |
|
|
|
" ],\n" + |
|
|
|
" \"spec\": {\n" + |
|
|
|
" \"type\": \"SIMPLE\"\n" + |
|
|
|
" }\n" + |
|
|
|
" },\n" + |
|
|
|
" \"schedule\": null,\n" + |
|
|
|
" \"alarmDetails\": \"Current humidity = ${humidity}\",\n" + |
|
|
|
" \"dashboardId\": null\n" + |
|
|
|
" },\n" + |
|
|
|
" \"propagate\": false,\n" + |
|
|
|
" \"propagateToOwner\": false,\n" + |
|
|
|
" \"propagateToTenant\": false,\n" + |
|
|
|
" \"propagateRelationTypes\": null\n" + |
|
|
|
" }\n" + |
|
|
|
" ]\n" + |
|
|
|
" },\n" + |
|
|
|
" \"provisionDeviceKey\": null,\n" + |
|
|
|
" \"firmwareId\": null,\n" + |
|
|
|
" \"softwareId\": null,\n" + |
|
|
|
" \"defaultEdgeRuleChainId\": null,\n" + |
|
|
|
" \"externalId\": null,\n" + |
|
|
|
" \"default\": false\n" + |
|
|
|
"}"; |
|
|
|
|
|
|
|
private static DeviceProfile dp; |
|
|
|
private static Device d; |
|
|
|
|
|
|
|
@BeforeAll |
|
|
|
public static void before() { |
|
|
|
dp = JacksonUtil.fromString(DEVICE_PROFILE_STR, DeviceProfile.class); |
|
|
|
d = new Device(); |
|
|
|
d.setId(new DeviceId(UUID.randomUUID())); |
|
|
|
d.setCreatedTime(System.currentTimeMillis()); |
|
|
|
d.setTenantId(new TenantId(UUID.randomUUID())); |
|
|
|
d.setCustomerId(new CustomerId(UUID.randomUUID())); |
|
|
|
d.setName(StringUtils.randomAlphabetic(10)); |
|
|
|
d.setLabel(StringUtils.randomAlphabetic(10)); |
|
|
|
d.setType(StringUtils.randomAlphabetic(10)); |
|
|
|
// d.setAdditionalInfo(JacksonUtil.newObjectNode().put("smth", StringUtils.randomAlphabetic(100)));
|
|
|
|
d.setFirmwareId(new OtaPackageId(UUID.randomUUID())); |
|
|
|
d.setSoftwareId(new OtaPackageId(UUID.randomUUID())); |
|
|
|
d.setExternalId(new DeviceId(UUID.randomUUID())); |
|
|
|
} |
|
|
|
|
|
|
|
@Ignore |
|
|
|
@Test |
|
|
|
void testDecode() { |
|
|
|
void testSerialize() { |
|
|
|
|
|
|
|
Assert.assertArrayEquals(toProto(d), toMapStruct(d)); |
|
|
|
// warmup
|
|
|
|
testDecode("FST", FSTUtils::encode); |
|
|
|
testDecode("Jackson", DataDecodingEncodingServiceTest::serializeJackson); |
|
|
|
testDecode("Default", SerializationUtils::serialize); |
|
|
|
testSerialize("FST", FSTUtils::encode); |
|
|
|
testSerialize("Proto", this::toProto); |
|
|
|
testSerialize("Proto2", this::toProto2); |
|
|
|
testSerialize("MapStruct", this::toMapStruct); |
|
|
|
testSerialize("Jackson", DataDecodingEncodingServiceTest::serializeJackson); |
|
|
|
// testSerialize("Default", SerializationUtils::serialize);
|
|
|
|
|
|
|
|
testDeserialize("FST", FSTUtils.encode(d), FSTUtils::decode); |
|
|
|
testDeserialize("Proto", toProto(d), this::fromProto); |
|
|
|
testDeserialize("Jackson", serializeJackson(d), data -> JacksonUtil.fromBytes(data, Device.class)); |
|
|
|
// testDeserialize("Default", serializeDefault(d), data -> (Device) SerializationUtils.deserialize(data));
|
|
|
|
} |
|
|
|
|
|
|
|
private byte[] toMapStruct(Device device) { |
|
|
|
return DeviceMapper.INSTANCE.map(device).toByteArray(); |
|
|
|
} |
|
|
|
|
|
|
|
private byte[] toProto(Device device) { |
|
|
|
var builder = DeviceProto.newBuilder(); |
|
|
|
builder.setId(device.getId().getId().toString()); |
|
|
|
builder.setCreatedTime(device.getCreatedTime()); |
|
|
|
builder.setTenantId(device.getTenantId().getId().toString()); |
|
|
|
toProto(device::getCustomerId, builder::setCustomerId); |
|
|
|
builder.setName(device.getName()); |
|
|
|
builder.setLabel(device.getLabel()); |
|
|
|
builder.setType(device.getType()); |
|
|
|
toProto(device::getFirmwareId, builder::setFirmwareId); |
|
|
|
toProto(device::getSoftwareId, builder::setSoftwareId); |
|
|
|
toProto(device::getExternalId, builder::setExternalId); |
|
|
|
return builder.build().toByteArray(); |
|
|
|
} |
|
|
|
|
|
|
|
testEncode("FST", FSTUtils.encode(dp), FSTUtils::decode); |
|
|
|
testEncode("Jackson", serializeJackson(dp), data -> JacksonUtil.fromBytes(data, DeviceProfile.class)); |
|
|
|
testEncode("Default", serializeDefault(dp), data -> (DeviceProfile) SerializationUtils.deserialize(data)); |
|
|
|
private byte[] toProto2(Device device) { |
|
|
|
var builder = DeviceProto2.newBuilder(); |
|
|
|
builder.setIdM(device.getId().getId().getMostSignificantBits()); |
|
|
|
builder.setIdL(device.getId().getId().getLeastSignificantBits()); |
|
|
|
builder.setCreatedTime(device.getCreatedTime()); |
|
|
|
builder.setTenantIdM(device.getTenantId().getId().getMostSignificantBits()); |
|
|
|
builder.setTenantIdL(device.getTenantId().getId().getLeastSignificantBits()); |
|
|
|
toProto(device::getCustomerId, builder::setCustomerIdM, builder::setCustomerIdL); |
|
|
|
builder.setName(device.getName()); |
|
|
|
builder.setLabel(device.getLabel()); |
|
|
|
builder.setType(device.getType()); |
|
|
|
toProto(device::getFirmwareId, builder::setFirmwareIdM, builder::setFirmwareIdL); |
|
|
|
toProto(device::getSoftwareId, builder::setSoftwareIdM, builder::setSoftwareIdL); |
|
|
|
toProto(device::getExternalId, builder::setExternalIdM, builder::setExternalIdL); |
|
|
|
return builder.build().toByteArray(); |
|
|
|
} |
|
|
|
|
|
|
|
private void testDecode(String methodName, Function<DeviceProfile, byte[]> serializeFunction) { |
|
|
|
@SneakyThrows |
|
|
|
private Device fromProto(byte[] bytes) { |
|
|
|
DeviceProto dp = DeviceProto.parseFrom(bytes); |
|
|
|
Device d = new Device(); |
|
|
|
d.setId(new DeviceId(UUID.fromString(dp.getId()))); |
|
|
|
d.setCreatedTime(dp.getCreatedTime()); |
|
|
|
d.setTenantId(new TenantId(UUID.fromString(dp.getTenantId()))); |
|
|
|
if (dp.hasCustomerId()) { |
|
|
|
d.setCustomerId(new CustomerId(UUID.fromString(dp.getCustomerId()))); |
|
|
|
} |
|
|
|
if (dp.hasName()) { |
|
|
|
d.setName(dp.getName()); |
|
|
|
} |
|
|
|
if (dp.hasLabel()) { |
|
|
|
d.setLabel(dp.getLabel()); |
|
|
|
} |
|
|
|
if (dp.hasType()) { |
|
|
|
d.setType(dp.getType()); |
|
|
|
} |
|
|
|
if (dp.hasFirmwareId()) { |
|
|
|
d.setFirmwareId(new OtaPackageId(UUID.fromString(dp.getFirmwareId()))); |
|
|
|
} |
|
|
|
if (dp.hasSoftwareId()) { |
|
|
|
d.setSoftwareId(new OtaPackageId(UUID.fromString(dp.getSoftwareId()))); |
|
|
|
} |
|
|
|
if (dp.hasExternalId()) { |
|
|
|
d.setExternalId(new DeviceId(UUID.fromString(dp.getExternalId()))); |
|
|
|
} |
|
|
|
return d; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private void toProto(Supplier<UUIDBased> extractId, Consumer<String> setId) { |
|
|
|
UUIDBased id = extractId.get(); |
|
|
|
if (id != null) { |
|
|
|
setId.accept(id.getId().toString()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void toProto(Supplier<UUIDBased> extractId, Consumer<Long> setIdM, Consumer<Long> setIdL) { |
|
|
|
UUIDBased id = extractId.get(); |
|
|
|
if (id != null) { |
|
|
|
var uuid = id.getId(); |
|
|
|
setIdM.accept(uuid.getMostSignificantBits()); |
|
|
|
setIdL.accept(uuid.getLeastSignificantBits()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void testSerialize(String methodName, Function<Device, byte[]> serializeFunction) { |
|
|
|
// WARMUP;
|
|
|
|
for (int i = 0; i < 1000; i++) { |
|
|
|
dp.setName(Integer.toString(i)); |
|
|
|
serializeFunction.apply(dp); |
|
|
|
for (int i = 0; i < _1M; i++) { |
|
|
|
d.setName(Integer.toString(i)); |
|
|
|
serializeFunction.apply(d); |
|
|
|
} |
|
|
|
long startTs = System.nanoTime(); |
|
|
|
for (int i = 0; i < _1M; i++) { |
|
|
|
dp.setName(Integer.toString(i)); |
|
|
|
serializeFunction.apply(dp); |
|
|
|
d.setName(Integer.toString(i)); |
|
|
|
serializeFunction.apply(d); |
|
|
|
} |
|
|
|
long elapsedTime = System.nanoTime() - startTs; |
|
|
|
System.out.println("S: " + methodName + " SIZE: " + serializeFunction.apply(dp).length + " TIME: " + elapsedTime + "ns or " + (elapsedTime / 1000000) + "ms"); |
|
|
|
System.out.println("S: " + methodName + " SIZE: " + serializeFunction.apply(d).length + " TIME: " + (elapsedTime / 1000000) + "ms"); |
|
|
|
} |
|
|
|
|
|
|
|
private void testEncode(String methodName, byte[] data, Function<byte[], DeviceProfile> deserializeFunction) { |
|
|
|
private void testDeserialize(String methodName, byte[] data, Function<byte[], Device> deserializeFunction) { |
|
|
|
// WARMUP;
|
|
|
|
for (int i = 0; i < 1000; i++) { |
|
|
|
dp.setName(Integer.toString(i)); |
|
|
|
d.setName(Integer.toString(i)); |
|
|
|
deserializeFunction.apply(data); |
|
|
|
} |
|
|
|
long startTs = System.nanoTime(); |
|
|
|
for (int i = 0; i < _1M; i++) { |
|
|
|
dp.setName(Integer.toString(i)); |
|
|
|
d.setName(Integer.toString(i)); |
|
|
|
deserializeFunction.apply(data); |
|
|
|
} |
|
|
|
long elapsedTime = System.nanoTime() - startTs; |
|
|
|
System.out.println("D: " +methodName + " TIME: " + elapsedTime + "ns or " + (elapsedTime / 1000000) + "ms"); |
|
|
|
System.out.println("D: " + methodName + " TIME: " + (elapsedTime / 1000000) + "ms"); |
|
|
|
} |
|
|
|
|
|
|
|
static <T> byte[] serializeDefault(T object) { |
|
|
|
|