Browse Source

Merge branch 'master' into gatewayStateVlad

pull/2627/head
nick 7 years ago
parent
commit
0c2c245e75
  1. 33
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  2. 2
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  3. 43
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  4. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  5. 48
      application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java
  6. 47
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  7. 56
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  8. 5
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  9. 3
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  10. 10
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  11. 3
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  12. 17
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  13. 26
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  14. 55
      application/src/main/java/org/thingsboard/server/utils/JsonUtils.java
  15. 1
      application/src/main/proto/cluster.proto
  16. 2
      application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java
  17. 2
      application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java
  18. 2
      application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java
  19. 2
      application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java
  20. 3
      common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java
  21. 7
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java
  22. 5
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java
  23. 5
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java
  24. 2
      common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java
  25. 69
      common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java
  26. 2
      common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java
  27. 40
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java
  28. 2
      common/transport/transport-api/src/main/proto/transport.proto
  29. 40
      dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java
  30. 17
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  31. 8
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java
  32. 8
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java
  33. 18
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java
  34. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java
  35. 15
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java
  36. 17
      dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java
  37. 118
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java
  38. 34
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java
  39. 1
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
  40. 19
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java
  41. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java
  42. 12
      dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java
  43. 3
      dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java
  44. 8
      dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java
  45. 33
      dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java
  46. 3
      dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java
  47. 4
      dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java
  48. 1
      dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java
  49. 21
      dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java
  50. 3
      dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java
  51. 9
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java
  52. 19
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java
  53. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java
  54. 48
      dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java
  55. 59
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  56. 1
      dao/src/main/resources/cassandra/schema-entities.cql
  57. 2
      dao/src/main/resources/cassandra/schema-ts.cql
  58. 3
      dao/src/main/resources/cassandra/system-data.cql
  59. 251
      dao/src/main/resources/sql/schema-entities-hsql.sql
  60. 1
      dao/src/main/resources/sql/schema-entities.sql
  61. 2
      dao/src/main/resources/sql/schema-timescale.sql
  62. 2
      dao/src/main/resources/sql/schema-ts-hsql.sql
  63. 4
      dao/src/main/resources/sql/schema-ts-psql.sql
  64. 3
      dao/src/main/resources/sql/system-data.sql
  65. 2
      dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java
  66. 2
      dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java
  67. 2
      msa/tb/docker-cassandra/Dockerfile
  68. 13
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java
  69. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java
  70. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java
  71. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  72. 2
      ui/src/app/admin/admin.controller.js
  73. 10
      ui/src/app/admin/outgoing-mail-settings.tpl.html
  74. 1
      ui/src/app/locale/locale.constant-cs_CZ.json
  75. 1
      ui/src/app/locale/locale.constant-de_DE.json
  76. 1
      ui/src/app/locale/locale.constant-el_GR.json
  77. 1
      ui/src/app/locale/locale.constant-en_US.json
  78. 1
      ui/src/app/locale/locale.constant-es_ES.json
  79. 1
      ui/src/app/locale/locale.constant-fa_IR.json
  80. 3469
      ui/src/app/locale/locale.constant-fr_FR.json
  81. 1
      ui/src/app/locale/locale.constant-it_IT.json
  82. 3055
      ui/src/app/locale/locale.constant-ja_JA.json
  83. 1
      ui/src/app/locale/locale.constant-ko_KR.json
  84. 1
      ui/src/app/locale/locale.constant-lv_LV.json
  85. 1
      ui/src/app/locale/locale.constant-ru_RU.json
  86. 1
      ui/src/app/locale/locale.constant-tr_TR.json
  87. 1
      ui/src/app/locale/locale.constant-uk_UA.json
  88. 1
      ui/src/app/locale/locale.constant-zh_CN.json
  89. 1
      ui/src/app/locale/locale.constant-zh_TW.json

33
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -69,6 +69,7 @@ import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import org.thingsboard.server.utils.JsonUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -102,7 +103,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap; private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap;
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final JsonParser jsonParser = new JsonParser();
private int rpcSeq = 0; private int rpcSeq = 0;
private String deviceName; private String deviceName;
@ -327,7 +327,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) { private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) {
JsonObject json = getJsonObject(postAttributes.getKvList()); JsonObject json = JsonUtils.getJsonObject(postAttributes.getKvList());
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(),
TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
pushToRuleEngine(context, tbMsg); pushToRuleEngine(context, tbMsg);
@ -335,7 +335,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
private void handlePostTelemetryRequest(ActorContext context, SessionInfoProto sessionInfo, PostTelemetryMsg postTelemetry) { private void handlePostTelemetryRequest(ActorContext context, SessionInfoProto sessionInfo, PostTelemetryMsg postTelemetry) {
for (TsKvListProto tsKv : postTelemetry.getTsKvListList()) { for (TsKvListProto tsKv : postTelemetry.getTsKvListList()) {
JsonObject json = getJsonObject(tsKv.getKvList()); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList());
TbMsgMetaData metaData = defaultMetaData.copy(); TbMsgMetaData metaData = defaultMetaData.copy();
metaData.putValue("ts", tsKv.getTs() + ""); metaData.putValue("ts", tsKv.getTs() + "");
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, metaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
@ -347,7 +347,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
UUID sessionId = getSessionId(sessionInfo); UUID sessionId = getSessionId(sessionInfo);
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
json.addProperty("method", request.getMethodName()); json.addProperty("method", request.getMethodName());
json.add("params", jsonParser.parse(request.getParams())); json.add("params", JsonUtils.parse(request.getParams()));
TbMsgMetaData requestMetaData = defaultMetaData.copy(); TbMsgMetaData requestMetaData = defaultMetaData.copy();
requestMetaData.putValue("requestId", Integer.toString(request.getRequestId())); requestMetaData.putValue("requestId", Integer.toString(request.getRequestId()));
@ -551,27 +551,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
this.defaultMetaData.putValue("deviceType", deviceType); this.defaultMetaData.putValue("deviceType", deviceType);
} }
private JsonObject getJsonObject(List<KeyValueProto> tsKv) {
JsonObject json = new JsonObject();
for (KeyValueProto kv : tsKv) {
switch (kv.getType()) {
case BOOLEAN_V:
json.addProperty(kv.getKey(), kv.getBoolV());
break;
case LONG_V:
json.addProperty(kv.getKey(), kv.getLongV());
break;
case DOUBLE_V:
json.addProperty(kv.getKey(), kv.getDoubleV());
break;
case STRING_V:
json.addProperty(kv.getKey(), kv.getStringV());
break;
}
}
return json;
}
private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) { private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder() DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionInfo.getSessionIdMSB()) .setSessionIdMSB(sessionInfo.getSessionIdMSB())
@ -643,6 +622,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
builder.setType(KeyValueType.STRING_V); builder.setType(KeyValueType.STRING_V);
builder.setStringV(kvEntry.getStrValue().get()); builder.setStringV(kvEntry.getStrValue().get());
break; break;
case JSON:
builder.setType(KeyValueType.JSON_V);
builder.setJsonV(kvEntry.getJsonValue().get());
break;
} }
return builder.build(); return builder.build();
} }

2
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -642,6 +642,8 @@ public abstract class BaseController {
entityNode.put(attr.getKey(), attr.getDoubleValue().get()); entityNode.put(attr.getKey(), attr.getDoubleValue().get());
} else if (attr.getDataType() == DataType.LONG) { } else if (attr.getDataType() == DataType.LONG) {
entityNode.put(attr.getKey(), attr.getLongValue().get()); entityNode.put(attr.getKey(), attr.getLongValue().get());
} else if (attr.getDataType() == DataType.JSON) {
entityNode.set(attr.getKey(), json.readTree(attr.getJsonValue().get()));
} else { } else {
entityNode.put(attr.getKey(), attr.getValueAsString()); entityNode.put(attr.getKey(), attr.getValueAsString());
} }

43
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -15,12 +15,15 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -56,8 +59,10 @@ import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@ -77,6 +82,7 @@ import org.thingsboard.server.service.telemetry.exception.UncheckedApiException;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
@ -107,6 +113,8 @@ public class TelemetryController extends BaseController {
private ExecutorService executor; private ExecutorService executor;
private static final ObjectMapper mapper = new ObjectMapper();
@PostConstruct @PostConstruct
public void initExecutor() { public void initExecutor() {
executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller")); executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller"));
@ -284,8 +292,7 @@ public class TelemetryController extends BaseController {
if (startTs == null || endTs == null) { if (startTs == null || endTs == null) {
deleteToTs = endTs; deleteToTs = endTs;
return getImmediateDeferredResult("When deleteAllDataForKeys is false, start and end timestamp values shouldn't be empty", HttpStatus.BAD_REQUEST); return getImmediateDeferredResult("When deleteAllDataForKeys is false, start and end timestamp values shouldn't be empty", HttpStatus.BAD_REQUEST);
} } else {
else{
deleteFromTs = startTs; deleteFromTs = startTs;
deleteToTs = endTs; deleteToTs = endTs;
} }
@ -536,8 +543,9 @@ public class TelemetryController extends BaseController {
return new FutureCallback<List<AttributeKvEntry>>() { return new FutureCallback<List<AttributeKvEntry>>() {
@Override @Override
public void onSuccess(List<AttributeKvEntry> attributes) { public void onSuccess(List<AttributeKvEntry> attributes) {
List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(), List<AttributeData> values = attributes.stream().map(attribute ->
attribute.getKey(), attribute.getValue())).collect(Collectors.toList()); new AttributeData(attribute.getLastUpdateTs(), attribute.getKey(), getKvValue(attribute))
).collect(Collectors.toList());
logAttributesRead(user, entityId, scope, keyList, null); logAttributesRead(user, entityId, scope, keyList, null);
response.setResult(new ResponseEntity<>(values, HttpStatus.OK)); response.setResult(new ResponseEntity<>(values, HttpStatus.OK));
} }
@ -639,7 +647,9 @@ public class TelemetryController extends BaseController {
jsonNode.fields().forEachRemaining(entry -> { jsonNode.fields().forEachRemaining(entry -> {
String key = entry.getKey(); String key = entry.getKey();
JsonNode value = entry.getValue(); JsonNode value = entry.getValue();
if (entry.getValue().isTextual()) { if (entry.getValue().isObject() || entry.getValue().isArray()) {
attributes.add(new BaseAttributeKvEntry(new JsonDataEntry(key, toJsonStr(value)), ts));
} else if (entry.getValue().isTextual()) {
if (maxStringValueLength > 0 && entry.getValue().textValue().length() > maxStringValueLength) { if (maxStringValueLength > 0 && entry.getValue().textValue().length() > maxStringValueLength) {
String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", entry.getValue().textValue().length(), key, maxStringValueLength); String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", entry.getValue().textValue().length(), key, maxStringValueLength);
throw new UncheckedApiException(new InvalidParametersException(message)); throw new UncheckedApiException(new InvalidParametersException(message));
@ -659,4 +669,27 @@ public class TelemetryController extends BaseController {
}); });
return attributes; return attributes;
} }
private String toJsonStr(JsonNode value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
throw new JsonParseException("Can't parse jsonValue: " + value, e);
}
}
private JsonNode toJsonNode(String value) {
try {
return mapper.readTree(value);
} catch (IOException e) {
throw new JsonParseException("Can't parse jsonValue: " + value, e);
}
}
private Object getKvValue(KvEntry entry) {
if (entry.getDataType() == DataType.JSON) {
return toJsonNode(entry.getJsonValue().get());
}
return entry.getValue();
}
} }

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -136,6 +136,7 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ...");
databaseTsUpgradeService.upgradeDatabase("2.4.3"); databaseTsUpgradeService.upgradeDatabase("2.4.3");
databaseEntitiesUpgradeService.upgradeDatabase("2.4.3");
log.info("Updating system data..."); log.info("Updating system data...");

48
application/src/main/java/org/thingsboard/server/service/install/AbstractCassandraDatabaseUpgradeService.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.service.install.cql.CQLStatementsParser;
import java.nio.file.Path;
import java.util.List;
@Slf4j
public abstract class AbstractCassandraDatabaseUpgradeService {
@Autowired
protected CassandraCluster cluster;
@Autowired
@Qualifier("CassandraInstallCluster")
private CassandraInstallCluster installCluster;
protected void loadCql(Path cql) throws Exception {
List<String> statements = new CQLStatementsParser(cql).getStatements();
statements.forEach(statement -> {
installCluster.getSession().execute(statement);
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
}
});
Thread.sleep(5000);
}
}

47
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -19,20 +19,15 @@ import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.exceptions.InvalidQueryException; import com.datastax.driver.core.exceptions.InvalidQueryException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.service.install.cql.CQLStatementsParser;
import org.thingsboard.server.service.install.cql.CassandraDbHelper; import org.thingsboard.server.service.install.cql.CassandraDbHelper;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.List;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSET; import static org.thingsboard.server.service.install.DatabaseHelper.ASSET;
@ -59,17 +54,10 @@ import static org.thingsboard.server.service.install.DatabaseHelper.TYPE;
@NoSqlDao @NoSqlDao
@Profile("install") @Profile("install")
@Slf4j @Slf4j
public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeService { public class CassandraDatabaseUpgradeService extends AbstractCassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeService {
private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
@Autowired
private CassandraCluster cluster;
@Autowired
@Qualifier("CassandraInstallCluster")
private CassandraInstallCluster installCluster;
@Autowired @Autowired
private DashboardService dashboardService; private DashboardService dashboardService;
@ -264,7 +252,8 @@ public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeS
try { try {
cluster.getSession().execute(updateDeviceTableStmt); cluster.getSession().execute(updateDeviceTableStmt);
Thread.sleep(2500); Thread.sleep(2500);
} catch (InvalidQueryException e) {} } catch (InvalidQueryException e) {
}
log.info("Schema updated."); log.info("Schema updated.");
break; break;
case "2.4.1": case "2.4.1":
@ -275,7 +264,8 @@ public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeS
cluster.getSession().execute(updateAssetTableStmt); cluster.getSession().execute(updateAssetTableStmt);
Thread.sleep(2500); Thread.sleep(2500);
log.info("Assets updated."); log.info("Assets updated.");
} catch (InvalidQueryException e) {} } catch (InvalidQueryException e) {
}
log.info("Schema updated."); log.info("Schema updated.");
break; break;
case "2.4.2": case "2.4.2":
@ -286,24 +276,25 @@ public class CassandraDatabaseUpgradeService implements DatabaseEntitiesUpgradeS
cluster.getSession().execute(updateAlarmTableStmt); cluster.getSession().execute(updateAlarmTableStmt);
Thread.sleep(2500); Thread.sleep(2500);
log.info("Alarms updated."); log.info("Alarms updated.");
} catch (InvalidQueryException e) {} } catch (InvalidQueryException e) {
}
log.info("Schema updated.");
break;
case "2.4.3":
log.info("Updating schema ...");
String updateAttributeKvTableStmt = "alter table attributes_kv_cf add json_v text";
try {
log.info("Updating attributes ...");
cluster.getSession().execute(updateAttributeKvTableStmt);
Thread.sleep(2500);
log.info("Attributes updated.");
} catch (InvalidQueryException e) {
}
log.info("Schema updated."); log.info("Schema updated.");
break; break;
default: default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
} }
}
private void loadCql(Path cql) throws Exception {
List<String> statements = new CQLStatementsParser(cql).getStatements();
statements.forEach(statement -> {
installCluster.getSession().execute(statement);
try {
Thread.sleep(2500);
} catch (InterruptedException e) {}
});
Thread.sleep(5000);
} }
} }

56
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import com.datastax.driver.core.exceptions.InvalidQueryException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.NoSqlTsDao;
@Service
@NoSqlTsDao
@Profile("install")
@Slf4j
public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabaseUpgradeService implements DatabaseTsUpgradeService {
@Override
public void upgradeDatabase(String fromVersion) throws Exception {
switch (fromVersion) {
case "2.4.3":
log.info("Updating schema ...");
String updateTsKvTableStmt = "alter table ts_kv_cf add json_v text";
String updateTsKvLatestTableStmt = "alter table ts_kv_latest_cf add json_v text";
try {
log.info("Updating ts ...");
cluster.getSession().execute(updateTsKvTableStmt);
Thread.sleep(2500);
log.info("Ts updated.");
log.info("Updating ts latest ...");
cluster.getSession().execute(updateTsKvLatestTableStmt);
Thread.sleep(2500);
log.info("Ts latest updated.");
} catch (InvalidQueryException e) {
}
log.info("Schema updated.");
break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
}
}
}

5
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -106,9 +106,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
node.put("smtpHost", "localhost"); node.put("smtpHost", "localhost");
node.put("smtpPort", "25"); node.put("smtpPort", "25");
node.put("timeout", "10000"); node.put("timeout", "10000");
node.put("enableTls", "false"); node.put("enableTls", false);
node.put("username", ""); node.put("username", "");
node.put("password", ""); //NOSONAR, key used to identify password field (not password value itself) node.put("password", "");
node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself)
mailSettings.setJsonValue(node); mailSettings.setJsonValue(node);
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
} }

3
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -101,6 +101,9 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeDropStatement(conn, DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE); executeDropStatement(conn, DROP_FUNCTION_CREATE_NEW_TS_KV_LATEST_TABLE);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;");
executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;");
log.info("schema timeseries updated!"); log.info("schema timeseries updated!");
} }
} }

10
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -207,6 +207,16 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Schema updated."); log.info("Schema updated.");
} }
break; break;
case "2.4.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;");
} catch (Exception e) {
}
log.info("Schema updated.");
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

3
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -104,6 +104,9 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV); executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TENANT_TS_KV);
executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST); executeDropStatement(conn, DROP_FUNCTION_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN json_v json;");
executeQuery(conn, "ALTER TABLE ts_kv_latest ADD COLUMN json_v json;");
log.info("schema timeseries updated!"); log.info("schema timeseries updated!");
} }
} }

17
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -103,7 +103,18 @@ public class DefaultMailService implements MailService {
javaMailProperties.put(MAIL_PROP + protocol + ".port", jsonConfig.get("smtpPort").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".port", jsonConfig.get("smtpPort").asText());
javaMailProperties.put(MAIL_PROP + protocol + ".timeout", jsonConfig.get("timeout").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".timeout", jsonConfig.get("timeout").asText());
javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText()))); javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText())));
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", jsonConfig.has("enableTls") ? jsonConfig.get("enableTls").asText() : "false"); boolean enableTls = false;
if (jsonConfig.has("enableTls")) {
if (jsonConfig.get("enableTls").isBoolean() && jsonConfig.get("enableTls").booleanValue()) {
enableTls = true;
} else if (jsonConfig.get("enableTls").isTextual()) {
enableTls = "true".equalsIgnoreCase(jsonConfig.get("enableTls").asText());
}
}
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", enableTls);
if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText());
}
return javaMailProperties; return javaMailProperties;
} }
@ -213,7 +224,7 @@ public class DefaultMailService implements MailService {
} }
@Override @Override
public void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException { public void sendAccountLockoutEmail(String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException {
String subject = messages.getMessage("account.lockout.subject", null, Locale.US); String subject = messages.getMessage("account.lockout.subject", null, Locale.US);
Map<String, Object> model = new HashMap<String, Object>(); Map<String, Object> model = new HashMap<String, Object>();
@ -244,7 +255,7 @@ public class DefaultMailService implements MailService {
} }
private static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation, private static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
String encoding, Map<String, Object> model) throws VelocityException { String encoding, Map<String, Object> model) throws VelocityException {
StringWriter result = new StringWriter(); StringWriter result = new StringWriter();
mergeTemplate(velocityEngine, templateLocation, encoding, model, result); mergeTemplate(velocityEngine, templateLocation, encoding, model, result);

26
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -24,9 +24,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -36,7 +36,20 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
@ -105,7 +118,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Autowired @Autowired
@Lazy @Lazy
private ActorService actorService; private ActorService actorService;
private ExecutorService tsCallBackExecutor; private ExecutorService tsCallBackExecutor;
private ExecutorService wsCallBackExecutor; private ExecutorService wsCallBackExecutor;
@ -692,6 +705,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
Optional<Double> doubleValue = attr.getDoubleValue(); Optional<Double> doubleValue = attr.getDoubleValue();
doubleValue.ifPresent(dataBuilder::setDoubleValue); doubleValue.ifPresent(dataBuilder::setDoubleValue);
break; break;
case JSON:
Optional<String> jsonValue = attr.getJsonValue();
jsonValue.ifPresent(dataBuilder::setJsonValue);
break;
case STRING: case STRING:
Optional<String> stringValue = attr.getStrValue(); Optional<String> stringValue = attr.getStrValue();
stringValue.ifPresent(dataBuilder::setStrValue); stringValue.ifPresent(dataBuilder::setStrValue);
@ -724,6 +741,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
case STRING: case STRING:
entry = new StringDataEntry(proto.getKey(), proto.getStrValue()); entry = new StringDataEntry(proto.getKey(), proto.getStrValue());
break; break;
case JSON:
entry = new JsonDataEntry(proto.getKey(), proto.getJsonValue());
break;
} }
return entry; return entry;
} }

55
application/src/main/java/org/thingsboard/server/utils/JsonUtils.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.utils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import java.util.List;
public class JsonUtils {
private static final JsonParser jsonParser = new JsonParser();
public static JsonObject getJsonObject(List<KeyValueProto> tsKv) {
JsonObject json = new JsonObject();
for (KeyValueProto kv : tsKv) {
switch (kv.getType()) {
case BOOLEAN_V:
json.addProperty(kv.getKey(), kv.getBoolV());
break;
case LONG_V:
json.addProperty(kv.getKey(), kv.getLongV());
break;
case DOUBLE_V:
json.addProperty(kv.getKey(), kv.getDoubleV());
break;
case STRING_V:
json.addProperty(kv.getKey(), kv.getStringV());
break;
case JSON_V:
json.add(kv.getKey(), jsonParser.parse(kv.getJsonV()));
break;
}
}
return json;
}
public static JsonElement parse(String params) {
return jsonParser.parse(params);
}
}

1
application/src/main/proto/cluster.proto

@ -125,6 +125,7 @@ message KeyValueProto {
int64 longValue = 5; int64 longValue = 5;
double doubleValue = 6; double doubleValue = 6;
bool boolValue = 7; bool boolValue = 7;
string jsonValue = 8;
} }
message FromDeviceRPCResponseProto { message FromDeviceRPCResponseProto {

2
application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java

@ -30,7 +30,7 @@ public class ControllerSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql", "sql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");
} }

2
application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java

@ -29,7 +29,7 @@ public class MqttSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql", "sql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");
} }

2
application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java

@ -30,7 +30,7 @@ public class RuleEngineSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql", "sql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");
} }

2
application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java

@ -31,7 +31,7 @@ public class SystemSqlTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql", "sql/drop-all-tables.sql",
"sql-test.properties"); "sql-test.properties");

3
common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java

@ -35,6 +35,7 @@ import java.util.function.Consumer;
@Slf4j @Slf4j
public abstract class SearchTextBasedWithAdditionalInfo<I extends UUIDBased> extends SearchTextBased<I> implements HasAdditionalInfo { public abstract class SearchTextBasedWithAdditionalInfo<I extends UUIDBased> extends SearchTextBased<I> implements HasAdditionalInfo {
private static final ObjectMapper mapper = new ObjectMapper();
private transient JsonNode additionalInfo; private transient JsonNode additionalInfo;
@JsonIgnore @JsonIgnore
private byte[] additionalInfoBytes; private byte[] additionalInfoBytes;
@ -97,7 +98,7 @@ public abstract class SearchTextBasedWithAdditionalInfo<I extends UUIDBased> ext
public static void setJson(JsonNode json, Consumer<JsonNode> jsonConsumer, Consumer<byte[]> bytesConsumer) { public static void setJson(JsonNode json, Consumer<JsonNode> jsonConsumer, Consumer<byte[]> bytesConsumer) {
jsonConsumer.accept(json); jsonConsumer.accept(json);
try { try {
bytesConsumer.accept(new ObjectMapper().writeValueAsBytes(json)); bytesConsumer.accept(mapper.writeValueAsBytes(json));
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
log.warn("Can't serialize json data: ", e); log.warn("Can't serialize json data: ", e);
} }

7
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseAttributeKvEntry.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.common.data.kv; package org.thingsboard.server.common.data.kv;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.Optional; import java.util.Optional;
/** /**
@ -65,6 +67,11 @@ public class BaseAttributeKvEntry implements AttributeKvEntry {
return kv.getDoubleValue(); return kv.getDoubleValue();
} }
@Override
public Optional<String> getJsonValue() {
return kv.getJsonValue();
}
@Override @Override
public String getValueAsString() { public String getValueAsString() {
return kv.getValueAsString(); return kv.getValueAsString();

5
common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicKvEntry.java

@ -51,6 +51,11 @@ public abstract class BasicKvEntry implements KvEntry {
return Optional.ofNullable(null); return Optional.ofNullable(null);
} }
@Override
public Optional<String> getJsonValue() {
return Optional.ofNullable(null);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

5
common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java

@ -58,6 +58,11 @@ public class BasicTsKvEntry implements TsKvEntry {
return kv.getDoubleValue(); return kv.getDoubleValue();
} }
@Override
public Optional<String> getJsonValue() {
return kv.getJsonValue();
}
@Override @Override
public Object getValue() { public Object getValue() {
return kv.getValue(); return kv.getValue();

2
common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java

@ -17,6 +17,6 @@ package org.thingsboard.server.common.data.kv;
public enum DataType { public enum DataType {
STRING, LONG, BOOLEAN, DOUBLE; STRING, LONG, BOOLEAN, DOUBLE, JSON;
} }

69
common/data/src/main/java/org/thingsboard/server/common/data/kv/JsonDataEntry.java

@ -0,0 +1,69 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.kv;
import java.util.Objects;
import java.util.Optional;
public class JsonDataEntry extends BasicKvEntry {
private final String value;
public JsonDataEntry(String key, String value) {
super(key);
this.value = value;
}
@Override
public DataType getDataType() {
return DataType.JSON;
}
@Override
public Optional<String> getJsonValue() {
return Optional.ofNullable(value);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof JsonDataEntry)) return false;
if (!super.equals(o)) return false;
JsonDataEntry that = (JsonDataEntry) o;
return Objects.equals(value, that.value);
}
@Override
public Object getValue() {
return value;
}
@Override
public int hashCode() {
return Objects.hash(super.hashCode(), value);
}
@Override
public String toString() {
return "JsonDataEntry{" +
"value=" + value +
"} " + super.toString();
}
@Override
public String getValueAsString() {
return value;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/kv/KvEntry.java

@ -37,6 +37,8 @@ public interface KvEntry extends Serializable {
Optional<Double> getDoubleValue(); Optional<Double> getDoubleValue();
Optional<String> getJsonValue();
String getValueAsString(); String getValueAsString();
Object getValue(); Object getValue();

40
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java

@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
@ -59,6 +60,7 @@ import java.util.stream.Collectors;
public class JsonConverter { public class JsonConverter {
private static final Gson GSON = new Gson(); private static final Gson GSON = new Gson();
private static final JsonParser JSON_PARSER = new JsonParser();
private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; private static final String CAN_T_PARSE_VALUE = "Can't parse value: ";
private static final String DEVICE_PROPERTY = "device"; private static final String DEVICE_PROPERTY = "device";
@ -204,6 +206,14 @@ public class JsonConverter {
} else if (!value.isJsonNull()) { } else if (!value.isJsonNull()) {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value);
} }
} else if (element.isJsonObject() || element.isJsonArray()) {
result.add(KeyValueProto
.newBuilder()
.setKey(valueEntry
.getKey())
.setType(KeyValueType.JSON_V)
.setJsonV(element.toString())
.build());
} else if (!element.isJsonNull()) { } else if (!element.isJsonNull()) {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element);
} }
@ -354,6 +364,9 @@ public class JsonConverter {
case LONG_V: case LONG_V:
json.addProperty(name, entry.getLongV()); json.addProperty(name, entry.getLongV());
break; break;
case JSON_V:
json.add(name, JSON_PARSER.parse(entry.getJsonV()));
break;
} }
} }
@ -363,47 +376,48 @@ public class JsonConverter {
private static Consumer<TsKvProto> addToObjectFromProto(JsonObject result) { private static Consumer<TsKvProto> addToObjectFromProto(JsonObject result) {
return de -> { return de -> {
JsonPrimitive value;
switch (de.getKv().getType()) { switch (de.getKv().getType()) {
case BOOLEAN_V: case BOOLEAN_V:
value = new JsonPrimitive(de.getKv().getBoolV()); result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getBoolV()));
break; break;
case DOUBLE_V: case DOUBLE_V:
value = new JsonPrimitive(de.getKv().getDoubleV()); result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getDoubleV()));
break; break;
case LONG_V: case LONG_V:
value = new JsonPrimitive(de.getKv().getLongV()); result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getLongV()));
break; break;
case STRING_V: case STRING_V:
value = new JsonPrimitive(de.getKv().getStringV()); result.add(de.getKv().getKey(), new JsonPrimitive(de.getKv().getStringV()));
break; break;
case JSON_V:
result.add(de.getKv().getKey(), JSON_PARSER.parse(de.getKv().getJsonV()));
default: default:
throw new IllegalArgumentException("Unsupported data type: " + de.getKv().getType()); throw new IllegalArgumentException("Unsupported data type: " + de.getKv().getType());
} }
result.add(de.getKv().getKey(), value);
}; };
} }
private static Consumer<AttributeKvEntry> addToObject(JsonObject result) { private static Consumer<AttributeKvEntry> addToObject(JsonObject result) {
return de -> { return de -> {
JsonPrimitive value;
switch (de.getDataType()) { switch (de.getDataType()) {
case BOOLEAN: case BOOLEAN:
value = new JsonPrimitive(de.getBooleanValue().get()); result.add(de.getKey(), new JsonPrimitive(de.getBooleanValue().get()));
break; break;
case DOUBLE: case DOUBLE:
value = new JsonPrimitive(de.getDoubleValue().get()); result.add(de.getKey(), new JsonPrimitive(de.getDoubleValue().get()));
break; break;
case LONG: case LONG:
value = new JsonPrimitive(de.getLongValue().get()); result.add(de.getKey(), new JsonPrimitive(de.getLongValue().get()));
break; break;
case STRING: case STRING:
value = new JsonPrimitive(de.getStrValue().get()); result.add(de.getKey(), new JsonPrimitive(de.getStrValue().get()));
break;
case JSON:
result.add(de.getKey(), JSON_PARSER.parse(de.getJsonValue().get()));
break; break;
default: default:
throw new IllegalArgumentException("Unsupported data type: " + de.getDataType()); throw new IllegalArgumentException("Unsupported data type: " + de.getDataType());
} }
result.add(de.getKey(), value);
}; };
} }
@ -464,6 +478,8 @@ public class JsonConverter {
} else { } else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value);
} }
} else if (element.isJsonObject() || element.isJsonArray()) {
result.add(new JsonDataEntry(valueEntry.getKey(), element.toString()));
} else { } else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + element);
} }

2
common/transport/transport-api/src/main/proto/transport.proto

@ -47,6 +47,7 @@ enum KeyValueType {
LONG_V = 1; LONG_V = 1;
DOUBLE_V = 2; DOUBLE_V = 2;
STRING_V = 3; STRING_V = 3;
JSON_V = 4;
} }
message KeyValueProto { message KeyValueProto {
@ -56,6 +57,7 @@ message KeyValueProto {
int64 long_v = 4; int64 long_v = 4;
double double_v = 5; double double_v = 5;
string string_v = 6; string string_v = 6;
string json_v = 7;
} }
message TsKvProto { message TsKvProto {

40
dao/src/main/java/org/thingsboard/server/dao/attributes/CassandraBaseAttributesDao.java

@ -112,31 +112,18 @@ public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implem
@Override @Override
public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) { public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) {
BoundStatement stmt = getSaveStmt().bind(); BoundStatement stmt = getSaveStmt().bind()
stmt.setString(0, entityId.getEntityType().name()); .setString(0, entityId.getEntityType().name())
stmt.setUUID(1, entityId.getId()); .setUUID(1, entityId.getId())
stmt.setString(2, attributeType); .setString(2, attributeType)
stmt.setString(3, attribute.getKey()); .setString(3, attribute.getKey())
stmt.setLong(4, attribute.getLastUpdateTs()); .setLong(4, attribute.getLastUpdateTs())
stmt.setString(5, attribute.getStrValue().orElse(null)); .set(5, attribute.getStrValue().orElse(null), String.class)
Optional<Boolean> booleanValue = attribute.getBooleanValue(); .set(6, attribute.getBooleanValue().orElse(null), Boolean.class)
if (booleanValue.isPresent()) { .set(7, attribute.getLongValue().orElse(null), Long.class)
stmt.setBool(6, booleanValue.get()); .set(8, attribute.getDoubleValue().orElse(null), Double.class)
} else { .set(9, attribute.getJsonValue().orElse(null), String.class);
stmt.setToNull(6);
}
Optional<Long> longValue = attribute.getLongValue();
if (longValue.isPresent()) {
stmt.setLong(7, longValue.get());
} else {
stmt.setToNull(7);
}
Optional<Double> doubleValue = attribute.getDoubleValue();
if (doubleValue.isPresent()) {
stmt.setDouble(8, doubleValue.get());
} else {
stmt.setToNull(8);
}
log.trace("Generated save stmt [{}] for entityId {} and attributeType {} and attribute", stmt, entityId, attributeType, attribute); log.trace("Generated save stmt [{}] for entityId {} and attributeType {} and attribute", stmt, entityId, attributeType, attribute);
return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null);
} }
@ -172,8 +159,9 @@ public class CassandraBaseAttributesDao extends CassandraAbstractAsyncDao implem
"," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.BOOLEAN_VALUE_COLUMN +
"," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN +
"," + ModelConstants.DOUBLE_VALUE_COLUMN + "," + ModelConstants.DOUBLE_VALUE_COLUMN +
"," + ModelConstants.JSON_VALUE_COLUMN +
")" + ")" +
" VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)"); " VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
} }
return saveStmt; return saveStmt;
} }

17
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -369,17 +369,18 @@ public class ModelConstants {
public static final String STRING_VALUE_COLUMN = "str_v"; public static final String STRING_VALUE_COLUMN = "str_v";
public static final String LONG_VALUE_COLUMN = "long_v"; public static final String LONG_VALUE_COLUMN = "long_v";
public static final String DOUBLE_VALUE_COLUMN = "dbl_v"; public static final String DOUBLE_VALUE_COLUMN = "dbl_v";
public static final String JSON_VALUE_COLUMN = "json_v";
protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN}; protected static final String[] NONE_AGGREGATION_COLUMNS = new String[]{LONG_VALUE_COLUMN, DOUBLE_VALUE_COLUMN, BOOLEAN_VALUE_COLUMN, STRING_VALUE_COLUMN, JSON_VALUE_COLUMN, KEY_COLUMN, TS_COLUMN};
protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN)}; protected static final String[] COUNT_AGGREGATION_COLUMNS = new String[]{count(LONG_VALUE_COLUMN), count(DOUBLE_VALUE_COLUMN), count(BOOLEAN_VALUE_COLUMN), count(STRING_VALUE_COLUMN), count(JSON_VALUE_COLUMN)};
protected static final String[] MIN_AGGREGATION_COLUMNS = ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, protected static final String[] MIN_AGGREGATION_COLUMNS =
new String[]{min(LONG_VALUE_COLUMN), min(DOUBLE_VALUE_COLUMN), min(BOOLEAN_VALUE_COLUMN), min(STRING_VALUE_COLUMN)}); ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, new String[]{min(LONG_VALUE_COLUMN), min(DOUBLE_VALUE_COLUMN), min(BOOLEAN_VALUE_COLUMN), min(STRING_VALUE_COLUMN), min(JSON_VALUE_COLUMN)});
protected static final String[] MAX_AGGREGATION_COLUMNS = ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, protected static final String[] MAX_AGGREGATION_COLUMNS =
new String[]{max(LONG_VALUE_COLUMN), max(DOUBLE_VALUE_COLUMN), max(BOOLEAN_VALUE_COLUMN), max(STRING_VALUE_COLUMN)}); ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, new String[]{max(LONG_VALUE_COLUMN), max(DOUBLE_VALUE_COLUMN), max(BOOLEAN_VALUE_COLUMN), max(STRING_VALUE_COLUMN), max(JSON_VALUE_COLUMN)});
protected static final String[] SUM_AGGREGATION_COLUMNS = ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, protected static final String[] SUM_AGGREGATION_COLUMNS =
new String[]{sum(LONG_VALUE_COLUMN), sum(DOUBLE_VALUE_COLUMN)}); ArrayUtils.addAll(COUNT_AGGREGATION_COLUMNS, new String[]{sum(LONG_VALUE_COLUMN), sum(DOUBLE_VALUE_COLUMN)});
protected static final String[] AVG_AGGREGATION_COLUMNS = SUM_AGGREGATION_COLUMNS; protected static final String[] AVG_AGGREGATION_COLUMNS = SUM_AGGREGATION_COLUMNS;
public static String min(String s) { public static String min(String s) {

8
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractTsKvEntity.java

@ -19,6 +19,7 @@ import lombok.Data;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
@ -29,12 +30,12 @@ import javax.persistence.Column;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import javax.persistence.Transient; import javax.persistence.Transient;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.JSON_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN;
@ -68,6 +69,9 @@ public abstract class AbstractTsKvEntity implements ToData<TsKvEntry> {
@Column(name = DOUBLE_VALUE_COLUMN) @Column(name = DOUBLE_VALUE_COLUMN)
protected Double doubleValue; protected Double doubleValue;
@Column(name = JSON_VALUE_COLUMN)
protected String jsonValue;
@Transient @Transient
protected String strKey; protected String strKey;
@ -93,6 +97,8 @@ public abstract class AbstractTsKvEntity implements ToData<TsKvEntry> {
kvEntry = new DoubleDataEntry(strKey, doubleValue); kvEntry = new DoubleDataEntry(strKey, doubleValue);
} else if (booleanValue != null) { } else if (booleanValue != null) {
kvEntry = new BooleanDataEntry(strKey, booleanValue); kvEntry = new BooleanDataEntry(strKey, booleanValue);
} else if (jsonValue != null) {
kvEntry = new JsonDataEntry(strKey, jsonValue);
} }
return new BasicTsKvEntry(ts, kvEntry); return new BasicTsKvEntry(ts, kvEntry);
} }

8
dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
@ -33,6 +34,7 @@ import java.io.Serializable;
import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.BOOLEAN_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.DOUBLE_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.JSON_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.LAST_UPDATE_TS_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.LAST_UPDATE_TS_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.LONG_VALUE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUMN;
@ -57,6 +59,9 @@ public class AttributeKvEntity implements ToData<AttributeKvEntry>, Serializable
@Column(name = DOUBLE_VALUE_COLUMN) @Column(name = DOUBLE_VALUE_COLUMN)
private Double doubleValue; private Double doubleValue;
@Column(name = JSON_VALUE_COLUMN)
private String jsonValue;
@Column(name = LAST_UPDATE_TS_COLUMN) @Column(name = LAST_UPDATE_TS_COLUMN)
private Long lastUpdateTs; private Long lastUpdateTs;
@ -71,7 +76,10 @@ public class AttributeKvEntity implements ToData<AttributeKvEntry>, Serializable
kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue);
} else if (longValue != null) { } else if (longValue != null) {
kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); kvEntry = new LongDataEntry(id.getAttributeKey(), longValue);
} else if (jsonValue != null) {
kvEntry = new JsonDataEntry(id.getAttributeKey(), jsonValue);
} }
return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); return new BaseAttributeKvEntry(kvEntry, lastUpdateTs);
} }
} }

18
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/hsql/TsKvEntity.java

@ -16,30 +16,16 @@
package org.thingsboard.server.dao.model.sqlts.hsql; package org.thingsboard.server.dao.model.sqlts.hsql;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.ToData;
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.IdClass; import javax.persistence.IdClass;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
@Data @Data
@ -98,12 +84,14 @@ public final class TsKvEntity extends AbstractTsKvEntity implements ToData<TsKvE
} }
} }
public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount) {
if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) {
if (booleanValueCount != 0) { if (booleanValueCount != 0) {
this.longValue = booleanValueCount; this.longValue = booleanValueCount;
} else if (strValueCount != 0) { } else if (strValueCount != 0) {
this.longValue = strValueCount; this.longValue = strValueCount;
} else if (jsonValueCount != 0) {
this.longValue = jsonValueCount;
} else { } else {
this.longValue = longValueCount + doubleValueCount; this.longValue = longValueCount + doubleValueCount;
} }

6
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/latest/TsKvLatestEntity.java

@ -52,6 +52,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
@ColumnResult(name = "boolValue", type = Boolean.class), @ColumnResult(name = "boolValue", type = Boolean.class),
@ColumnResult(name = "longValue", type = Long.class), @ColumnResult(name = "longValue", type = Long.class),
@ColumnResult(name = "doubleValue", type = Double.class), @ColumnResult(name = "doubleValue", type = Double.class),
@ColumnResult(name = "jsonValue", type = String.class),
@ColumnResult(name = "ts", type = Long.class), @ColumnResult(name = "ts", type = Long.class),
} }
@ -74,13 +75,13 @@ public final class TsKvLatestEntity extends AbstractTsKvEntity {
@Override @Override
public boolean isNotEmpty() { public boolean isNotEmpty() {
return strValue != null || longValue != null || doubleValue != null || booleanValue != null; return strValue != null || longValue != null || doubleValue != null || booleanValue != null || jsonValue != null;
} }
public TsKvLatestEntity() { public TsKvLatestEntity() {
} }
public TsKvLatestEntity(UUID entityId, Integer key, String strKey, String strValue, Boolean boolValue, Long longValue, Double doubleValue, Long ts) { public TsKvLatestEntity(UUID entityId, Integer key, String strKey, String strValue, Boolean boolValue, Long longValue, Double doubleValue, String jsonValue, Long ts) {
this.entityId = entityId; this.entityId = entityId;
this.key = key; this.key = key;
this.ts = ts; this.ts = ts;
@ -88,6 +89,7 @@ public final class TsKvLatestEntity extends AbstractTsKvEntity {
this.doubleValue = doubleValue; this.doubleValue = doubleValue;
this.strValue = strValue; this.strValue = strValue;
this.booleanValue = boolValue; this.booleanValue = boolValue;
this.jsonValue = jsonValue;
this.strKey = strKey; this.strKey = strKey;
} }
} }

15
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/psql/TsKvEntity.java

@ -16,14 +16,6 @@
package org.thingsboard.server.dao.model.sqlts.psql; package org.thingsboard.server.dao.model.sqlts.psql;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ToData;
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity;
import javax.persistence.Column; import javax.persistence.Column;
@ -31,10 +23,7 @@ import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.IdClass; import javax.persistence.IdClass;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
@Data @Data
@ -93,12 +82,14 @@ public final class TsKvEntity extends AbstractTsKvEntity {
} }
} }
public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount) {
if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { if (!isAllNull(booleanValueCount, strValueCount, longValueCount, doubleValueCount)) {
if (booleanValueCount != 0) { if (booleanValueCount != 0) {
this.longValue = booleanValueCount; this.longValue = booleanValueCount;
} else if (strValueCount != 0) { } else if (strValueCount != 0) {
this.longValue = strValueCount; this.longValue = strValueCount;
} else if (jsonValueCount != 0) {
this.longValue = jsonValueCount;
} else { } else {
this.longValue = longValueCount + doubleValueCount; this.longValue = longValueCount + doubleValueCount;
} }

17
dao/src/main/java/org/thingsboard/server/dao/model/sqlts/timescale/TimescaleTsKvEntity.java

@ -18,12 +18,6 @@ package org.thingsboard.server.dao.model.sqlts.timescale;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ToData; import org.thingsboard.server.dao.model.ToData;
import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity;
@ -39,10 +33,8 @@ import javax.persistence.NamedNativeQuery;
import javax.persistence.SqlResultSetMapping; import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings; import javax.persistence.SqlResultSetMappings;
import javax.persistence.Table; import javax.persistence.Table;
import javax.persistence.Transient;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.KEY_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG; import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.FIND_AVG;
@ -92,6 +84,7 @@ import static org.thingsboard.server.dao.sqlts.timescale.AggregationRepository.F
@ColumnResult(name = "strValueCount", type = Long.class), @ColumnResult(name = "strValueCount", type = Long.class),
@ColumnResult(name = "longValueCount", type = Long.class), @ColumnResult(name = "longValueCount", type = Long.class),
@ColumnResult(name = "doubleValueCount", type = Long.class), @ColumnResult(name = "doubleValueCount", type = Long.class),
@ColumnResult(name = "jsonValueCount", type = Long.class),
} }
) )
}), }),
@ -179,13 +172,15 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToD
} }
} }
public TimescaleTsKvEntity(Long tsBucket, Long interval, Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) { public TimescaleTsKvEntity(Long tsBucket, Long interval, Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount, Long jsonValueCount) {
if (!isAllNull(tsBucket, interval, booleanValueCount, strValueCount, longValueCount, doubleValueCount)) { if (!isAllNull(tsBucket, interval, booleanValueCount, strValueCount, longValueCount, doubleValueCount, jsonValueCount)) {
this.ts = tsBucket + interval / 2; this.ts = tsBucket + interval / 2;
if (booleanValueCount != 0) { if (booleanValueCount != 0) {
this.longValue = booleanValueCount; this.longValue = booleanValueCount;
} else if (strValueCount != 0) { } else if (strValueCount != 0) {
this.longValue = strValueCount; this.longValue = strValueCount;
} else if (jsonValueCount != 0) {
this.longValue = jsonValueCount;
} else { } else {
this.longValue = longValueCount + doubleValueCount; this.longValue = longValueCount + doubleValueCount;
} }
@ -194,6 +189,6 @@ public final class TimescaleTsKvEntity extends AbstractTsKvEntity implements ToD
@Override @Override
public boolean isNotEmpty() { public boolean isNotEmpty() {
return ts != null && (strValue != null || longValue != null || doubleValue != null || booleanValue != null); return ts != null && (strValue != null || longValue != null || doubleValue != null || booleanValue != null || jsonValue != null);
} }
} }

118
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java

@ -18,7 +18,6 @@ package org.thingsboard.server.dao.sql.attributes;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
@ -28,8 +27,6 @@ import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
@ -45,19 +42,14 @@ public abstract class AttributeKvInsertRepository {
private static final ThreadLocal<Pattern> PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE))); private static final ThreadLocal<Pattern> PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE)));
private static final String EMPTY_STR = ""; private static final String EMPTY_STR = "";
private static final String BATCH_UPDATE = "UPDATE attribute_kv SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, last_update_ts = ? " + private static final String BATCH_UPDATE = "UPDATE attribute_kv SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ? " +
"WHERE entity_type = ? and entity_id = ? and attribute_type =? and attribute_key = ?;"; "WHERE entity_type = ? and entity_id = ? and attribute_type =? and attribute_key = ?;";
private static final String INSERT_OR_UPDATE = private static final String INSERT_OR_UPDATE =
"INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, last_update_ts) " + "INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?) " + "VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json), ?) " +
"ON CONFLICT (entity_type, entity_id, attribute_type, attribute_key) " + "ON CONFLICT (entity_type, entity_id, attribute_type, attribute_key) " +
"DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, last_update_ts = ?;"; "DO UPDATE SET str_v = ?, long_v = ?, dbl_v = ?, bool_v = ?, json_v = cast(? AS json), last_update_ts = ?;";
protected static final String BOOL_V = "bool_v";
protected static final String STR_V = "str_v";
protected static final String LONG_V = "long_v";
protected static final String DBL_V = "dbl_v";
@Autowired @Autowired
protected JdbcTemplate jdbcTemplate; protected JdbcTemplate jdbcTemplate;
@ -68,74 +60,6 @@ public abstract class AttributeKvInsertRepository {
@Value("${sql.remove_null_chars}") @Value("${sql.remove_null_chars}")
private boolean removeNullChars; private boolean removeNullChars;
@PersistenceContext
protected EntityManager entityManager;
public abstract void saveOrUpdate(AttributeKvEntity entity);
protected void processSaveOrUpdate(AttributeKvEntity entity, String requestBoolValue, String requestStrValue, String requestLongValue, String requestDblValue) {
if (entity.getBooleanValue() != null) {
saveOrUpdateBoolean(entity, requestBoolValue);
}
if (entity.getStrValue() != null) {
saveOrUpdateString(entity, requestStrValue);
}
if (entity.getLongValue() != null) {
saveOrUpdateLong(entity, requestLongValue);
}
if (entity.getDoubleValue() != null) {
saveOrUpdateDouble(entity, requestDblValue);
}
}
@Modifying
private void saveOrUpdateBoolean(AttributeKvEntity entity, String query) {
entityManager.createNativeQuery(query)
.setParameter("entity_type", entity.getId().getEntityType().name())
.setParameter("entity_id", entity.getId().getEntityId())
.setParameter("attribute_type", entity.getId().getAttributeType())
.setParameter("attribute_key", entity.getId().getAttributeKey())
.setParameter("bool_v", entity.getBooleanValue())
.setParameter("last_update_ts", entity.getLastUpdateTs())
.executeUpdate();
}
@Modifying
private void saveOrUpdateString(AttributeKvEntity entity, String query) {
entityManager.createNativeQuery(query)
.setParameter("entity_type", entity.getId().getEntityType().name())
.setParameter("entity_id", entity.getId().getEntityId())
.setParameter("attribute_type", entity.getId().getAttributeType())
.setParameter("attribute_key", entity.getId().getAttributeKey())
.setParameter("str_v", replaceNullChars(entity.getStrValue()))
.setParameter("last_update_ts", entity.getLastUpdateTs())
.executeUpdate();
}
@Modifying
private void saveOrUpdateLong(AttributeKvEntity entity, String query) {
entityManager.createNativeQuery(query)
.setParameter("entity_type", entity.getId().getEntityType().name())
.setParameter("entity_id", entity.getId().getEntityId())
.setParameter("attribute_type", entity.getId().getAttributeType())
.setParameter("attribute_key", entity.getId().getAttributeKey())
.setParameter("long_v", entity.getLongValue())
.setParameter("last_update_ts", entity.getLastUpdateTs())
.executeUpdate();
}
@Modifying
private void saveOrUpdateDouble(AttributeKvEntity entity, String query) {
entityManager.createNativeQuery(query)
.setParameter("entity_type", entity.getId().getEntityType().name())
.setParameter("entity_id", entity.getId().getEntityId())
.setParameter("attribute_type", entity.getId().getAttributeType())
.setParameter("attribute_key", entity.getId().getAttributeKey())
.setParameter("dbl_v", entity.getDoubleValue())
.setParameter("last_update_ts", entity.getLastUpdateTs())
.executeUpdate();
}
protected void saveOrUpdate(List<AttributeKvEntity> entities) { protected void saveOrUpdate(List<AttributeKvEntity> entities) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() { transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override @Override
@ -164,11 +88,13 @@ public abstract class AttributeKvInsertRepository {
ps.setNull(4, Types.BOOLEAN); ps.setNull(4, Types.BOOLEAN);
} }
ps.setLong(5, kvEntity.getLastUpdateTs()); ps.setString(5, replaceNullChars(kvEntity.getJsonValue()));
ps.setString(6, kvEntity.getId().getEntityType().name());
ps.setString(7, kvEntity.getId().getEntityId()); ps.setLong(6, kvEntity.getLastUpdateTs());
ps.setString(8, kvEntity.getId().getAttributeType()); ps.setString(7, kvEntity.getId().getEntityType().name());
ps.setString(9, kvEntity.getId().getAttributeKey()); ps.setString(8, kvEntity.getId().getEntityId());
ps.setString(9, kvEntity.getId().getAttributeType());
ps.setString(10, kvEntity.getId().getAttributeKey());
} }
@Override @Override
@ -199,35 +125,39 @@ public abstract class AttributeKvInsertRepository {
ps.setString(2, kvEntity.getId().getEntityId()); ps.setString(2, kvEntity.getId().getEntityId());
ps.setString(3, kvEntity.getId().getAttributeType()); ps.setString(3, kvEntity.getId().getAttributeType());
ps.setString(4, kvEntity.getId().getAttributeKey()); ps.setString(4, kvEntity.getId().getAttributeKey());
ps.setString(5, replaceNullChars(kvEntity.getStrValue())); ps.setString(5, replaceNullChars(kvEntity.getStrValue()));
ps.setString(10, replaceNullChars(kvEntity.getStrValue())); ps.setString(11, replaceNullChars(kvEntity.getStrValue()));
if (kvEntity.getLongValue() != null) { if (kvEntity.getLongValue() != null) {
ps.setLong(6, kvEntity.getLongValue()); ps.setLong(6, kvEntity.getLongValue());
ps.setLong(11, kvEntity.getLongValue()); ps.setLong(12, kvEntity.getLongValue());
} else { } else {
ps.setNull(6, Types.BIGINT); ps.setNull(6, Types.BIGINT);
ps.setNull(11, Types.BIGINT); ps.setNull(12, Types.BIGINT);
} }
if (kvEntity.getDoubleValue() != null) { if (kvEntity.getDoubleValue() != null) {
ps.setDouble(7, kvEntity.getDoubleValue()); ps.setDouble(7, kvEntity.getDoubleValue());
ps.setDouble(12, kvEntity.getDoubleValue()); ps.setDouble(13, kvEntity.getDoubleValue());
} else { } else {
ps.setNull(7, Types.DOUBLE); ps.setNull(7, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE); ps.setNull(13, Types.DOUBLE);
} }
if (kvEntity.getBooleanValue() != null) { if (kvEntity.getBooleanValue() != null) {
ps.setBoolean(8, kvEntity.getBooleanValue()); ps.setBoolean(8, kvEntity.getBooleanValue());
ps.setBoolean(13, kvEntity.getBooleanValue()); ps.setBoolean(14, kvEntity.getBooleanValue());
} else { } else {
ps.setNull(8, Types.BOOLEAN); ps.setNull(8, Types.BOOLEAN);
ps.setNull(13, Types.BOOLEAN); ps.setNull(14, Types.BOOLEAN);
} }
ps.setLong(9, kvEntity.getLastUpdateTs()); ps.setString(9, replaceNullChars(kvEntity.getJsonValue()));
ps.setLong(14, kvEntity.getLastUpdateTs()); ps.setString(15, replaceNullChars(kvEntity.getJsonValue()));
ps.setLong(10, kvEntity.getLastUpdateTs());
ps.setLong(16, kvEntity.getLastUpdateTs());
} }
@Override @Override

34
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/HsqlAttributesInsertRepository.java

@ -30,36 +30,16 @@ import java.util.List;
@Transactional @Transactional
public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository { public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository {
private static final String ON_BOOL_VALUE_UPDATE_SET_NULLS = " attribute_kv.str_v = null, attribute_kv.long_v = null, attribute_kv.dbl_v = null ";
private static final String ON_STR_VALUE_UPDATE_SET_NULLS = " attribute_kv.bool_v = null, attribute_kv.long_v = null, attribute_kv.dbl_v = null ";
private static final String ON_LONG_VALUE_UPDATE_SET_NULLS = " attribute_kv.str_v = null, attribute_kv.bool_v = null, attribute_kv.dbl_v = null ";
private static final String ON_DBL_VALUE_UPDATE_SET_NULLS = " attribute_kv.str_v = null, attribute_kv.long_v = null, attribute_kv.bool_v = null ";
private static final String INSERT_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, ON_BOOL_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_STR_STATEMENT = getInsertOrUpdateString(STR_V, ON_STR_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_LONG_STATEMENT = getInsertOrUpdateString(LONG_V, ON_LONG_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, ON_DBL_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_OR_UPDATE = private static final String INSERT_OR_UPDATE =
"MERGE INTO attribute_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?) " + "MERGE INTO attribute_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
"A (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, last_update_ts) " + "A (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"ON (attribute_kv.entity_type=A.entity_type " + "ON (attribute_kv.entity_type=A.entity_type " +
"AND attribute_kv.entity_id=A.entity_id " + "AND attribute_kv.entity_id=A.entity_id " +
"AND attribute_kv.attribute_type=A.attribute_type " + "AND attribute_kv.attribute_type=A.attribute_type " +
"AND attribute_kv.attribute_key=A.attribute_key) " + "AND attribute_kv.attribute_key=A.attribute_key) " +
"WHEN MATCHED THEN UPDATE SET attribute_kv.str_v = A.str_v, attribute_kv.long_v = A.long_v, attribute_kv.dbl_v = A.dbl_v, attribute_kv.bool_v = A.bool_v, attribute_kv.last_update_ts = A.last_update_ts " + "WHEN MATCHED THEN UPDATE SET attribute_kv.str_v = A.str_v, attribute_kv.long_v = A.long_v, attribute_kv.dbl_v = A.dbl_v, attribute_kv.bool_v = A.bool_v, attribute_kv.json_v = A.json_v, attribute_kv.last_update_ts = A.last_update_ts " +
"WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, last_update_ts) " + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, str_v, long_v, dbl_v, bool_v, json_v, last_update_ts) " +
"VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A.str_v, A.long_v, A.dbl_v, A.bool_v, A.last_update_ts)"; "VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A.str_v, A.long_v, A.dbl_v, A.bool_v, A.json_v, A.last_update_ts)";
@Override
public void saveOrUpdate(AttributeKvEntity entity) {
processSaveOrUpdate(entity, INSERT_BOOL_STATEMENT, INSERT_STR_STATEMENT, INSERT_LONG_STATEMENT, INSERT_DBL_STATEMENT);
}
private static String getInsertOrUpdateString(String value, String nullValues) {
return "MERGE INTO attribute_kv USING(VALUES :entity_type, :entity_id, :attribute_type, :attribute_key, :" + value + ", :last_update_ts) A (entity_type, entity_id, attribute_type, attribute_key, " + value + ", last_update_ts) ON (attribute_kv.entity_type=A.entity_type AND attribute_kv.entity_id=A.entity_id AND attribute_kv.attribute_type=A.attribute_type AND attribute_kv.attribute_key=A.attribute_key) WHEN MATCHED THEN UPDATE SET attribute_kv." + value + " = A." + value + ", attribute_kv.last_update_ts = A.last_update_ts," + nullValues + "WHEN NOT MATCHED THEN INSERT (entity_type, entity_id, attribute_type, attribute_key, " + value + ", last_update_ts) VALUES (A.entity_type, A.entity_id, A.attribute_type, A.attribute_key, A." + value + ", A.last_update_ts)";
}
@Override @Override
protected void saveOrUpdate(List<AttributeKvEntity> entities) { protected void saveOrUpdate(List<AttributeKvEntity> entities) {
@ -89,7 +69,9 @@ public class HsqlAttributesInsertRepository extends AttributeKvInsertRepository
ps.setNull(8, Types.BOOLEAN); ps.setNull(8, Types.BOOLEAN);
} }
ps.setLong(9, entity.getLastUpdateTs()); ps.setString(9, entity.getJsonValue());
ps.setLong(10, entity.getLastUpdateTs());
}); });
}); });
} }

1
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java

@ -128,6 +128,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); entity.setDoubleValue(attribute.getDoubleValue().orElse(null));
entity.setLongValue(attribute.getLongValue().orElse(null)); entity.setLongValue(attribute.getLongValue().orElse(null));
entity.setBooleanValue(attribute.getBooleanValue().orElse(null)); entity.setBooleanValue(attribute.getBooleanValue().orElse(null));
entity.setJsonValue(attribute.getJsonValue().orElse(null));
return addToQueue(entity); return addToQueue(entity);
} }

19
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/PsqlAttributesInsertRepository.java

@ -17,7 +17,6 @@ package org.thingsboard.server.dao.sql.attributes;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
@ -27,22 +26,4 @@ import org.thingsboard.server.dao.util.SqlDao;
@Transactional @Transactional
public class PsqlAttributesInsertRepository extends AttributeKvInsertRepository { public class PsqlAttributesInsertRepository extends AttributeKvInsertRepository {
private static final String ON_BOOL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, dbl_v = null";
private static final String ON_STR_VALUE_UPDATE_SET_NULLS = "bool_v = null, long_v = null, dbl_v = null";
private static final String ON_LONG_VALUE_UPDATE_SET_NULLS = "str_v = null, bool_v = null, dbl_v = null";
private static final String ON_DBL_VALUE_UPDATE_SET_NULLS = "str_v = null, long_v = null, bool_v = null";
private static final String INSERT_OR_UPDATE_BOOL_STATEMENT = getInsertOrUpdateString(BOOL_V, ON_BOOL_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_OR_UPDATE_STR_STATEMENT = getInsertOrUpdateString(STR_V, ON_STR_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_OR_UPDATE_LONG_STATEMENT = getInsertOrUpdateString(LONG_V , ON_LONG_VALUE_UPDATE_SET_NULLS);
private static final String INSERT_OR_UPDATE_DBL_STATEMENT = getInsertOrUpdateString(DBL_V, ON_DBL_VALUE_UPDATE_SET_NULLS);
@Override
public void saveOrUpdate(AttributeKvEntity entity) {
processSaveOrUpdate(entity, INSERT_OR_UPDATE_BOOL_STATEMENT, INSERT_OR_UPDATE_STR_STATEMENT, INSERT_OR_UPDATE_LONG_STATEMENT, INSERT_OR_UPDATE_DBL_STATEMENT);
}
private static String getInsertOrUpdateString(String value, String nullValues) {
return "INSERT INTO attribute_kv (entity_type, entity_id, attribute_type, attribute_key, " + value + ", last_update_ts) VALUES (:entity_type, :entity_id, :attribute_type, :attribute_key, :" + value + ", :last_update_ts) ON CONFLICT (entity_type, entity_id, attribute_type, attribute_key) DO UPDATE SET " + value + " = :" + value + ", last_update_ts = :last_update_ts," + nullValues;
}
} }

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java

@ -253,6 +253,8 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); latestEntity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null)); latestEntity.setLongValue(tsKvEntry.getLongValue().orElse(null));
latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); latestEntity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
latestEntity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
return tsLatestQueue.add(latestEntity); return tsLatestQueue.add(latestEntity);
} }

12
dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/HsqlInsertTsRepository.java

@ -37,14 +37,14 @@ import java.util.List;
public class HsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> { public class HsqlInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TsKvEntity> {
private static final String INSERT_OR_UPDATE = private static final String INSERT_OR_UPDATE =
"MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + "MERGE INTO ts_kv USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " +
"T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"ON (ts_kv.entity_id=T.entity_id " + "ON (ts_kv.entity_id=T.entity_id " +
"AND ts_kv.key=T.key " + "AND ts_kv.key=T.key " +
"AND ts_kv.ts=T.ts) " + "AND ts_kv.ts=T.ts) " +
"WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v " + "WHEN MATCHED THEN UPDATE SET ts_kv.bool_v = T.bool_v, ts_kv.str_v = T.str_v, ts_kv.long_v = T.long_v, ts_kv.dbl_v = T.dbl_v ,ts_kv.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v, T.json_v);";
@Override @Override
public void saveOrUpdate(List<EntityContainer<TsKvEntity>> entities) { public void saveOrUpdate(List<EntityContainer<TsKvEntity>> entities) {
@ -76,6 +76,8 @@ public class HsqlInsertTsRepository extends AbstractInsertRepository implements
} else { } else {
ps.setNull(7, Types.DOUBLE); ps.setNull(7, Types.DOUBLE);
} }
ps.setString(8, tsKvEntity.getJsonValue());
} }
@Override @Override

3
dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/TsKvHsqlRepository.java

@ -97,7 +97,8 @@ public interface TsKvHsqlRepository extends CrudRepository<TsKvEntity, TsKvCompo
@Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId, CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId,
@Param("entityKey") int entityKey, @Param("entityKey") int entityKey,

8
dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/HsqlLatestInsertTsRepository.java

@ -36,11 +36,11 @@ import java.util.List;
public class HsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { public class HsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
private static final String INSERT_OR_UPDATE = private static final String INSERT_OR_UPDATE =
"MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?) " + "MERGE INTO ts_kv_latest USING(VALUES ?, ?, ?, ?, ?, ?, ?, ?) " +
"T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + "T (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " +
"ON (ts_kv_latest.entity_id=T.entity_id " + "ON (ts_kv_latest.entity_id=T.entity_id " +
"AND ts_kv_latest.key=T.key) " + "AND ts_kv_latest.key=T.key) " +
"WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v " + "WHEN MATCHED THEN UPDATE SET ts_kv_latest.ts = T.ts, ts_kv_latest.bool_v = T.bool_v, ts_kv_latest.str_v = T.str_v, ts_kv_latest.long_v = T.long_v, ts_kv_latest.dbl_v = T.dbl_v, ts_kv_latest.json_v = T.json_v " +
"WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + "WHEN NOT MATCHED THEN INSERT (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " +
"VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);"; "VALUES (T.entity_id, T.key, T.ts, T.bool_v, T.str_v, T.long_v, T.dbl_v);";
@ -72,6 +72,8 @@ public class HsqlLatestInsertTsRepository extends AbstractInsertRepository imple
} else { } else {
ps.setNull(7, Types.DOUBLE); ps.setNull(7, Types.DOUBLE);
} }
ps.setString(8, entities.get(i).getJsonValue());
} }
@Override @Override

33
dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/PsqlLatestInsertTsRepository.java

@ -38,12 +38,12 @@ import java.util.List;
public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository {
private static final String BATCH_UPDATE = private static final String BATCH_UPDATE =
"UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ? WHERE entity_id = ? and key = ?"; "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? and key = ?";
private static final String INSERT_OR_UPDATE = private static final String INSERT_OR_UPDATE =
"INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?) " + "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " +
"ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);";
@Override @Override
public void saveOrUpdate(List<TsKvLatestEntity> entities) { public void saveOrUpdate(List<TsKvLatestEntity> entities) {
@ -76,8 +76,10 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple
ps.setNull(5, Types.DOUBLE); ps.setNull(5, Types.DOUBLE);
} }
ps.setObject(6, tsKvLatestEntity.getEntityId()); ps.setString(6, replaceNullChars(tsKvLatestEntity.getJsonValue()));
ps.setInt(7, tsKvLatestEntity.getKey());
ps.setObject(7, tsKvLatestEntity.getEntityId());
ps.setInt(8, tsKvLatestEntity.getKey());
} }
@Override @Override
@ -106,36 +108,39 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple
TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i); TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i);
ps.setObject(1, tsKvLatestEntity.getEntityId()); ps.setObject(1, tsKvLatestEntity.getEntityId());
ps.setInt(2, tsKvLatestEntity.getKey()); ps.setInt(2, tsKvLatestEntity.getKey());
ps.setLong(3, tsKvLatestEntity.getTs()); ps.setLong(3, tsKvLatestEntity.getTs());
ps.setLong(8, tsKvLatestEntity.getTs()); ps.setLong(9, tsKvLatestEntity.getTs());
if (tsKvLatestEntity.getBooleanValue() != null) { if (tsKvLatestEntity.getBooleanValue() != null) {
ps.setBoolean(4, tsKvLatestEntity.getBooleanValue()); ps.setBoolean(4, tsKvLatestEntity.getBooleanValue());
ps.setBoolean(9, tsKvLatestEntity.getBooleanValue()); ps.setBoolean(10, tsKvLatestEntity.getBooleanValue());
} else { } else {
ps.setNull(4, Types.BOOLEAN); ps.setNull(4, Types.BOOLEAN);
ps.setNull(9, Types.BOOLEAN); ps.setNull(10, Types.BOOLEAN);
} }
ps.setString(5, replaceNullChars(tsKvLatestEntity.getStrValue())); ps.setString(5, replaceNullChars(tsKvLatestEntity.getStrValue()));
ps.setString(10, replaceNullChars(tsKvLatestEntity.getStrValue())); ps.setString(11, replaceNullChars(tsKvLatestEntity.getStrValue()));
if (tsKvLatestEntity.getLongValue() != null) { if (tsKvLatestEntity.getLongValue() != null) {
ps.setLong(6, tsKvLatestEntity.getLongValue()); ps.setLong(6, tsKvLatestEntity.getLongValue());
ps.setLong(11, tsKvLatestEntity.getLongValue()); ps.setLong(12, tsKvLatestEntity.getLongValue());
} else { } else {
ps.setNull(6, Types.BIGINT); ps.setNull(6, Types.BIGINT);
ps.setNull(11, Types.BIGINT); ps.setNull(12, Types.BIGINT);
} }
if (tsKvLatestEntity.getDoubleValue() != null) { if (tsKvLatestEntity.getDoubleValue() != null) {
ps.setDouble(7, tsKvLatestEntity.getDoubleValue()); ps.setDouble(7, tsKvLatestEntity.getDoubleValue());
ps.setDouble(12, tsKvLatestEntity.getDoubleValue()); ps.setDouble(13, tsKvLatestEntity.getDoubleValue());
} else { } else {
ps.setNull(7, Types.DOUBLE); ps.setNull(7, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE); ps.setNull(13, Types.DOUBLE);
} }
ps.setString(8, replaceNullChars(tsKvLatestEntity.getJsonValue()));
ps.setString(14, replaceNullChars(tsKvLatestEntity.getJsonValue()));
} }
@Override @Override

3
dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/SearchTsKvLatestRepository.java

@ -29,8 +29,9 @@ import java.util.UUID;
public class SearchTsKvLatestRepository { public class SearchTsKvLatestRepository {
public static final String FIND_ALL_BY_ENTITY_ID = "findAllByEntityId"; public static final String FIND_ALL_BY_ENTITY_ID = "findAllByEntityId";
public static final String FIND_ALL_BY_ENTITY_ID_QUERY = "SELECT ts_kv_latest.entity_id AS entityId, ts_kv_latest.key AS key, ts_kv_dictionary.key AS strKey, ts_kv_latest.str_v AS strValue," + public static final String FIND_ALL_BY_ENTITY_ID_QUERY = "SELECT ts_kv_latest.entity_id AS entityId, ts_kv_latest.key AS key, ts_kv_dictionary.key AS strKey, ts_kv_latest.str_v AS strValue," +
" ts_kv_latest.bool_v AS boolValue, ts_kv_latest.long_v AS longValue, ts_kv_latest.dbl_v AS doubleValue, ts_kv_latest.ts AS ts FROM ts_kv_latest " + " ts_kv_latest.bool_v AS boolValue, ts_kv_latest.long_v AS longValue, ts_kv_latest.dbl_v AS doubleValue, ts_kv_latest.json_v AS jsonValue, ts_kv_latest.ts AS ts FROM ts_kv_latest " +
"INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id WHERE ts_kv_latest.entity_id = cast(:id AS uuid)"; "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id WHERE ts_kv_latest.entity_id = cast(:id AS uuid)";
@PersistenceContext @PersistenceContext

4
dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java

@ -20,11 +20,7 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
import java.util.UUID;
@SqlDao @SqlDao
public interface TsKvLatestRepository extends CrudRepository<TsKvLatestEntity, TsKvLatestCompositeKey> { public interface TsKvLatestRepository extends CrudRepository<TsKvLatestEntity, TsKvLatestCompositeKey> {
List<TsKvLatestEntity> findAllByEntityId(UUID entityId);
} }

1
dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java

@ -104,6 +104,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null));
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
PsqlPartition psqlPartition = toPartition(tsKvEntry.getTs()); PsqlPartition psqlPartition = toPartition(tsKvEntry.getTs());
log.trace("Saving entity: {}", entity); log.trace("Saving entity: {}", entity);
return tsQueue.add(new EntityContainer(entity, psqlPartition.getPartitionDate())); return tsQueue.add(new EntityContainer(entity, psqlPartition.getPartitionDate()));

21
dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/PsqlInsertTsRepository.java

@ -41,8 +41,8 @@ public class PsqlInsertTsRepository extends AbstractInsertRepository implements
private static final String INSERT_INTO_TS_KV = "INSERT INTO ts_kv_"; private static final String INSERT_INTO_TS_KV = "INSERT INTO ts_kv_";
private static final String VALUES_ON_CONFLICT_DO_UPDATE = " (entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES (?, ?, ?, ?, ?, ?, ?) " + private static final String VALUES_ON_CONFLICT_DO_UPDATE = " (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES (?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " +
"ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; "ON CONFLICT (entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);";
@Override @Override
public void saveOrUpdate(List<EntityContainer<TsKvEntity>> entities) { public void saveOrUpdate(List<EntityContainer<TsKvEntity>> entities) {
@ -61,30 +61,33 @@ public class PsqlInsertTsRepository extends AbstractInsertRepository implements
if (tsKvEntity.getBooleanValue() != null) { if (tsKvEntity.getBooleanValue() != null) {
ps.setBoolean(4, tsKvEntity.getBooleanValue()); ps.setBoolean(4, tsKvEntity.getBooleanValue());
ps.setBoolean(8, tsKvEntity.getBooleanValue()); ps.setBoolean(9, tsKvEntity.getBooleanValue());
} else { } else {
ps.setNull(4, Types.BOOLEAN); ps.setNull(4, Types.BOOLEAN);
ps.setNull(8, Types.BOOLEAN); ps.setNull(9, Types.BOOLEAN);
} }
ps.setString(5, replaceNullChars(tsKvEntity.getStrValue())); ps.setString(5, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(9, replaceNullChars(tsKvEntity.getStrValue())); ps.setString(10, replaceNullChars(tsKvEntity.getStrValue()));
if (tsKvEntity.getLongValue() != null) { if (tsKvEntity.getLongValue() != null) {
ps.setLong(6, tsKvEntity.getLongValue()); ps.setLong(6, tsKvEntity.getLongValue());
ps.setLong(10, tsKvEntity.getLongValue()); ps.setLong(11, tsKvEntity.getLongValue());
} else { } else {
ps.setNull(6, Types.BIGINT); ps.setNull(6, Types.BIGINT);
ps.setNull(10, Types.BIGINT); ps.setNull(11, Types.BIGINT);
} }
if (tsKvEntity.getDoubleValue() != null) { if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(7, tsKvEntity.getDoubleValue()); ps.setDouble(7, tsKvEntity.getDoubleValue());
ps.setDouble(11, tsKvEntity.getDoubleValue()); ps.setDouble(12, tsKvEntity.getDoubleValue());
} else { } else {
ps.setNull(7, Types.DOUBLE); ps.setNull(7, Types.DOUBLE);
ps.setNull(11, Types.DOUBLE); ps.setNull(12, Types.DOUBLE);
ps.setString(8, replaceNullChars(tsKvEntity.getJsonValue()));
ps.setString(13, replaceNullChars(tsKvEntity.getJsonValue()));
} }
} }

3
dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/TsKvPsqlRepository.java

@ -98,7 +98,8 @@ public interface TsKvPsqlRepository extends CrudRepository<TsKvEntity, TsKvCompo
@Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " + @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " + "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " + "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
"SUM(CASE WHEN tskv.jsonValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs") "WHERE tskv.entityId = :entityId AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId, CompletableFuture<TsKvEntity> findCount(@Param("entityId") UUID entityId,
@Param("entityKey") int entityKey, @Param("entityKey") int entityKey,

9
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/AggregationRepository.java

@ -36,18 +36,17 @@ public class AggregationRepository {
public static final String FIND_SUM = "findSum"; public static final String FIND_SUM = "findSum";
public static final String FIND_COUNT = "findCount"; public static final String FIND_COUNT = "findCount";
public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS uuid) AND tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket"; public static final String FROM_WHERE_CLAUSE = "FROM tenant_ts_kv tskv WHERE tskv.tenant_id = cast(:tenantId AS uuid) AND tskv.entity_id = cast(:entityId AS uuid) AND tskv.key= cast(:entityKey AS int) AND tskv.ts > :startTs AND tskv.ts <= :endTs GROUP BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket ORDER BY tskv.tenant_id, tskv.entity_id, tskv.key, tsBucket";
public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType "; public static final String FIND_AVG_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'AVG' AS aggType ";
public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, 'MAX' AS aggType "; public static final String FIND_MAX_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MAX(COALESCE(tskv.long_v, -9223372036854775807)) AS longValue, MAX(COALESCE(tskv.dbl_v, -1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MAX(tskv.str_v) AS strValue, MAX(tskv.json_v) AS jsonValue, 'MAX' AS aggType ";
public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, 'MIN' AS aggType "; public static final String FIND_MIN_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, MIN(COALESCE(tskv.long_v, 9223372036854775807)) AS longValue, MIN(COALESCE(tskv.dbl_v, 1.79769E+308)) as doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, MIN(tskv.str_v) AS strValue, MIN(tskv.json_v) AS jsonValue,'MIN' AS aggType ";
public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, 'SUM' AS aggType "; public static final String FIND_SUM_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(COALESCE(tskv.long_v, 0)) AS longValue, SUM(COALESCE(tskv.dbl_v, 0.0)) AS doubleValue, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longCountValue, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleCountValue, null AS strValue, null AS jsonValue, 'SUM' AS aggType ";
public static final String FIND_COUNT_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(CASE WHEN tskv.bool_v IS NULL THEN 0 ELSE 1 END) AS booleanValueCount, SUM(CASE WHEN tskv.str_v IS NULL THEN 0 ELSE 1 END) AS strValueCount, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longValueCount, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleValueCount "; public static final String FIND_COUNT_QUERY = "SELECT time_bucket(:timeBucket, tskv.ts) AS tsBucket, :timeBucket AS interval, SUM(CASE WHEN tskv.bool_v IS NULL THEN 0 ELSE 1 END) AS booleanValueCount, SUM(CASE WHEN tskv.str_v IS NULL THEN 0 ELSE 1 END) AS strValueCount, SUM(CASE WHEN tskv.long_v IS NULL THEN 0 ELSE 1 END) AS longValueCount, SUM(CASE WHEN tskv.dbl_v IS NULL THEN 0 ELSE 1 END) AS doubleValueCount, SUM(CASE WHEN tskv.json_v IS NULL THEN 0 ELSE 1 END) AS jsonValueCount ";
@PersistenceContext @PersistenceContext
private EntityManager entityManager; private EntityManager entityManager;

19
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertTsRepository.java

@ -37,8 +37,8 @@ import java.util.List;
public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TimescaleTsKvEntity> { public class TimescaleInsertTsRepository extends AbstractInsertRepository implements InsertTsRepository<TimescaleTsKvEntity> {
private static final String INSERT_OR_UPDATE = private static final String INSERT_OR_UPDATE =
"INSERT INTO tenant_ts_kv (tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?) " + "INSERT INTO tenant_ts_kv (tenant_id, entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " +
"ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?;"; "ON CONFLICT (tenant_id, entity_id, key, ts) DO UPDATE SET bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);";
@Override @Override
public void saveOrUpdate(List<EntityContainer<TimescaleTsKvEntity>> entities) { public void saveOrUpdate(List<EntityContainer<TimescaleTsKvEntity>> entities) {
@ -56,28 +56,31 @@ public class TimescaleInsertTsRepository extends AbstractInsertRepository implem
ps.setBoolean(9, tsKvEntity.getBooleanValue()); ps.setBoolean(9, tsKvEntity.getBooleanValue());
} else { } else {
ps.setNull(5, Types.BOOLEAN); ps.setNull(5, Types.BOOLEAN);
ps.setNull(9, Types.BOOLEAN); ps.setNull(10, Types.BOOLEAN);
} }
ps.setString(6, replaceNullChars(tsKvEntity.getStrValue())); ps.setString(6, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(10, replaceNullChars(tsKvEntity.getStrValue())); ps.setString(11, replaceNullChars(tsKvEntity.getStrValue()));
if (tsKvEntity.getLongValue() != null) { if (tsKvEntity.getLongValue() != null) {
ps.setLong(7, tsKvEntity.getLongValue()); ps.setLong(7, tsKvEntity.getLongValue());
ps.setLong(11, tsKvEntity.getLongValue()); ps.setLong(12, tsKvEntity.getLongValue());
} else { } else {
ps.setNull(7, Types.BIGINT); ps.setNull(7, Types.BIGINT);
ps.setNull(11, Types.BIGINT); ps.setNull(12, Types.BIGINT);
} }
if (tsKvEntity.getDoubleValue() != null) { if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(8, tsKvEntity.getDoubleValue()); ps.setDouble(8, tsKvEntity.getDoubleValue());
ps.setDouble(12, tsKvEntity.getDoubleValue()); ps.setDouble(13, tsKvEntity.getDoubleValue());
} else { } else {
ps.setNull(8, Types.DOUBLE); ps.setNull(8, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE); ps.setNull(13, Types.DOUBLE);
} }
ps.setString(9, replaceNullChars(tsKvEntity.getJsonValue()));
ps.setString(14, replaceNullChars(tsKvEntity.getJsonValue()));
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java

@ -174,6 +174,8 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null)); entity.setDoubleValue(tsKvEntry.getDoubleValue().orElse(null));
entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setLongValue(tsKvEntry.getLongValue().orElse(null));
entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null));
entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null));
log.trace("Saving entity to timescale db: {}", entity); log.trace("Saving entity to timescale db: {}", entity);
return tsQueue.add(new EntityContainer(entity, null)); return tsQueue.add(new EntityContainer(entity, null));
} }

48
dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -41,10 +42,12 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
private static final int DOUBLE_CNT_POS = 1; private static final int DOUBLE_CNT_POS = 1;
private static final int BOOL_CNT_POS = 2; private static final int BOOL_CNT_POS = 2;
private static final int STR_CNT_POS = 3; private static final int STR_CNT_POS = 3;
private static final int LONG_POS = 4; private static final int JSON_CNT_POS = 4;
private static final int DOUBLE_POS = 5; private static final int LONG_POS = 5;
private static final int BOOL_POS = 6; private static final int DOUBLE_POS = 6;
private static final int STR_POS = 7; private static final int BOOL_POS = 7;
private static final int STR_POS = 8;
private static final int JSON_POS = 9;
private final Aggregation aggregation; private final Aggregation aggregation;
private final String key; private final String key;
@ -72,7 +75,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
} }
} }
return processAggregationResult(aggResult); return processAggregationResult(aggResult);
}catch (Exception e){ } catch (Exception e) {
log.error("[{}][{}][{}] Failed to aggregate data", key, ts, aggregation, e); log.error("[{}][{}][{}] Failed to aggregate data", key, ts, aggregation, e);
return Optional.empty(); return Optional.empty();
} }
@ -85,11 +88,13 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
Double curDValue = null; Double curDValue = null;
Boolean curBValue = null; Boolean curBValue = null;
String curSValue = null; String curSValue = null;
String curJValue = null;
long longCount = row.getLong(LONG_CNT_POS); long longCount = row.getLong(LONG_CNT_POS);
long doubleCount = row.getLong(DOUBLE_CNT_POS); long doubleCount = row.getLong(DOUBLE_CNT_POS);
long boolCount = row.getLong(BOOL_CNT_POS); long boolCount = row.getLong(BOOL_CNT_POS);
long strCount = row.getLong(STR_CNT_POS); long strCount = row.getLong(STR_CNT_POS);
long jsonCount = row.getLong(JSON_CNT_POS);
if (longCount > 0 || doubleCount > 0) { if (longCount > 0 || doubleCount > 0) {
if (longCount > 0) { if (longCount > 0) {
@ -111,6 +116,10 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
aggResult.dataType = DataType.STRING; aggResult.dataType = DataType.STRING;
curCount = strCount; curCount = strCount;
curSValue = getStringValue(row); curSValue = getStringValue(row);
} else if (jsonCount > 0) {
aggResult.dataType = DataType.JSON;
curCount = jsonCount;
curJValue = getJsonValue(row);
} else { } else {
return; return;
} }
@ -120,9 +129,9 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
} else if (aggregation == Aggregation.AVG || aggregation == Aggregation.SUM) { } else if (aggregation == Aggregation.AVG || aggregation == Aggregation.SUM) {
processAvgOrSumAggregation(aggResult, curCount, curLValue, curDValue); processAvgOrSumAggregation(aggResult, curCount, curLValue, curDValue);
} else if (aggregation == Aggregation.MIN) { } else if (aggregation == Aggregation.MIN) {
processMinAggregation(aggResult, curLValue, curDValue, curBValue, curSValue); processMinAggregation(aggResult, curLValue, curDValue, curBValue, curSValue, curJValue);
} else if (aggregation == Aggregation.MAX) { } else if (aggregation == Aggregation.MAX) {
processMaxAggregation(aggResult, curLValue, curDValue, curBValue, curSValue); processMaxAggregation(aggResult, curLValue, curDValue, curBValue, curSValue, curJValue);
} }
} }
@ -136,7 +145,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
} }
} }
private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue, String curJValue) {
if (curDValue != null || curLValue != null) { if (curDValue != null || curLValue != null) {
if (curDValue != null) { if (curDValue != null) {
aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue); aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue);
@ -148,10 +157,12 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue && curBValue; aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue && curBValue;
} else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) < 0)) { } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) < 0)) {
aggResult.sValue = curSValue; aggResult.sValue = curSValue;
} else if (curJValue != null && (aggResult.jValue == null || curJValue.compareTo(aggResult.jValue) < 0)) {
aggResult.jValue = curJValue;
} }
} }
private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) { private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue, String curJValue) {
if (curDValue != null || curLValue != null) { if (curDValue != null || curLValue != null) {
if (curDValue != null) { if (curDValue != null) {
aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue); aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue);
@ -163,6 +174,8 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue || curBValue; aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue || curBValue;
} else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) > 0)) { } else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) > 0)) {
aggResult.sValue = curSValue; aggResult.sValue = curSValue;
} else if (curJValue != null && (aggResult.jValue == null || curJValue.compareTo(aggResult.jValue) > 0)) {
aggResult.jValue = curJValue;
} }
} }
@ -182,6 +195,14 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
} }
} }
private String getJsonValue(Row row) {
if (aggregation == Aggregation.MIN || aggregation == Aggregation.MAX) {
return row.getString(JSON_POS);
} else {
return null;
}
}
private Long getLongValue(Row row) { private Long getLongValue(Row row) {
if (aggregation == Aggregation.MIN || aggregation == Aggregation.MAX if (aggregation == Aggregation.MIN || aggregation == Aggregation.MAX
|| aggregation == Aggregation.SUM || aggregation == Aggregation.AVG) { || aggregation == Aggregation.SUM || aggregation == Aggregation.AVG) {
@ -223,7 +244,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) { if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) {
return Optional.empty(); return Optional.empty();
} else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) {
if(aggregation == Aggregation.AVG || aggResult.hasDouble) { if (aggregation == Aggregation.AVG || aggResult.hasDouble) {
double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L); double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L);
return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count)))); return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count))));
} else { } else {
@ -235,15 +256,17 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
private Optional<TsKvEntry> processMinOrMaxResult(AggregationResult aggResult) { private Optional<TsKvEntry> processMinOrMaxResult(AggregationResult aggResult) {
if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) { if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) {
if(aggResult.hasDouble) { if (aggResult.hasDouble) {
double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE); double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE);
double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE); double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE);
return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL)))); return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL))));
} else { } else {
return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue))); return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue)));
} }
} else if (aggResult.dataType == DataType.STRING) { } else if (aggResult.dataType == DataType.STRING) {
return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue))); return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue)));
} else if (aggResult.dataType == DataType.JSON) {
return Optional.of(new BasicTsKvEntry(ts, new JsonDataEntry(key, aggResult.jValue)));
} else { } else {
return Optional.of(new BasicTsKvEntry(ts, new BooleanDataEntry(key, aggResult.bValue))); return Optional.of(new BasicTsKvEntry(ts, new BooleanDataEntry(key, aggResult.bValue)));
} }
@ -253,6 +276,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
DataType dataType = null; DataType dataType = null;
Boolean bValue = null; Boolean bValue = null;
String sValue = null; String sValue = null;
String jValue = null;
Double dValue = null; Double dValue = null;
Long lValue = null; Long lValue = null;
long count = 0; long count = 0;

59
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -29,6 +29,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@ -337,21 +339,31 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON));
break; break;
case BOOLEAN: case BOOLEAN:
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON));
break; break;
case DOUBLE: case DOUBLE:
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON));
break; break;
case STRING: case STRING:
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG)); futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.JSON));
break;
case JSON:
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.BOOLEAN));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.DOUBLE));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.LONG));
futures.add(saveNull(tenantId, entityId, tsKvEntry, ttl, partition, DataType.STRING));
break; break;
} }
} }
@ -411,6 +423,13 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
.set(5, tsKvEntry.getStrValue().orElse(null), String.class) .set(5, tsKvEntry.getStrValue().orElse(null), String.class)
.set(6, tsKvEntry.getLongValue().orElse(null), Long.class) .set(6, tsKvEntry.getLongValue().orElse(null), Long.class)
.set(7, tsKvEntry.getDoubleValue().orElse(null), Double.class); .set(7, tsKvEntry.getDoubleValue().orElse(null), Double.class);
Optional<String> jsonV = tsKvEntry.getJsonValue();
if (jsonV.isPresent()) {
stmt.setString(8, tsKvEntry.getJsonValue().get());
} else {
stmt.setToNull(8);
}
return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null);
} }
@ -669,7 +688,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
if (boolV != null) { if (boolV != null) {
kvEntry = new BooleanDataEntry(key, boolV); kvEntry = new BooleanDataEntry(key, boolV);
} else { } else {
log.warn("All values in key-value row are nullable "); String jsonV = row.get(ModelConstants.JSON_VALUE_COLUMN, String.class);
if (StringUtils.isNoneEmpty(jsonV)) {
kvEntry = new JsonDataEntry(key, jsonV);
} else {
log.warn("All values in key-value row are nullable ");
}
} }
} }
} }
@ -772,8 +796,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
"," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.BOOLEAN_VALUE_COLUMN +
"," + ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.STRING_VALUE_COLUMN +
"," + ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN +
"," + ModelConstants.DOUBLE_VALUE_COLUMN + ")" + "," + ModelConstants.DOUBLE_VALUE_COLUMN +
" VALUES(?, ?, ?, ?, ?, ?, ?, ?)"); "," + ModelConstants.JSON_VALUE_COLUMN + ")" +
" VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)");
} }
return latestInsertStmt; return latestInsertStmt;
} }
@ -812,7 +837,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.STRING_VALUE_COLUMN + "," +
ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," +
ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + "," +
ModelConstants.DOUBLE_VALUE_COLUMN + " " + ModelConstants.DOUBLE_VALUE_COLUMN + "," +
ModelConstants.JSON_VALUE_COLUMN + " " +
"FROM " + ModelConstants.TS_KV_LATEST_CF + " " + "FROM " + ModelConstants.TS_KV_LATEST_CF + " " +
"WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM + "WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM +
"AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM +
@ -829,7 +855,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
ModelConstants.STRING_VALUE_COLUMN + "," + ModelConstants.STRING_VALUE_COLUMN + "," +
ModelConstants.BOOLEAN_VALUE_COLUMN + "," + ModelConstants.BOOLEAN_VALUE_COLUMN + "," +
ModelConstants.LONG_VALUE_COLUMN + "," + ModelConstants.LONG_VALUE_COLUMN + "," +
ModelConstants.DOUBLE_VALUE_COLUMN + " " + ModelConstants.DOUBLE_VALUE_COLUMN + "," +
ModelConstants.JSON_VALUE_COLUMN + " " +
"FROM " + ModelConstants.TS_KV_LATEST_CF + " " + "FROM " + ModelConstants.TS_KV_LATEST_CF + " " +
"WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM + "WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM +
"AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM); "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM);
@ -847,6 +874,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return ModelConstants.LONG_VALUE_COLUMN; return ModelConstants.LONG_VALUE_COLUMN;
case DOUBLE: case DOUBLE:
return ModelConstants.DOUBLE_VALUE_COLUMN; return ModelConstants.DOUBLE_VALUE_COLUMN;
case JSON:
return ModelConstants.JSON_VALUE_COLUMN;
default: default:
throw new RuntimeException("Not implemented!"); throw new RuntimeException("Not implemented!");
} }
@ -856,27 +885,23 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
switch (kvEntry.getDataType()) { switch (kvEntry.getDataType()) {
case BOOLEAN: case BOOLEAN:
Optional<Boolean> booleanValue = kvEntry.getBooleanValue(); Optional<Boolean> booleanValue = kvEntry.getBooleanValue();
if (booleanValue.isPresent()) { booleanValue.ifPresent(b -> stmt.setBool(column, b));
stmt.setBool(column, booleanValue.get().booleanValue());
}
break; break;
case STRING: case STRING:
Optional<String> stringValue = kvEntry.getStrValue(); Optional<String> stringValue = kvEntry.getStrValue();
if (stringValue.isPresent()) { stringValue.ifPresent(s -> stmt.setString(column, s));
stmt.setString(column, stringValue.get());
}
break; break;
case LONG: case LONG:
Optional<Long> longValue = kvEntry.getLongValue(); Optional<Long> longValue = kvEntry.getLongValue();
if (longValue.isPresent()) { longValue.ifPresent(l -> stmt.setLong(column, l));
stmt.setLong(column, longValue.get().longValue());
}
break; break;
case DOUBLE: case DOUBLE:
Optional<Double> doubleValue = kvEntry.getDoubleValue(); Optional<Double> doubleValue = kvEntry.getDoubleValue();
if (doubleValue.isPresent()) { doubleValue.ifPresent(d -> stmt.setDouble(column, d));
stmt.setDouble(column, doubleValue.get().doubleValue()); break;
} case JSON:
Optional<String> jsonValue = kvEntry.getJsonValue();
jsonValue.ifPresent(jsonObject -> stmt.setString(column, jsonObject));
break; break;
} }
} }

1
dao/src/main/resources/cassandra/schema-entities.cql

@ -410,6 +410,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.attributes_kv_cf (
str_v text, str_v text,
long_v bigint, long_v bigint,
dbl_v double, dbl_v double,
json_v text,
last_update_ts bigint, last_update_ts bigint,
PRIMARY KEY ((entity_type, entity_id, attribute_type), attribute_key) PRIMARY KEY ((entity_type, entity_id, attribute_type), attribute_key)
) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; ) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };

2
dao/src/main/resources/cassandra/schema-ts.cql

@ -30,6 +30,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf (
str_v text, str_v text,
long_v bigint, long_v bigint,
dbl_v double, dbl_v double,
json_v text,
PRIMARY KEY (( entity_type, entity_id, key, partition ), ts) PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)
); );
@ -51,5 +52,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf (
str_v text, str_v text,
long_v bigint, long_v bigint,
dbl_v double, dbl_v double,
json_v text,
PRIMARY KEY (( entity_type, entity_id ), key) PRIMARY KEY (( entity_type, entity_id ), key)
) WITH compaction = { 'class' : 'LeveledCompactionStrategy' }; ) WITH compaction = { 'class' : 'LeveledCompactionStrategy' };

3
dao/src/main/resources/cassandra/system-data.cql

@ -38,7 +38,8 @@ VALUES ( now ( ), 'mail', '{
"smtpHost": "localhost", "smtpHost": "localhost",
"smtpPort": "25", "smtpPort": "25",
"timeout": "10000", "timeout": "10000",
"enableTls": "false", "enableTls": false,
"tlsVersion": "TLSv1.2",
"username": "", "username": "",
"password": "" "password": ""
}' ); }' );

251
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -0,0 +1,251 @@
--
-- Copyright © 2016-2020 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
CREATE TABLE IF NOT EXISTS admin_settings (
id varchar(31) NOT NULL CONSTRAINT admin_settings_pkey PRIMARY KEY,
json_value varchar,
key varchar(255)
);
CREATE TABLE IF NOT EXISTS alarm (
id varchar(31) NOT NULL CONSTRAINT alarm_pkey PRIMARY KEY,
ack_ts bigint,
clear_ts bigint,
additional_info varchar,
end_ts bigint,
originator_id varchar(31),
originator_type integer,
propagate boolean,
severity varchar(255),
start_ts bigint,
status varchar(255),
tenant_id varchar(31),
propagate_relation_types varchar,
type varchar(255)
);
CREATE TABLE IF NOT EXISTS asset (
id varchar(31) NOT NULL CONSTRAINT asset_pkey PRIMARY KEY,
additional_info varchar,
customer_id varchar(31),
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id varchar(31),
type varchar(255),
CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name)
);
CREATE TABLE IF NOT EXISTS audit_log (
id varchar(31) NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY,
tenant_id varchar(31),
customer_id varchar(31),
entity_id varchar(31),
entity_type varchar(255),
entity_name varchar(255),
user_id varchar(31),
user_name varchar(255),
action_type varchar(255),
action_data varchar(1000000),
action_status varchar(255),
action_failure_details varchar(1000000)
);
CREATE TABLE IF NOT EXISTS attribute_kv (
entity_type varchar(255),
entity_id varchar(31),
attribute_type varchar(255),
attribute_key varchar(255),
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v varchar(10000000),
last_update_ts bigint,
CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key)
);
CREATE TABLE IF NOT EXISTS component_descriptor (
id varchar(31) NOT NULL CONSTRAINT component_descriptor_pkey PRIMARY KEY,
actions varchar(255),
clazz varchar UNIQUE,
configuration_descriptor varchar,
name varchar(255),
scope varchar(255),
search_text varchar(255),
type varchar(255)
);
CREATE TABLE IF NOT EXISTS customer (
id varchar(31) NOT NULL CONSTRAINT customer_pkey PRIMARY KEY,
additional_info varchar,
address varchar,
address2 varchar,
city varchar(255),
country varchar(255),
email varchar(255),
phone varchar(255),
search_text varchar(255),
state varchar(255),
tenant_id varchar(31),
title varchar(255),
zip varchar(255)
);
CREATE TABLE IF NOT EXISTS dashboard (
id varchar(31) NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
configuration varchar(10000000),
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id varchar(31),
title varchar(255)
);
CREATE TABLE IF NOT EXISTS device (
id varchar(31) NOT NULL CONSTRAINT device_pkey PRIMARY KEY,
additional_info varchar,
customer_id varchar(31),
type varchar(255),
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id varchar(31),
CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name)
);
CREATE TABLE IF NOT EXISTS device_credentials (
id varchar(31) NOT NULL CONSTRAINT device_credentials_pkey PRIMARY KEY,
credentials_id varchar,
credentials_type varchar(255),
credentials_value varchar,
device_id varchar(31),
CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id)
);
CREATE TABLE IF NOT EXISTS event (
id varchar(31) NOT NULL CONSTRAINT event_pkey PRIMARY KEY,
body varchar(10000000),
entity_id varchar(31),
entity_type varchar(255),
event_type varchar(255),
event_uid varchar(255),
tenant_id varchar(31),
CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid)
);
CREATE TABLE IF NOT EXISTS relation (
from_id varchar(31),
from_type varchar(255),
to_id varchar(31),
to_type varchar(255),
relation_type_group varchar(255),
relation_type varchar(255),
additional_info varchar,
CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
);
CREATE TABLE IF NOT EXISTS tb_user (
id varchar(31) NOT NULL CONSTRAINT tb_user_pkey PRIMARY KEY,
additional_info varchar,
authority varchar(255),
customer_id varchar(31),
email varchar(255) UNIQUE,
first_name varchar(255),
last_name varchar(255),
search_text varchar(255),
tenant_id varchar(31)
);
CREATE TABLE IF NOT EXISTS tenant (
id varchar(31) NOT NULL CONSTRAINT tenant_pkey PRIMARY KEY,
additional_info varchar,
address varchar,
address2 varchar,
city varchar(255),
country varchar(255),
email varchar(255),
phone varchar(255),
region varchar(255),
search_text varchar(255),
state varchar(255),
title varchar(255),
zip varchar(255)
);
CREATE TABLE IF NOT EXISTS user_credentials (
id varchar(31) NOT NULL CONSTRAINT user_credentials_pkey PRIMARY KEY,
activate_token varchar(255) UNIQUE,
enabled boolean,
password varchar(255),
reset_token varchar(255) UNIQUE,
user_id varchar(31) UNIQUE
);
CREATE TABLE IF NOT EXISTS widget_type (
id varchar(31) NOT NULL CONSTRAINT widget_type_pkey PRIMARY KEY,
alias varchar(255),
bundle_alias varchar(255),
descriptor varchar(1000000),
name varchar(255),
tenant_id varchar(31)
);
CREATE TABLE IF NOT EXISTS widgets_bundle (
id varchar(31) NOT NULL CONSTRAINT widgets_bundle_pkey PRIMARY KEY,
alias varchar(255),
search_text varchar(255),
tenant_id varchar(31),
title varchar(255)
);
CREATE TABLE IF NOT EXISTS rule_chain (
id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY,
additional_info varchar,
configuration varchar(10000000),
name varchar(255),
first_rule_node_id varchar(31),
root boolean,
debug_mode boolean,
search_text varchar(255),
tenant_id varchar(31)
);
CREATE TABLE IF NOT EXISTS rule_node (
id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
rule_chain_id varchar(31),
additional_info varchar,
configuration varchar(10000000),
type varchar(255),
name varchar(255),
debug_mode boolean,
search_text varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_view (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),
customer_id varchar(31),
type varchar(255),
name varchar(255),
keys varchar(10000000),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);

1
dao/src/main/resources/sql/schema-entities.sql

@ -74,6 +74,7 @@ CREATE TABLE IF NOT EXISTS attribute_kv (
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision, dbl_v double precision,
json_v json,
last_update_ts bigint, last_update_ts bigint,
CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key) CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key)
); );

2
dao/src/main/resources/sql/schema-timescale.sql

@ -25,6 +25,7 @@ CREATE TABLE IF NOT EXISTS tenant_ts_kv (
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision, dbl_v double precision,
json_v json,
CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts) CONSTRAINT tenant_ts_kv_pkey PRIMARY KEY (tenant_id, entity_id, key, ts)
); );
@ -42,5 +43,6 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest (
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision, dbl_v double precision,
json_v json,
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
); );

2
dao/src/main/resources/sql/schema-ts-hsql.sql

@ -24,6 +24,7 @@ CREATE TABLE IF NOT EXISTS ts_kv (
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision, dbl_v double precision,
json_v varchar(10000000),
CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts) CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts)
); );
@ -35,6 +36,7 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest (
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision, dbl_v double precision,
json_v varchar(10000000),
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
); );

4
dao/src/main/resources/sql/schema-ts-psql.sql

@ -21,7 +21,8 @@ CREATE TABLE IF NOT EXISTS ts_kv (
bool_v boolean, bool_v boolean,
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision dbl_v double precision,
json_v json
) PARTITION BY RANGE (ts); ) PARTITION BY RANGE (ts);
CREATE TABLE IF NOT EXISTS ts_kv_latest ( CREATE TABLE IF NOT EXISTS ts_kv_latest (
@ -32,6 +33,7 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest (
str_v varchar(10000000), str_v varchar(10000000),
long_v bigint, long_v bigint,
dbl_v double precision, dbl_v double precision,
json_v json,
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key) CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
); );

3
dao/src/main/resources/sql/system-data.sql

@ -38,7 +38,8 @@ VALUES ( '1e746126eaaefa6a91992ebcb67fe33', 'mail', '{
"smtpHost": "localhost", "smtpHost": "localhost",
"smtpPort": "25", "smtpPort": "25",
"timeout": "10000", "timeout": "10000",
"enableTls": "false", "enableTls": false,
"tlsVersion": "TLSv1.2",
"username": "", "username": "",
"password": "" "password": ""
}' ); }' );

2
dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java

@ -30,7 +30,7 @@ public class JpaDaoTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/system-data.sql"), Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
"sql/drop-all-tables.sql", "sql/drop-all-tables.sql",
"sql-test.properties" "sql-test.properties"
); );

2
dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java

@ -30,7 +30,7 @@ public class SqlDaoServiceTestSuite {
@ClassRule @ClassRule
public static CustomSqlUnit sqlUnit = new CustomSqlUnit( public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"),
"sql/drop-all-tables.sql", "sql/drop-all-tables.sql",
"sql-test.properties" "sql-test.properties"
); );

2
msa/tb/docker-cassandra/Dockerfile

@ -19,7 +19,7 @@ FROM thingsboard/openjdk8
RUN apt-get update RUN apt-get update
RUN apt-get install -y curl nmap procps RUN apt-get install -y curl nmap procps
RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null
RUN curl https://www.apache.org/dist/cassandra/KEYS | apt-key add - RUN curl -L https://www.apache.org/dist/cassandra/KEYS | apt-key add -
RUN apt-get update RUN apt-get update
RUN apt-get install -y cassandra cassandra-tools RUN apt-get install -y cassandra cassandra-tools
RUN update-rc.d cassandra disable RUN update-rc.d cassandra disable

13
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java

@ -20,8 +20,12 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.mail.javamail.MimeMessageHelper;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -137,10 +141,13 @@ public class TbSendEmailNode implements TbNode {
String protocol = this.config.getSmtpProtocol(); String protocol = this.config.getSmtpProtocol();
javaMailProperties.put("mail.transport.protocol", protocol); javaMailProperties.put("mail.transport.protocol", protocol);
javaMailProperties.put(MAIL_PROP + protocol + ".host", this.config.getSmtpHost()); javaMailProperties.put(MAIL_PROP + protocol + ".host", this.config.getSmtpHost());
javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort()+""); javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort() + "");
javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout()+""); javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout() + "");
javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(this.config.getUsername()))); javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(this.config.getUsername())));
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", Boolean.valueOf(this.config.isEnableTls()).toString()); javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", Boolean.valueOf(this.config.isEnableTls()).toString());
if (this.config.isEnableTls() && StringUtils.isNoneEmpty(this.config.getTlsVersion())) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", this.config.getTlsVersion());
}
return javaMailProperties; return javaMailProperties;
} }
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java

@ -29,6 +29,7 @@ public class TbSendEmailNodeConfiguration implements NodeConfiguration {
private String smtpProtocol; private String smtpProtocol;
private int timeout; private int timeout;
private boolean enableTls; private boolean enableTls;
private String tlsVersion;
@Override @Override
public TbSendEmailNodeConfiguration defaultConfiguration() { public TbSendEmailNodeConfiguration defaultConfiguration() {
@ -39,6 +40,7 @@ public class TbSendEmailNodeConfiguration implements NodeConfiguration {
configuration.setSmtpPort(25); configuration.setSmtpPort(25);
configuration.setTimeout(10000); configuration.setTimeout(10000);
configuration.setEnableTls(false); configuration.setEnableTls(false);
configuration.setTlsVersion("TLSv1.2");
return configuration; return configuration;
} }
} }

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonParseException;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -77,7 +79,8 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
} }
@Override @Override
public void destroy() { } public void destroy() {
}
protected abstract ListenableFuture<T> findEntityIdAsync(TbContext ctx, TbMsg msg); protected abstract ListenableFuture<T> findEntityIdAsync(TbContext ctx, TbMsg msg);
@ -168,6 +171,12 @@ public abstract class TbAbstractGetAttributesNode<C extends TbGetAttributesNodeC
case DOUBLE: case DOUBLE:
value.put(VALUE, r.getDoubleValue().get()); value.put(VALUE, r.getDoubleValue().get());
break; break;
case JSON:
try {
value.set(VALUE, mapper.readTree(r.getJsonValue().get()));
} catch (IOException e) {
throw new JsonParseException("Can't parse jsonValue: " + r.getJsonValue().get(), e);
}
} }
msg.getMetaData().putValue(r.getKey(), value.toString()); msg.getMetaData().putValue(r.getKey(), value.toString());
} }

9
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java

@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonParseException;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -39,6 +40,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -180,6 +182,13 @@ public class TbGetTelemetryNode implements TbNode {
case DOUBLE: case DOUBLE:
obj.put("value", entry.getDoubleValue().get()); obj.put("value", entry.getDoubleValue().get());
break; break;
case JSON:
try {
obj.set("value", mapper.readTree(entry.getJsonValue().get()));
} catch (IOException e) {
throw new JsonParseException("Can't parse jsonValue: " + entry.getJsonValue().get(), e);
}
break;
} }
return obj; return obj;
} }

2
ui/src/app/admin/admin.controller.js

@ -25,6 +25,8 @@ export default function AdminController(adminService, toast, $scope, $rootScope,
return protocol; return protocol;
}); });
vm.tlsVersions = ['TLSv1.0', 'TLSv1.1', 'TLSv1.2', 'TLSv1.3'];
$translate('admin.test-mail-sent').then(function (translation) { $translate('admin.test-mail-sent').then(function (translation) {
vm.testMailSent = translation; vm.testMailSent = translation;
}, function (translationId) { }, function (translationId) {

10
ui/src/app/admin/outgoing-mail-settings.tpl.html

@ -78,8 +78,16 @@
<div translate ng-message="md-maxlength">admin.timeout-invalid</div> <div translate ng-message="md-maxlength">admin.timeout-invalid</div>
</div> </div>
</md-input-container> </md-input-container>
<md-checkbox ng-disabled="$root.loading" ng-true-value="'true'" ng-false-value="'false'" <md-checkbox ng-disabled="$root.loading"
aria-label="{{ 'admin.enable-tls' | translate }}" ng-model="vm.settings.jsonValue.enableTls">{{ 'admin.enable-tls' | translate }}</md-checkbox> aria-label="{{ 'admin.enable-tls' | translate }}" ng-model="vm.settings.jsonValue.enableTls">{{ 'admin.enable-tls' | translate }}</md-checkbox>
<md-input-container class="md-block" ng-if="vm.settings.jsonValue.enableTls">
<label translate>admin.tls-version</label>
<md-select ng-disabled="$root.loading" ng-model="vm.settings.jsonValue.tlsVersion">
<md-option ng-repeat="tlsVersion in vm.tlsVersions" value="{{tlsVersion}}">
{{tlsVersion}}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block"> <md-input-container class="md-block">
<label translate>common.username</label> <label translate>common.username</label>
<input name="username" placeholder="{{ 'common.enter-username' | translate }}" ng-model="vm.settings.jsonValue.username"> <input name="username" placeholder="{{ 'common.enter-username' | translate }}" ng-model="vm.settings.jsonValue.username">

1
ui/src/app/locale/locale.constant-cs_CZ.json

@ -83,6 +83,7 @@
"timeout-required": "Hodnota Časový limit je povinná.", "timeout-required": "Hodnota Časový limit je povinná.",
"timeout-invalid": "Tohle nevypadá jako platný časový limit.", "timeout-invalid": "Tohle nevypadá jako platný časový limit.",
"enable-tls": "Povolit TLS", "enable-tls": "Povolit TLS",
"tls-version": "Verze TLS",
"send-test-mail": "Odeslat testovací zprávu" "send-test-mail": "Odeslat testovací zprávu"
}, },
"alarm": { "alarm": {

1
ui/src/app/locale/locale.constant-de_DE.json

@ -83,6 +83,7 @@
"timeout-required": "Wartezeit ist erforderlich.", "timeout-required": "Wartezeit ist erforderlich.",
"timeout-invalid": "Das ist keine gültige Wartezeit.", "timeout-invalid": "Das ist keine gültige Wartezeit.",
"enable-tls": "TLS aktivieren", "enable-tls": "TLS aktivieren",
"tls-version" : "TLS-Version",
"send-test-mail": "Test E-Mail senden", "send-test-mail": "Test E-Mail senden",
"security-settings": "Sicherheitseinstellungen", "security-settings": "Sicherheitseinstellungen",
"password-policy": "Kennwortrichtlinie", "password-policy": "Kennwortrichtlinie",

1
ui/src/app/locale/locale.constant-el_GR.json

@ -88,6 +88,7 @@
"timeout-required": "Απαιτείται τιμή Timeout.", "timeout-required": "Απαιτείται τιμή Timeout.",
"timeout-invalid": "Αυτή δε φαίνεται να είναι μια έγκυρη τιμή timeout.", "timeout-invalid": "Αυτή δε φαίνεται να είναι μια έγκυρη τιμή timeout.",
"enable-tls": "Ενεργοποίηση TLS", "enable-tls": "Ενεργοποίηση TLS",
"tls-version": "Έκδοση TLS",
"send-test-mail": "Αποστολή δοκιμαστικού μηνύματος", "send-test-mail": "Αποστολή δοκιμαστικού μηνύματος",
"use-system-mail-settings": "Χρήση των ρυθμίσεων διακομιστή αλληλογραφίας συστήματος", "use-system-mail-settings": "Χρήση των ρυθμίσεων διακομιστή αλληλογραφίας συστήματος",
"mail-templates": "Πρότυπα αλληλογραφίας", "mail-templates": "Πρότυπα αλληλογραφίας",

1
ui/src/app/locale/locale.constant-en_US.json

@ -86,6 +86,7 @@
"timeout-required": "Timeout is required.", "timeout-required": "Timeout is required.",
"timeout-invalid": "That doesn't look like a valid timeout.", "timeout-invalid": "That doesn't look like a valid timeout.",
"enable-tls": "Enable TLS", "enable-tls": "Enable TLS",
"tls-version": "TLS version",
"send-test-mail": "Send test mail", "send-test-mail": "Send test mail",
"security-settings": "Security settings", "security-settings": "Security settings",
"password-policy": "Password policy", "password-policy": "Password policy",

1
ui/src/app/locale/locale.constant-es_ES.json

@ -85,6 +85,7 @@
"timeout-required": "Tiempo de espera es requerido.", "timeout-required": "Tiempo de espera es requerido.",
"timeout-invalid": "Eso no parece un tiempo de espera válido.", "timeout-invalid": "Eso no parece un tiempo de espera válido.",
"enable-tls": "Habilitar TLS", "enable-tls": "Habilitar TLS",
"tls-version": "Versión TLS",
"send-test-mail": "Enviar correo de prueba", "send-test-mail": "Enviar correo de prueba",
"password-policy": "Política de contraseñas", "password-policy": "Política de contraseñas",
"security-settings": "Configuraciones de seguridad", "security-settings": "Configuraciones de seguridad",

1
ui/src/app/locale/locale.constant-fa_IR.json

@ -83,6 +83,7 @@
"timeout-required": ".مهلت مورد نياز است", "timeout-required": ".مهلت مورد نياز است",
"timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد", "timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد",
"enable-tls": "TLS فعال سازي", "enable-tls": "TLS فعال سازي",
"tls-version": "نسخه TLS",
"send-test-mail": "ارسال پيام آزمايشي" "send-test-mail": "ارسال پيام آزمايشي"
}, },
"alarm": { "alarm": {

3469
ui/src/app/locale/locale.constant-fr_FR.json

File diff suppressed because it is too large

1
ui/src/app/locale/locale.constant-it_IT.json

@ -84,6 +84,7 @@
"timeout-required": "Timeout obbligatorio.", "timeout-required": "Timeout obbligatorio.",
"timeout-invalid": "Timeout non valido.", "timeout-invalid": "Timeout non valido.",
"enable-tls": "Abilita TLS", "enable-tls": "Abilita TLS",
"tls-version" : "Versione TLS",
"send-test-mail": "Invia mail di test", "send-test-mail": "Invia mail di test",
"security-settings": "Settaggi di sicurezza", "security-settings": "Settaggi di sicurezza",
"password-policy": "Politica password", "password-policy": "Politica password",

3055
ui/src/app/locale/locale.constant-ja_JA.json

File diff suppressed because it is too large

1
ui/src/app/locale/locale.constant-ko_KR.json

@ -83,6 +83,7 @@
"timeout-required": "제한시간을 입력해야 합니다.", "timeout-required": "제한시간을 입력해야 합니다.",
"timeout-invalid": "올바른 제한시간이 아닙니다.", "timeout-invalid": "올바른 제한시간이 아닙니다.",
"enable-tls": "TLS 사용", "enable-tls": "TLS 사용",
"tls-version" : "TLS 버전",
"send-test-mail": "테스트 메일 보내기" "send-test-mail": "테스트 메일 보내기"
}, },
"alarm": { "alarm": {

1
ui/src/app/locale/locale.constant-lv_LV.json

@ -84,6 +84,7 @@
"timeout-required": "Timeout is required.", "timeout-required": "Timeout is required.",
"timeout-invalid": "That doesn't look like a valid timeout.", "timeout-invalid": "That doesn't look like a valid timeout.",
"enable-tls": "Enable TLS", "enable-tls": "Enable TLS",
"tls-version": "TLS version",
"send-test-mail": "Send test mail" "send-test-mail": "Send test mail"
}, },
"alarm": { "alarm": {

1
ui/src/app/locale/locale.constant-ru_RU.json

@ -85,6 +85,7 @@
"timeout-required": "Таймаут обязателен.", "timeout-required": "Таймаут обязателен.",
"timeout-invalid": "Недействительный таймаут.", "timeout-invalid": "Недействительный таймаут.",
"enable-tls": "Включить TLS", "enable-tls": "Включить TLS",
"tls-version" : "Версия TLS",
"send-test-mail": "Отправить пробное письмо", "send-test-mail": "Отправить пробное письмо",
"security-settings": "Настройки безопасности", "security-settings": "Настройки безопасности",
"password-policy": "Политика паролей", "password-policy": "Политика паролей",

1
ui/src/app/locale/locale.constant-tr_TR.json

@ -83,6 +83,7 @@
"timeout-required": "Zaman aşımı değeri gerekli.", "timeout-required": "Zaman aşımı değeri gerekli.",
"timeout-invalid": "Bu geçerli bir zaman aşımı gibi görünmüyor.", "timeout-invalid": "Bu geçerli bir zaman aşımı gibi görünmüyor.",
"enable-tls": "TLS'i etkinleştir.", "enable-tls": "TLS'i etkinleştir.",
"tls-version" : "TLS sürümü",
"send-test-mail": "Test e-postası gönder" "send-test-mail": "Test e-postası gönder"
}, },
"alarm": { "alarm": {

1
ui/src/app/locale/locale.constant-uk_UA.json

@ -87,6 +87,7 @@
"timeout-required": "Необхідно задати час очікування.", "timeout-required": "Необхідно задати час очікування.",
"timeout-invalid": "Це не схоже на правильний час очікування.", "timeout-invalid": "Це не схоже на правильний час очікування.",
"enable-tls": "Увімкнути TLS", "enable-tls": "Увімкнути TLS",
"tls-version" : "Версія TLS",
"send-test-mail": "Надіслати тестове повідомлення", "send-test-mail": "Надіслати тестове повідомлення",
"use-system-mail-settings": "Використовувати параметри системного поштового сервера", "use-system-mail-settings": "Використовувати параметри системного поштового сервера",
"mail-templates": "Шаблони електронної пошти", "mail-templates": "Шаблони електронної пошти",

1
ui/src/app/locale/locale.constant-zh_CN.json

@ -83,6 +83,7 @@
"timeout-required": "超时必填。", "timeout-required": "超时必填。",
"timeout-invalid": "这看起来不像有效的超时值。", "timeout-invalid": "这看起来不像有效的超时值。",
"enable-tls": "启用TLS", "enable-tls": "启用TLS",
"tls-version" : "TLS版本",
"send-test-mail": "发送测试邮件" "send-test-mail": "发送测试邮件"
}, },
"alarm": { "alarm": {

1
ui/src/app/locale/locale.constant-zh_TW.json

@ -83,6 +83,7 @@
        "timeout-required": "超時必填。",         "timeout-required": "超時必填。",
        "timeout-invalid": "這看起來不像有效的超時值。",         "timeout-invalid": "這看起來不像有效的超時值。",
        "enable-tls": "啟用TLS",         "enable-tls": "啟用TLS",
"tls-version": "TLS版本",
        "send-test-mail": "發送測試郵件"         "send-test-mail": "發送測試郵件"
    },     },
    "alarm": {     "alarm": {

Loading…
Cancel
Save