Browse Source

merged master

pull/7746/head
Serafym Tuhai 4 years ago
parent
commit
74d2fd9e1a
  1. 8
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  2. 8
      application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json
  3. 8
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  4. 4
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 16
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  6. 28
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  7. 4
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  8. 50
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  9. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java
  10. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
  11. 9
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  12. 38
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  14. 8
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java
  15. 15
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  16. 10
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  19. 18
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  20. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java
  21. 36
      application/src/main/resources/thingsboard.yml
  22. 7
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  23. 16
      application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java
  24. 12
      application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
  25. 2
      application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java
  26. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  27. 247
      application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
  28. 2
      application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
  29. 33
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
  30. 68
      application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java
  31. 1
      common/cluster-api/src/main/proto/queue.proto
  32. 1
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  33. 2
      common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java
  34. 2
      common/script/script-api/pom.xml
  35. 167
      common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbUtils.java
  36. 71
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java
  37. 42
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java
  38. 2
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbJson.java
  39. 319
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  40. 6
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelInvokeService.java
  41. 5
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelScript.java
  42. 8
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelScriptExecutionTask.java
  43. 98
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  44. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  45. 2
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java
  46. 27
      dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java
  47. 32
      docker/compose-utils.sh
  48. 2
      docker/docker-compose.aws-sqs.yml
  49. 6
      docker/docker-compose.cassandra.volumes.yml
  50. 2
      docker/docker-compose.confluent.yml
  51. 2
      docker/docker-compose.hybrid.yml
  52. 2
      docker/docker-compose.kafka.yml
  53. 6
      docker/docker-compose.postgres.volumes.yml
  54. 2
      docker/docker-compose.postgres.yml
  55. 2
      docker/docker-compose.prometheus-grafana.yml
  56. 2
      docker/docker-compose.pubsub.yml
  57. 2
      docker/docker-compose.rabbitmq.yml
  58. 26
      docker/docker-compose.redis-cluster.volumes.yml
  59. 2
      docker/docker-compose.redis-cluster.yml
  60. 6
      docker/docker-compose.redis.volumes.yml
  61. 2
      docker/docker-compose.redis.yml
  62. 2
      docker/docker-compose.service-bus.yml
  63. 30
      docker/docker-compose.volumes.yml
  64. 5
      docker/docker-compose.yml
  65. 41
      docker/docker-install-tb.sh
  66. 20
      docker/docker-remove-services.sh
  67. 20
      docker/docker-start-services.sh
  68. 20
      docker/docker-stop-services.sh
  69. 29
      docker/docker-update-service.sh
  70. 40
      docker/docker-upgrade-tb.sh
  71. 10
      msa/black-box-tests/pom.xml
  72. 34
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
  73. 121
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java
  74. 68
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java
  75. 37
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  76. 120
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java
  77. 85
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
  78. 106
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java
  79. 6
      pom.xml
  80. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  81. 7
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java
  82. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
  83. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNodeConfiguration.java
  84. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java
  85. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
  86. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java
  87. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java
  88. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
  89. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
  90. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java
  91. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
  92. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
  93. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
  94. 13
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java
  95. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java
  96. 37
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  97. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java
  98. 38
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java
  99. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeConfiguration.java
  100. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java

8
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -57,9 +57,9 @@
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -71,9 +71,9 @@
"name": "Log Other",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{

8
application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json

@ -70,9 +70,9 @@
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -84,9 +84,9 @@
"name": "Log Other",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{

8
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -57,9 +57,9 @@
"name": "Log RPC from Device",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{
@ -71,9 +71,9 @@
"name": "Log Other",
"debugMode": false,
"configuration": {
"scriptLang": "MVEL",
"scriptLang": "TBEL",
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);",
"mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
"tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
}
},
{

4
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -33,7 +33,7 @@ import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.cluster.TbClusterService;
@ -274,7 +274,7 @@ public class ActorSystemContext {
@Autowired(required = false)
@Getter
private MvelInvokeService mvelInvokeService;
private TbelInvokeService tbelInvokeService;
@Autowired
@Getter

16
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -97,7 +97,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine;
import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine;
import java.util.Collections;
import java.util.List;
@ -474,11 +474,11 @@ class DefaultTbContext implements TbContext {
return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsInvokeService(), script, argNames);
}
private ScriptEngine createMvelScriptEngine(String script, String... argNames) {
if (mainCtx.getMvelInvokeService() == null) {
throw new RuntimeException("MVEL execution is disabled!");
private ScriptEngine createTbelScriptEngine(String script, String... argNames) {
if (mainCtx.getTbelInvokeService() == null) {
throw new RuntimeException("TBEL execution is disabled!");
}
return new RuleNodeMvelScriptEngine(getTenantId(), mainCtx.getMvelInvokeService(), script, argNames);
return new RuleNodeTbelScriptEngine(getTenantId(), mainCtx.getTbelInvokeService(), script, argNames);
}
@Override
@ -492,11 +492,11 @@ class DefaultTbContext implements TbContext {
switch (scriptLang) {
case JS:
return createJsScriptEngine(script, argNames);
case MVEL:
case TBEL:
if (Arrays.isNullOrEmpty(argNames)) {
return createMvelScriptEngine(script, "msg", "metadata", "msgType");
return createTbelScriptEngine(script, "msg", "metadata", "msgType");
} else {
return createMvelScriptEngine(script, argNames);
return createTbelScriptEngine(script, argNames);
}
default:
throw new RuntimeException("Unsupported script language: " + scriptLang.name());

28
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -38,7 +38,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.script.api.js.JsInvokeService;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.EventInfo;
@ -69,7 +69,7 @@ import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.rule.TbRuleChainService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine;
import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
@ -146,7 +146,7 @@ public class RuleChainController extends BaseController {
private JsInvokeService jsInvokeService;
@Autowired(required = false)
private MvelInvokeService mvelInvokeService;
private TbelInvokeService tbelInvokeService;
@Autowired(required = false)
private ActorSystemContext actorContext;
@ -154,8 +154,8 @@ public class RuleChainController extends BaseController {
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}")
private boolean debugPerTenantEnabled;
@Value("${mvel.enabled:true}")
private boolean mvelEnabled;
@Value("${tbel.enabled:true}")
private boolean tbelEnabled;
@ApiOperation(value = "Get Rule Chain (getRuleChainById)",
notes = "Fetch the Rule Chain object based on the provided Rule Chain Id. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@ -378,13 +378,13 @@ public class RuleChainController extends BaseController {
}
}
@ApiOperation(value = "Is MVEL script executor enabled",
notes = "Returns 'True' if the MVEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
@ApiOperation(value = "Is TBEL script executor enabled",
notes = "Returns 'True' if the TBEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/mvelEnabled", method = RequestMethod.GET)
@RequestMapping(value = "/ruleChain/tbelEnabled", method = RequestMethod.GET)
@ResponseBody
public Boolean isMvelEnabled() {
return mvelEnabled;
public Boolean isTbelEnabled() {
return tbelEnabled;
}
@ApiOperation(value = "Test Script function",
@ -393,7 +393,7 @@ public class RuleChainController extends BaseController {
@RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
@ResponseBody
public JsonNode testScript(
@ApiParam(value = "Script language: JS or MVEL")
@ApiParam(value = "Script language: JS or TBEL")
@RequestParam(required = false) ScriptLanguage scriptLang,
@ApiParam(value = "Test JS request. See API call description above.")
@RequestBody JsonNode inputParams) throws ThingsboardException {
@ -418,10 +418,10 @@ public class RuleChainController extends BaseController {
if (ScriptLanguage.JS.equals(scriptLang)) {
engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames);
} else {
if (mvelInvokeService == null) {
throw new IllegalArgumentException("MVEL script engine is disabled!");
if (tbelInvokeService == null) {
throw new IllegalArgumentException("TBEL script engine is disabled!");
}
engine = new RuleNodeMvelScriptEngine(getTenantId(), mvelInvokeService, script, argNames);
engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames);
}
TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data);
switch (scriptType) {

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

@ -661,10 +661,8 @@ public class TelemetryController extends BaseController {
logAttributesDeleted(user, entityId, scope, keys, null);
if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) {
DeviceId deviceId = new DeviceId(entityId.getId());
Set<AttributeKey> keysToNotify = new HashSet<>();
keys.forEach(key -> keysToNotify.add(new AttributeKey(scope, key)));
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
user.getTenantId(), deviceId, keysToNotify), null);
user.getTenantId(), deviceId, scope, keys), null);
}
result.setResult(new ResponseEntity<>(HttpStatus.OK));
}

50
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -187,10 +187,7 @@ public final class EdgeGrpcSession implements Closeable {
public void startSyncProcess(TenantId tenantId, EdgeId edgeId, boolean fullSync) {
log.trace("[{}][{}] Staring edge sync process", tenantId, edgeId);
syncCompleted = false;
if (sessionState.getSendDownlinkMsgsFuture() != null && sessionState.getSendDownlinkMsgsFuture().isDone()) {
String errorMsg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId);
sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg));
}
interruptGeneralProcessingOnSync(tenantId, edgeId);
doSync(new EdgeSyncCursor(ctx, edge, fullSync));
}
@ -265,10 +262,7 @@ public final class EdgeGrpcSession implements Closeable {
}
if (sessionState.getPendingMsgsMap().isEmpty()) {
log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey());
if (sessionState.getScheduledSendDownlinkTask() != null) {
sessionState.getScheduledSendDownlinkTask().cancel(false);
}
sessionState.getSendDownlinkMsgsFuture().set(null);
stopCurrentSendDownlinkMsgsTask(null);
}
} catch (Exception e) {
log.error("[{}] Can't process downlink response message [{}]", this.sessionId, msg, e);
@ -391,15 +385,14 @@ public final class EdgeGrpcSession implements Closeable {
}
private ListenableFuture<Void> sendDownlinkMsgsPack(List<DownlinkMsg> downlinkMsgsPack) {
if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) {
String errorMsg = "[" + this.sessionId + "] Previous send downlink future was not properly completed, stopping it now";
log.error(errorMsg);
sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg));
}
interruptPreviousSendDownlinkMsgsTask();
sessionState.setSendDownlinkMsgsFuture(SettableFuture.create());
sessionState.getPendingMsgsMap().clear();
downlinkMsgsPack.forEach(msg -> sessionState.getPendingMsgsMap().put(msg.getDownlinkMsgId(), msg));
scheduleDownlinkMsgsPackSend(1);
return sessionState.getSendDownlinkMsgsFuture();
}
@ -422,13 +415,13 @@ public final class EdgeGrpcSession implements Closeable {
} else {
log.warn("[{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}",
this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy);
sessionState.getSendDownlinkMsgsFuture().set(null);
stopCurrentSendDownlinkMsgsTask(null);
}
} else {
sessionState.getSendDownlinkMsgsFuture().set(null);
stopCurrentSendDownlinkMsgsTask(null);
}
} catch (Exception e) {
sessionState.getSendDownlinkMsgsFuture().setException(e);
stopCurrentSendDownlinkMsgsTask(e);
}
};
@ -673,4 +666,29 @@ public final class EdgeGrpcSession implements Closeable {
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage());
}
}
private void interruptPreviousSendDownlinkMsgsTask() {
String msg = String.format("[%s] Previous send downlink future was not properly completed, stopping it now!", this.sessionId);
stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg));
}
private void interruptGeneralProcessingOnSync(TenantId tenantId, EdgeId edgeId) {
String msg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId);
stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg));
}
public void stopCurrentSendDownlinkMsgsTask(Exception e) {
if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) {
if (e != null) {
log.warn(e.getMessage(), e);
sessionState.getSendDownlinkMsgsFuture().setException(e);
} else {
sessionState.getSendDownlinkMsgsFuture().set(null);
}
}
if (sessionState.getScheduledSendDownlinkTask() != null) {
sessionState.getScheduledSendDownlinkTask().cancel(true);
}
}
}

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.SettableFuture;
import lombok.Data;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
@ -26,7 +27,7 @@ import java.util.concurrent.ScheduledFuture;
@Data
public class EdgeSessionState {
private final Map<Integer, DownlinkMsg> pendingMsgsMap = new LinkedHashMap<>();
private final Map<Integer, DownlinkMsg> pendingMsgsMap = Collections.synchronizedMap(new LinkedHashMap<>());
private SettableFuture<Void> sendDownlinkMsgsFuture;
private ScheduledFuture<?> scheduledSendDownlinkTask;
}

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java

@ -287,12 +287,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
List<String> attributeNames = attributeDeleteMsg.getAttributeNamesList();
attributesService.removeAll(tenantId, entityId, scope, attributeNames);
if (EntityType.DEVICE.name().equals(entityType)) {
Set<AttributeKey> attributeKeys = new HashSet<>();
for (String attributeName : attributeNames) {
attributeKeys.add(new AttributeKey(scope, attributeName));
}
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(
tenantId, (DeviceId) entityId, attributeKeys), new TbQueueCallback() {
tenantId, (DeviceId) entityId, scope, attributeNames), new TbQueueCallback() {
@Override
public void onSuccess(TbQueueMsgMetadata metadata) {
futureToSet.set(null);

9
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -181,10 +181,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
private void update(TenantId tenantId, DeviceProfile deviceProfile, OtaPackageType otaPackageType) {
Consumer<Device> updateConsumer;
OtaPackageId packageId = OtaPackageUtil.getOtaPackageId(deviceProfile, otaPackageType);
if (deviceProfile.getFirmwareId() != null) {
if (packageId != null) {
long ts = System.currentTimeMillis();
updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, otaPackageType);
updateConsumer = d -> send(d.getTenantId(), d.getId(), packageId, ts, otaPackageType);
} else {
updateConsumer = d -> remove(d, otaPackageType);
}
@ -360,9 +361,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
Set<AttributeKey> keysToNotify = new HashSet<>();
attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys), null);
}
@Override

38
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -88,10 +88,8 @@ public class DefaultTbClusterService implements TbClusterService {
@Value("${cluster.stats.enabled:false}")
private boolean statsEnabled;
@Value("${edges.enabled}")
@Value("${edges.enabled:true}")
protected boolean edgesEnabled;
@Value("${service.type:monolith}")
private String serviceType;
private final AtomicInteger toCoreMsgs = new AtomicInteger(0);
private final AtomicInteger toCoreNfs = new AtomicInteger(0);
@ -584,25 +582,27 @@ public class DefaultTbClusterService implements TbClusterService {
}
private void doSendQueueNotifications(ToRuleEngineNotificationMsg ruleEngineMsg, ToCoreNotificationMsg coreMsg, ToTransportMsg transportMsg) {
Set<TransportProtos.ServiceInfo> tbRuleEngineServices = partitionService.getAllServices(ServiceType.TB_RULE_ENGINE);
for (TransportProtos.ServiceInfo ruleEngineService : tbRuleEngineServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngineService.getServiceId());
Set<String> tbRuleEngineServices = partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE);
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
// No need to push notifications twice
tbTransportServices.removeAll(tbCoreServices);
tbCoreServices.removeAll(tbRuleEngineServices);
for (String ruleEngineServiceId : tbRuleEngineServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngineServiceId);
producerProvider.getRuleEngineNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), ruleEngineMsg), null);
toRuleEngineNfs.incrementAndGet();
}
if (!serviceType.equals("monolith")) {
Set<TransportProtos.ServiceInfo> tbCoreServices = partitionService.getAllServices(ServiceType.TB_CORE);
for (TransportProtos.ServiceInfo coreService : tbCoreServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, coreService.getServiceId());
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), coreMsg), null);
toCoreNfs.incrementAndGet();
}
Set<TransportProtos.ServiceInfo> tbTransportServices = partitionService.getAllServices(ServiceType.TB_TRANSPORT);
for (TransportProtos.ServiceInfo transportService : tbTransportServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportService.getServiceId());
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
toTransportNfs.incrementAndGet();
}
for (String coreServiceId : tbCoreServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, coreServiceId);
producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), coreMsg), null);
toCoreNfs.incrementAndGet();
}
for (String transportServiceId : tbTransportServices) {
TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
toTransportNfs.incrementAndGet();
}
}
}

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -490,7 +490,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.onAttributesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), callback);
proto.getScope(), proto.getKeysList(), proto.getNotifyDevice(), callback);
} else if (msg.hasTsDelete()) {
TbTimeSeriesDeleteProto proto = msg.getTsDelete();
subscriptionManagerService.onTimeSeriesDelete(

8
application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java → application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java

@ -23,7 +23,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.RuleNodeScriptFactory;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
@ -42,9 +42,9 @@ import java.util.stream.Collectors;
@Slf4j
public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine<MvelInvokeService, Object> {
public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine<TbelInvokeService, Object> {
public RuleNodeMvelScriptEngine(TenantId tenantId, MvelInvokeService scriptInvokeService, String script, String... argNames) {
public RuleNodeTbelScriptEngine(TenantId tenantId, TbelInvokeService scriptInvokeService, String script, String... argNames) {
super(tenantId, scriptInvokeService, script, argNames);
}
@ -64,7 +64,7 @@ public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine<MvelInvokeSer
List<TbMsg> res = new ArrayList<>();
for (Object resObject : (Collection) result) {
if (resObject instanceof Map) {
res.add(unbindMsg((Map) result, msg));
res.add(unbindMsg((Map) resObject, msg));
} else {
return wrongResultType(resObject);
}

15
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -104,10 +104,10 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(),
registration);
JwtPair tokenPair = tokenFactory.createTokenPair(securityUser);
clearAuthenticationAttributes(request, response);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken());
JwtPair tokenPair = tokenFactory.createTokenPair(securityUser);
getRedirectStrategy().sendRedirect(request, response, getRedirectUrl(baseUrl, tokenPair));
systemSecurityService.logLoginAction(securityUser, new RestAuthenticationDetails(request), ActionType.LOGIN, registration.getName(), null);
} catch (Exception e) {
log.debug("Error occurred during processing authentication success result. " +
@ -128,4 +128,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
super.clearAuthenticationAttributes(request);
httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
}
String getRedirectUrl(String baseUrl, JwtPair tokenPair) {
if (baseUrl.indexOf("?") > 0) {
baseUrl += "&";
} else {
baseUrl += "/?";
}
return baseUrl + "accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken();
}
}

10
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -326,7 +326,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
@Override
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback callback) {
public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
@ -349,7 +349,13 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return subscriptionUpdate;
}, false);
if (entityId.getEntityType() == EntityType.DEVICE) {
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)
|| TbAttributeSubscriptionScope.ANY_SCOPE.name().equalsIgnoreCase(scope)) {
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
} else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId,
new DeviceId(entityId.getId()), scope, keys), null);
}
}
callback.onSuccess();
}

2
application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java

@ -38,7 +38,7 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, TbCallback empty);
void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback);

3
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -236,7 +236,7 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List<String> keys) {
public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice) {
TbAttributeDeleteProto.Builder builder = TbAttributeDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
@ -245,6 +245,7 @@ public class TbSubscriptionUtils {
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setScope(scope);
builder.addAllKeys(keys);
builder.setNotifyDevice(notifyDevice);
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setAttrDelete(builder);

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

@ -271,14 +271,20 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, callback);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback);
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback) {
public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
checkInternalEntity(entityId);
deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback);
}
@Override
public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback) {
ListenableFuture<List<String>> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys));
addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice));
}
@Override
@ -382,16 +388,16 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}
}
private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys) {
private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY);
subscriptionManagerService.get().onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys);
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}

2
application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java

@ -37,7 +37,7 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService {
void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback);
void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback);

36
application/src/main/resources/thingsboard.yml

@ -148,7 +148,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.1}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.2}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -611,25 +611,25 @@ state:
defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}"
persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}"
mvel:
enabled: "${MVEL_ENABLED:true}"
max_total_args_size: "${MVEL_MAX_TOTAL_ARGS_SIZE:100000}"
max_result_size: "${MVEL_MAX_RESULT_SIZE:300000}"
max_script_body_size: "${MVEL_MAX_SCRIPT_BODY_SIZE:50000}"
# Maximum allowed MVEL script execution memory
max_memory_limit_mb: "${MVEL_MAX_MEMORY_LIMIT_MB: 8}"
# Maximum allowed MVEL script execution errors before it will be blacklisted
max_errors: "${MVEL_MAX_ERRORS:3}"
# MVEL Eval max request timeout in milliseconds. 0 - no timeout
max_requests_timeout: "${MVEL_MAX_REQUEST_TIMEOUT:500}"
tbel:
enabled: "${TBEL_ENABLED:true}"
max_total_args_size: "${TBEL_MAX_TOTAL_ARGS_SIZE:100000}"
max_result_size: "${TBEL_MAX_RESULT_SIZE:300000}"
max_script_body_size: "${TBEL_MAX_SCRIPT_BODY_SIZE:50000}"
# Maximum allowed TBEL script execution memory
max_memory_limit_mb: "${TBEL_MAX_MEMORY_LIMIT_MB: 8}"
# Maximum allowed TBEL script execution errors before it will be blacklisted
max_errors: "${TBEL_MAX_ERRORS:3}"
# TBEL Eval max request timeout in milliseconds. 0 - no timeout
max_requests_timeout: "${TBEL_MAX_REQUEST_TIMEOUT:500}"
# Maximum time in seconds for black listed function to stay in the list.
max_black_list_duration_sec: "${MVEL_MAX_BLACKLIST_DURATION_SEC:60}"
max_black_list_duration_sec: "${TBEL_MAX_BLACKLIST_DURATION_SEC:60}"
# Specify thread pool size for javascript executor service
thread_pool_size: "${MVEL_THREAD_POOL_SIZE:50}"
compiled_scripts_cache_size: "${MVEL_COMPILED_SCRIPTS_CACHE_SIZE:1000}"
thread_pool_size: "${TBEL_THREAD_POOL_SIZE:50}"
compiled_scripts_cache_size: "${TBEL_COMPILED_SCRIPTS_CACHE_SIZE:1000}"
stats:
enabled: "${TB_MVEL_STATS_ENABLED:false}"
print_interval_ms: "${TB_MVEL_STATS_PRINT_INTERVAL_MS:10000}"
enabled: "${TB_TBEL_STATS_ENABLED:false}"
print_interval_ms: "${TB_TBEL_STATS_PRINT_INTERVAL_MS:10000}"
js:
evaluator: "${JS_EVALUATOR:local}" # local/remote
@ -930,7 +930,7 @@ edges:
storage:
max_read_records_count: "${EDGES_STORAGE_MAX_READ_RECORDS_COUNT:50}"
no_read_records_sleep: "${EDGES_NO_READ_RECORDS_SLEEP:1000}"
sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:1000}"
sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:10000}"
scheduler_pool_size: "${EDGES_SCHEDULER_POOL_SIZE:1}"
send_scheduler_pool_size: "${EDGES_SEND_SCHEDULER_POOL_SIZE:1}"
grpc_callback_thread_pool_size: "${EDGES_GRPC_CALLBACK_POOL_SIZE:1}"

7
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -88,6 +88,7 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
@ -768,4 +769,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus());
}
protected <T> T getFieldValue(Object target, String fieldName) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(target);
}
}

16
application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java

@ -44,6 +44,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@ -202,6 +203,21 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest
});
}
@Test
public void whenSavingAuditLogAndPartitionSaveErrorOccurred_thenSaveAuditLogAnyway() throws Exception {
// creating partition bigger than sql.audit_logs.partition_size
partitioningRepository.createPartitionIfNotExists("audit_log", System.currentTimeMillis(), TimeUnit.DAYS.toMillis(7));
List<Long> partitions = partitioningRepository.fetchPartitions("audit_log");
assertThat(partitions).size().isOne();
partitioningRepository.cleanupPartitionsCache("audit_log", System.currentTimeMillis(), 0);
assertDoesNotThrow(() -> {
// expecting partition overlap error on partition save
createAuditLog(ActionType.LOGIN, tenantAdminUserId);
});
assertThat(partitioningRepository.fetchPartitions("audit_log")).isEqualTo(partitions);
}
private AuditLog createAuditLog(ActionType actionType, EntityId entityId) {
AuditLog auditLog = new AuditLog();
auditLog.setTenantId(tenantId);

12
application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java

@ -439,9 +439,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Assert.assertTrue(edgeImitator.waitForResponses());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getMessageFromTail(2);
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Optional<DeviceUpdateMsg> deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class);
Assert.assertTrue(deviceUpdateMsgOpt.isPresent());
DeviceUpdateMsg latestDeviceUpdateMsg = deviceUpdateMsgOpt.get();
Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName());
Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName());
@ -453,9 +453,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(device);
Assert.assertNotEquals(deviceOnCloudName, device.getName());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg);
DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage;
Optional<DeviceCredentialsRequestMsg> deviceCredentialsUpdateMsgOpt = edgeImitator.findMessageByType(DeviceCredentialsRequestMsg.class);
Assert.assertTrue(deviceCredentialsUpdateMsgOpt.isPresent());
DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = deviceCredentialsUpdateMsgOpt.get();
Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB());
Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB());

2
application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java

@ -34,7 +34,7 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest {
@Test
public void testTimeseriesWithFailures() throws Exception {
int numberOfTimeseriesToSend = 1000;
int numberOfTimeseriesToSend = 333;
Device device = findDeviceByName("Edge Device 1");

6
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -365,11 +365,7 @@ public class EdgeImitator {
}
public AbstractMessage getLatestMessage() {
return getMessageFromTail(1);
}
public AbstractMessage getMessageFromTail(int offset) {
return downlinkMsgs.get(downlinkMsgs.size() - offset);
return downlinkMsgs.get(downlinkMsgs.size() - 1);
}
public void ignoreType(Class<? extends AbstractMessage> type) {

247
application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java

@ -0,0 +1,247 @@
/**
* Copyright © 2016-2022 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.queue;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.NotificationsTopicService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.gateway_device.GatewayNotificationsService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import java.util.UUID;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = DefaultTbClusterService.class)
public class DefaultTbClusterServiceTest {
public static final String MONOLITH = "monolith";
public static final String CORE = "core";
public static final String RULE_ENGINE = "rule_engine";
public static final String TRANSPORT = "transport";
@MockBean
protected DataDecodingEncodingService encodingService;
@MockBean
protected TbDeviceProfileCache deviceProfileCache;
@MockBean
protected TbAssetProfileCache assetProfileCache;
@MockBean
protected GatewayNotificationsService gatewayNotificationsService;
@MockBean
protected PartitionService partitionService;
@MockBean
protected TbQueueProducerProvider producerProvider;
@SpyBean
protected NotificationsTopicService notificationsTopicService;
@SpyBean
protected TbClusterService clusterService;
@Test
public void testOnQueueChangeSingleMonolith() {
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(MONOLITH));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH);
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any());
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_TRANSPORT), any());
verify(tbQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(producerProvider, never()).getTbCoreNotificationsMsgProducer();
verify(producerProvider, never()).getTransportNotificationsMsgProducer();
}
@Test
public void testOnQueueChangeMultipleMonoliths() {
String monolith1 = MONOLITH + 1;
String monolith2 = MONOLITH + 2;
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(monolith1, monolith2));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(monolith1, monolith2));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(monolith1, monolith2));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2);
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any());
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_TRANSPORT), any());
verify(tbQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2)), any(TbProtoQueueMsg.class), isNull());
verify(producerProvider, never()).getTbCoreNotificationsMsgProducer();
verify(producerProvider, never()).getTransportNotificationsMsgProducer();
}
@Test
public void testOnQueueChangeSingleMonolithAndSingleRemoteTransport() {
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(MONOLITH));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(MONOLITH, TRANSPORT));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbREQueueProducer = mock(TbQueueProducer.class);
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToTransportMsg>> tbTransportQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer);
when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT);
verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, MONOLITH)), any(TbProtoQueueMsg.class), isNull());
verify(producerProvider, never()).getTbCoreNotificationsMsgProducer();
}
@Test
public void testOnQueueChangeMultipleMicroservices() {
String monolith1 = MONOLITH + 1;
String monolith2 = MONOLITH + 2;
String core1 = CORE + 1;
String core2 = CORE + 2;
String ruleEngine1 = RULE_ENGINE + 1;
String ruleEngine2 = RULE_ENGINE + 2;
String transport1 = TRANSPORT + 1;
String transport2 = TRANSPORT + 2;
when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(monolith1, monolith2, ruleEngine1, ruleEngine2));
when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(monolith1, monolith2, core1, core2));
when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(monolith1, monolith2, transport1, transport2));
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToRuleEngineNotificationMsg>> tbREQueueProducer = mock(TbQueueProducer.class);
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCoreNotificationMsg>> tbCoreQueueProducer = mock(TbQueueProducer.class);
TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToTransportMsg>> tbTransportQueueProducer = mock(TbQueueProducer.class);
when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer);
when(producerProvider.getTbCoreNotificationsMsgProducer()).thenReturn(tbCoreQueueProducer);
when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer);
clusterService.onQueueChange(createTestQueue());
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine2);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_CORE, core1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_CORE, core2);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_CORE, monolith1);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_CORE, monolith2);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, transport1);
verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, transport2);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith1);
verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith2);
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2)), any(TbProtoQueueMsg.class), isNull());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine1)), any(TbProtoQueueMsg.class), isNull());
verify(tbREQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine2)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, core1)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, core2)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbCoreQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, monolith2)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transport1)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, times(1))
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transport2)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith1)), any(TbProtoQueueMsg.class), isNull());
verify(tbTransportQueueProducer, never())
.send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith2)), any(TbProtoQueueMsg.class), isNull());
}
protected Queue createTestQueue() {
TenantId tenantId = TenantId.SYS_TENANT_ID;
Queue queue = new Queue(new QueueId(UUID.randomUUID()));
queue.setTenantId(tenantId);
queue.setName("Main");
queue.setTopic("main");
queue.setPartitions(10);
return queue;
}
}

2
application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java

@ -70,7 +70,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
}
long duration = System.currentTimeMillis() - startTs;
System.out.println(iterations + " invocations took: " + duration + "ms");
Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(3));
Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(4));
}
@Test

33
application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java → application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java

@ -24,14 +24,13 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.mvel.MvelInvokeService;
import org.thingsboard.script.api.mvel.MvelScript;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.script.api.tbel.TbelScript;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -45,18 +44,18 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DaoSqlTest
@TestPropertySource(properties = {
"mvel.max_script_body_size=100",
"mvel.max_total_args_size=50",
"mvel.max_result_size=50",
"mvel.max_errors=2",
"mvel.compiled_scripts_cache_size=100"
"tbel.max_script_body_size=100",
"tbel.max_total_args_size=50",
"tbel.max_result_size=50",
"tbel.max_errors=2",
"tbel.compiled_scripts_cache_size=100"
})
class MvelInvokeServiceTest extends AbstractControllerTest {
class TbelInvokeServiceTest extends AbstractControllerTest {
@Autowired
private MvelInvokeService invokeService;
private TbelInvokeService invokeService;
@Value("${mvel.max_errors}")
@Value("${tbel.max_errors}")
private int maxJsErrors;
@Test
@ -129,7 +128,7 @@ class MvelInvokeServiceTest extends AbstractControllerTest {
}
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Map<String, MvelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Map<String, TbelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
@ -149,7 +148,7 @@ class MvelInvokeServiceTest extends AbstractControllerTest {
}
Map<UUID, String> scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash");
Map<String, MvelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Map<String, TbelScript> scriptMap = getFieldValue(invokeService, "scriptMap");
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
String scriptHash = scriptIdToHash.get(scriptsIds.get(0));
@ -173,7 +172,7 @@ class MvelInvokeServiceTest extends AbstractControllerTest {
Cache<String, Serializable> compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache");
List<UUID> scriptsIds = new ArrayList<>();
for (int i = 0; i < 110; i++) { // mvel.compiled_scripts_cache_size = 100
for (int i = 0; i < 110; i++) { // tbel.compiled_scripts_cache_size = 100
String script = "return msg.temperature > " + i;
UUID scriptId = evalScript(script);
scriptsIds.add(scriptId);
@ -217,10 +216,4 @@ class MvelInvokeServiceTest extends AbstractControllerTest {
return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString();
}
private <T> T getFieldValue(Object target, String fieldName) throws Exception {
Field field = target.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return (T) field.get(target);
}
}

68
application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java

@ -0,0 +1,68 @@
/**
* Copyright © 2016-2022 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.security.auth.oauth2;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.model.JwtPair;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import java.util.UUID;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@DaoSqlTest
public class Oauth2AuthenticationSuccessHandlerTest extends AbstractControllerTest {
@Autowired
private Oauth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
@Mock
private JwtTokenFactory jwtTokenFactory;
private SecurityUser securityUser;
@Before
public void before() {
UserId userId = new UserId(UUID.randomUUID());
securityUser = new SecurityUser(userId);
when(jwtTokenFactory.createTokenPair(eq(securityUser))).thenReturn(new JwtPair("testAccessToken", "testRefreshToken"));
}
@Test
public void testGetRedirectUrl() {
JwtPair jwtPair = jwtTokenFactory.createTokenPair(securityUser);
String urlWithoutParams = "http://localhost:8080/dashboardGroups/3fa13530-6597-11ed-bd76-8bd591f0ec3e";
String urlWithParams = "http://localhost:8080/dashboardGroups/3fa13530-6597-11ed-bd76-8bd591f0ec3e?state=someState&page=1";
String redirectUrl = oauth2AuthenticationSuccessHandler.getRedirectUrl(urlWithoutParams, jwtPair);
String expectedUrl = urlWithoutParams + "/?accessToken=" + jwtPair.getToken() + "&refreshToken=" + jwtPair.getRefreshToken();
assertEquals(expectedUrl, redirectUrl);
redirectUrl = oauth2AuthenticationSuccessHandler.getRedirectUrl(urlWithParams, jwtPair);
expectedUrl = urlWithParams + "&accessToken=" + jwtPair.getToken() + "&refreshToken=" + jwtPair.getRefreshToken();
assertEquals(expectedUrl, redirectUrl);
}
}

1
common/cluster-api/src/main/proto/queue.proto

@ -600,6 +600,7 @@ message TbAttributeDeleteProto {
int64 tenantIdLSB = 5;
string scope = 6;
repeated string keys = 7;
bool notifyDevice = 8;
}
message TbTimeSeriesDeleteProto {

1
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -28,6 +28,7 @@ public class DataConstants {
public static final String CLIENT_SCOPE = "CLIENT_SCOPE";
public static final String SERVER_SCOPE = "SERVER_SCOPE";
public static final String SHARED_SCOPE = "SHARED_SCOPE";
public static final String NOTIFY_DEVICE_METADATA_KEY = "notifyDevice";
public static final String LATEST_TS = "LATEST_TS";
public static final String IS_NEW_ALARM = "isNewAlarm";
public static final String IS_EXISTING_ALARM = "isExistingAlarm";

2
common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java

@ -16,5 +16,5 @@
package org.thingsboard.server.common.data.script;
public enum ScriptLanguage {
JS, MVEL
JS, TBEL
}

2
common/script/script-api/pom.xml

@ -90,7 +90,7 @@
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>mvel2</artifactId>
<artifactId>tbel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

167
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbUtils.java

@ -1,167 +0,0 @@
/**
* Copyright © 2016-2022 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.script.api.mvel;
import org.mvel2.ExecutionContext;
import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.util.MethodStub;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.List;
public class TbUtils {
public static void register(ParserConfiguration parserConfig) throws Exception {
parserConfig.addImport("btoa", new MethodStub(TbUtils.class.getMethod("btoa",
String.class)));
parserConfig.addImport("atob", new MethodStub(TbUtils.class.getMethod("atob",
String.class)));
parserConfig.addImport("bytesToString", new MethodStub(TbUtils.class.getMethod("bytesToString",
List.class)));
parserConfig.addImport("bytesToString", new MethodStub(TbUtils.class.getMethod("bytesToString",
List.class, String.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class, String.class)));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",
String.class)));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",
String.class, int.class)));
parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat",
String.class)));
parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble",
String.class)));
}
public static void main(String[] args) {
System.out.println(Integer.class == int.class);
}
public static String btoa(String input) {
return new String(Base64.getEncoder().encode(input.getBytes()));
}
public static String atob(String encoded) {
return new String(Base64.getDecoder().decode(encoded));
}
public static String bytesToString(List<Byte> bytesList) {
byte[] bytes = bytesFromList(bytesList);
return new String(bytes);
}
public static String bytesToString(List<Byte> bytesList, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = bytesFromList(bytesList);
return new String(bytes, charsetName);
}
public static List<Byte> stringToBytes(ExecutionContext ctx, String str) {
byte[] bytes = str.getBytes();
return bytesToList(ctx, bytes);
}
public static List<Byte> stringToBytes(ExecutionContext ctx, String str, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = str.getBytes(charsetName);
return bytesToList(ctx, bytes);
}
private static byte[] bytesFromList(List<Byte> bytesList) {
byte[] bytes = new byte[bytesList.size()];
for (int i = 0; i < bytesList.size(); i++) {
bytes[i] = bytesList.get(i);
}
return bytes;
}
private static List<Byte> bytesToList(ExecutionContext ctx, byte[] bytes) {
List<Byte> list = new ExecutionArrayList<>(ctx);
for (int i = 0; i < bytes.length; i++) {
list.add(bytes[i]);
}
return list;
}
public static Integer parseInt(String value) {
if (value != null) {
try {
int radix = 10;
if (isHexadecimal(value)) {
radix = 16;
}
return Integer.parseInt(prepareNumberString(value), radix);
} catch (NumberFormatException e) {
Float f = parseFloat(value);
if (f != null) {
return f.intValue();
}
}
}
return null;
}
public static Integer parseInt(String value, int radix) {
if (value != null) {
try {
return Integer.parseInt(prepareNumberString(value), radix);
} catch (NumberFormatException e) {
Float f = parseFloat(value);
if (f != null) {
return f.intValue();
}
}
}
return null;
}
public static Float parseFloat(String value) {
if (value != null) {
try {
return Float.parseFloat(prepareNumberString(value));
} catch (NumberFormatException e) {
}
}
return null;
}
public static Double parseDouble(String value) {
if (value != null) {
try {
return Double.parseDouble(prepareNumberString(value));
} catch (NumberFormatException e) {
}
}
return null;
}
private static boolean isHexadecimal(String value) {
return value != null && (value.contains("0x") || value.contains("0X"));
}
private static String prepareNumberString(String value) {
if (value != null) {
value = value.trim();
if (isHexadecimal(value)) {
value = value.replace("0x", "");
value = value.replace("0X", "");
}
value = value.replace(",", ".");
}
return value;
}
}

71
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/DefaultMvelInvokeService.java → common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.mvel;
package org.thingsboard.script.api.tbel;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
@ -27,8 +27,8 @@ import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.mvel2.ExecutionContext;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.SandboxedParserConfiguration;
import org.mvel2.SandboxedParserContext;
import org.mvel2.ScriptMemoryOverflowException;
import org.mvel2.optimizers.OptimizerFactory;
import org.springframework.beans.factory.annotation.Value;
@ -47,73 +47,72 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.Calendar;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
@Slf4j
@ConditionalOnProperty(prefix = "mvel", value = "enabled", havingValue = "true", matchIfMissing = true)
@ConditionalOnProperty(prefix = "tbel", value = "enabled", havingValue = "true", matchIfMissing = true)
@Service
public class DefaultMvelInvokeService extends AbstractScriptInvokeService implements MvelInvokeService {
public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService {
protected final Map<UUID, String> scriptIdToHash = new ConcurrentHashMap<>();
protected final Map<String, MvelScript> scriptMap = new ConcurrentHashMap<>();
protected final Map<String, TbelScript> scriptMap = new ConcurrentHashMap<>();
protected Cache<String, Serializable> compiledScriptsCache;
private SandboxedParserConfiguration parserConfig;
private static final Pattern NEW_KEYWORD_PATTERN = Pattern.compile("new\\s");
@Getter
@Value("${mvel.max_total_args_size:100000}")
@Value("${tbel.max_total_args_size:100000}")
private long maxTotalArgsSize;
@Getter
@Value("${mvel.max_result_size:300000}")
@Value("${tbel.max_result_size:300000}")
private long maxResultSize;
@Getter
@Value("${mvel.max_script_body_size:50000}")
@Value("${tbel.max_script_body_size:50000}")
private long maxScriptBodySize;
@Getter
@Value("${mvel.max_errors:3}")
@Value("${tbel.max_errors:3}")
private int maxErrors;
@Getter
@Value("${mvel.max_black_list_duration_sec:60}")
@Value("${tbel.max_black_list_duration_sec:60}")
private int maxBlackListDurationSec;
@Getter
@Value("${mvel.max_requests_timeout:0}")
@Value("${tbel.max_requests_timeout:0}")
private long maxInvokeRequestsTimeout;
@Getter
@Value("${mvel.stats.enabled:false}")
@Value("${tbel.stats.enabled:false}")
private boolean statsEnabled;
@Value("${mvel.thread_pool_size:50}")
@Value("${tbel.thread_pool_size:50}")
private int threadPoolSize;
@Value("${mvel.max_memory_limit_mb:8}")
@Value("${tbel.max_memory_limit_mb:8}")
private long maxMemoryLimitMb;
@Value("${mvel.compiled_scripts_cache_size:1000}")
@Value("${tbel.compiled_scripts_cache_size:1000}")
private int compiledScriptsCacheSize;
private ListeningExecutorService executor;
private final Lock lock = new ReentrantLock();
protected DefaultMvelInvokeService(Optional<TbApiUsageStateClient> apiUsageStateClient, Optional<TbApiUsageReportClient> apiUsageReportClient) {
protected DefaultTbelInvokeService(Optional<TbApiUsageStateClient> apiUsageStateClient, Optional<TbApiUsageReportClient> apiUsageReportClient) {
super(apiUsageStateClient, apiUsageReportClient);
}
@Scheduled(fixedDelayString = "${mvel.stats.print_interval_ms:10000}")
@Scheduled(fixedDelayString = "${tbel.stats.print_interval_ms:10000}")
public void printStats() {
super.printStats();
}
@ -123,13 +122,15 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem
public void init() {
super.init();
OptimizerFactory.setDefaultOptimizer(OptimizerFactory.SAFE_REFLECTIVE);
parserConfig = new SandboxedParserConfiguration();
parserConfig = ParserContext.enableSandboxedMode();
parserConfig.addImport("JSON", TbJson.class);
parserConfig.registerDataType("Date", TbDate.class, date -> 8L);
parserConfig.registerDataType("Random", Random.class, date -> 8L);
parserConfig.registerDataType("Calendar", Calendar.class, date -> 8L);
TbUtils.register(parserConfig);
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, "mvel-executor"));
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, "tbel-executor"));
try {
// Special command to warm up MVEL engine
// Special command to warm up TBEL engine
Serializable script = compileScript("var warmUp = {}; warmUp");
MVEL.executeTbExpression(script, new ExecutionContext(parserConfig), Collections.emptyMap());
} catch (Exception e) {
@ -149,7 +150,7 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem
@Override
protected String getStatsName() {
return "MVEL Scripts Stats";
return "TBEL Scripts Stats";
}
@Override
@ -167,15 +168,11 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem
return executor.submit(() -> {
try {
String scriptHash = hash(scriptBody, argNames);
compiledScriptsCache.get(scriptHash, k -> {
return compileScript(scriptBody);
});
compiledScriptsCache.get(scriptHash, k -> compileScript(scriptBody));
lock.lock();
try {
scriptIdToHash.put(scriptId, scriptHash);
scriptMap.computeIfAbsent(scriptHash, k -> {
return new MvelScript(scriptBody, argNames);
});
scriptMap.computeIfAbsent(scriptHash, k -> new TbelScript(scriptBody, argNames));
} finally {
lock.unlock();
}
@ -187,17 +184,15 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem
}
@Override
protected MvelScriptExecutionTask doInvokeFunction(UUID scriptId, Object[] args) {
protected TbelScriptExecutionTask doInvokeFunction(UUID scriptId, Object[] args) {
ExecutionContext executionContext = new ExecutionContext(this.parserConfig, maxMemoryLimitMb * 1024 * 1024);
return new MvelScriptExecutionTask(executionContext, executor.submit(() -> {
return new TbelScriptExecutionTask(executionContext, executor.submit(() -> {
String scriptHash = scriptIdToHash.get(scriptId);
if (scriptHash == null) {
throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, null, new RuntimeException("Script not found!"));
}
MvelScript script = scriptMap.get(scriptHash);
Serializable compiledScript = compiledScriptsCache.get(scriptHash, k -> {
return compileScript(script.getScriptBody());
});
TbelScript script = scriptMap.get(scriptHash);
Serializable compiledScript = compiledScriptsCache.get(scriptHash, k -> compileScript(script.getScriptBody()));
try {
return MVEL.executeTbExpression(compiledScript, executionContext, script.createVars(args));
} catch (ScriptMemoryOverflowException e) {
@ -209,7 +204,7 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem
}
@Override
protected void doRelease(UUID scriptId) throws Exception {
protected void doRelease(UUID scriptId) {
String scriptHash = scriptIdToHash.remove(scriptId);
if (scriptHash != null) {
lock.lock();
@ -225,7 +220,7 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem
}
private Serializable compileScript(String scriptBody) {
return MVEL.compileExpression(scriptBody, new SandboxedParserContext(parserConfig));
return MVEL.compileExpression(scriptBody, new ParserContext());
}
@SuppressWarnings("UnstableApiUsage")

42
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbDate.java → common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java

@ -13,10 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.mvel;
package org.thingsboard.script.api.tbel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Locale;
@ -24,6 +32,11 @@ import java.util.TimeZone;
public class TbDate extends Date {
private static final DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern(
"yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault());
private static final DateFormat isoDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
public TbDate() {
super();
}
@ -60,8 +73,7 @@ public class TbDate extends Date {
}
public String toISOString() {
DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sssZ");
return formatter.format(this);
return isoDateFormat.format(this);
}
public String toLocaleString(String locale) {
@ -79,14 +91,32 @@ public class TbDate extends Date {
return System.currentTimeMillis();
}
public static long parse(String value) {
public static long parse(String value, String format) {
try {
return Date.parse(value);
} catch (IllegalArgumentException e) {
DateFormat dateFormat = new SimpleDateFormat(format);
return dateFormat.parse(value).getTime();
} catch (Exception e) {
return -1;
}
}
public static long parse(String value) {
try {
TemporalAccessor accessor = isoDateFormatter.parseBest(value,
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from);
Instant instant = Instant.from(accessor);
return Instant.EPOCH.until(instant, ChronoUnit.MILLIS);
} catch (Exception e) {
try {
return Date.parse(value);
} catch (IllegalArgumentException e2) {
return -1;
}
}
}
public static long UTC(int year, int month, int date,
int hrs, int min, int sec) {
return Date.UTC(year - 1900, month, date, hrs, min, sec);

2
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbJson.java → common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbJson.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.mvel;
package org.thingsboard.script.api.tbel;
import com.fasterxml.jackson.databind.JsonNode;
import org.mvel2.ExecutionContext;

319
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -0,0 +1,319 @@
/**
* Copyright © 2016-2022 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.script.api.tbel;
import org.mvel2.ExecutionContext;
import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.util.MethodStub;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
public class TbUtils {
private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII);
public static void register(ParserConfiguration parserConfig) throws Exception {
parserConfig.addImport("btoa", new MethodStub(TbUtils.class.getMethod("btoa",
String.class)));
parserConfig.addImport("atob", new MethodStub(TbUtils.class.getMethod("atob",
String.class)));
parserConfig.addImport("bytesToString", new MethodStub(TbUtils.class.getMethod("bytesToString",
List.class)));
parserConfig.addImport("bytesToString", new MethodStub(TbUtils.class.getMethod("bytesToString",
List.class, String.class)));
parserConfig.addImport("decodeToString", new MethodStub(TbUtils.class.getMethod("bytesToString",
List.class)));
parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson",
ExecutionContext.class, List.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class)));
parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes",
ExecutionContext.class, String.class, String.class)));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",
String.class)));
parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt",
String.class, int.class)));
parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat",
String.class)));
parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble",
String.class)));
parserConfig.addImport("parseLittleEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToInt",
String.class)));
parserConfig.addImport("parseBigEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToInt",
String.class)));
parserConfig.addImport("parseHexToInt", new MethodStub(TbUtils.class.getMethod("parseHexToInt",
String.class)));
parserConfig.addImport("parseHexToInt", new MethodStub(TbUtils.class.getMethod("parseHexToInt",
String.class, boolean.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed",
double.class, int.class)));
parserConfig.addImport("hexToBytes", new MethodStub(TbUtils.class.getMethod("hexToBytes",
ExecutionContext.class, String.class)));
parserConfig.addImport("base64ToHex", new MethodStub(TbUtils.class.getMethod("base64ToHex",
String.class)));
parserConfig.addImport("base64ToBytes", new MethodStub(TbUtils.class.getMethod("base64ToBytes",
String.class)));
parserConfig.addImport("bytesToBase64", new MethodStub(TbUtils.class.getMethod("bytesToBase64",
byte[].class)));
parserConfig.addImport("bytesToHex", new MethodStub(TbUtils.class.getMethod("bytesToHex",
byte[].class)));
parserConfig.addImport("bytesToHex", new MethodStub(TbUtils.class.getMethod("bytesToHex",
ExecutionArrayList.class)));
}
public static String btoa(String input) {
return new String(Base64.getEncoder().encode(input.getBytes()));
}
public static String atob(String encoded) {
return new String(Base64.getDecoder().decode(encoded));
}
public static Object decodeToJson(ExecutionContext ctx, List<Byte> bytesList) throws IOException {
return TbJson.parse(ctx, bytesToString(bytesList));
}
public static String bytesToString(List<Byte> bytesList) {
byte[] bytes = bytesFromList(bytesList);
return new String(bytes);
}
public static String bytesToString(List<Byte> bytesList, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = bytesFromList(bytesList);
return new String(bytes, charsetName);
}
public static List<Byte> stringToBytes(ExecutionContext ctx, String str) {
byte[] bytes = str.getBytes();
return bytesToList(ctx, bytes);
}
public static List<Byte> stringToBytes(ExecutionContext ctx, String str, String charsetName) throws UnsupportedEncodingException {
byte[] bytes = str.getBytes(charsetName);
return bytesToList(ctx, bytes);
}
private static byte[] bytesFromList(List<Byte> bytesList) {
byte[] bytes = new byte[bytesList.size()];
for (int i = 0; i < bytesList.size(); i++) {
bytes[i] = bytesList.get(i);
}
return bytes;
}
private static List<Byte> bytesToList(ExecutionContext ctx, byte[] bytes) {
List<Byte> list = new ExecutionArrayList<>(ctx);
for (byte aByte : bytes) {
list.add(aByte);
}
return list;
}
public static Integer parseInt(String value) {
if (value != null) {
try {
int radix = 10;
if (isHexadecimal(value)) {
radix = 16;
}
return Integer.parseInt(prepareNumberString(value), radix);
} catch (NumberFormatException e) {
Float f = parseFloat(value);
if (f != null) {
return f.intValue();
}
}
}
return null;
}
public static Integer parseInt(String value, int radix) {
if (value != null) {
try {
return Integer.parseInt(prepareNumberString(value), radix);
} catch (NumberFormatException e) {
Float f = parseFloat(value);
if (f != null) {
return f.intValue();
}
}
}
return null;
}
public static Float parseFloat(String value) {
if (value != null) {
try {
return Float.parseFloat(prepareNumberString(value));
} catch (NumberFormatException e) {
}
}
return null;
}
public static Double parseDouble(String value) {
if (value != null) {
try {
return Double.parseDouble(prepareNumberString(value));
} catch (NumberFormatException e) {
}
}
return null;
}
public static int parseLittleEndianHexToInt(String hex) {
return parseHexToInt(hex, false);
}
public static int parseBigEndianHexToInt(String hex) {
return parseHexToInt(hex, true);
}
public static int parseHexToInt(String hex) {
return parseHexToInt(hex, true);
}
public static int parseHexToInt(String hex, boolean bigEndian) {
int length = hex.length();
if (length > 8) {
throw new IllegalArgumentException("Hex string is too large. Maximum 8 symbols allowed.");
}
if (length % 2 > 0) {
throw new IllegalArgumentException("Hex string must be even-length.");
}
byte[] data = new byte[length / 2];
for (int i = 0; i < length; i += 2) {
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16));
}
return parseBytesToInt(data, 0, data.length, bigEndian);
}
public static ExecutionArrayList<Byte> hexToBytes(ExecutionContext ctx, String hex) {
int len = hex.length();
if (len % 2 > 0) {
throw new IllegalArgumentException("Hex string must be even-length.");
}
ExecutionArrayList<Byte> data = new ExecutionArrayList<>(ctx);
for (int i = 0; i < len; i += 2) {
data.add((byte)((Character.digit(hex.charAt(i), 16) << 4)
+ Character.digit(hex.charAt(i + 1), 16)));
}
return data;
}
public static String base64ToHex(String base64) {
return bytesToHex(Base64.getDecoder().decode(base64));
}
public static String bytesToBase64(byte[] bytes) {
return Base64.getEncoder().encodeToString(bytes);
}
public static byte[] base64ToBytes(String input) {
return Base64.getDecoder().decode(input);
}
public static int parseBytesToInt(List<Byte> data, int offset, int length) {
return parseBytesToInt(data, offset, length, true);
}
public static int parseBytesToInt(List<Byte> data, int offset, int length, boolean bigEndian) {
final byte[] bytes = new byte[data.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = data.get(i);
}
return parseBytesToInt(bytes, offset, length, bigEndian);
}
public static int parseBytesToInt(byte[] data, int offset, int length) {
return parseBytesToInt(data, offset, length, true);
}
public static int parseBytesToInt(byte[] data, int offset, int length, boolean bigEndian) {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if (length > 4) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
var bb = ByteBuffer.allocate(4);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
bb.position(bigEndian ? 4 - length : 0);
bb.put(data, offset, length);
bb.position(0);
return bb.getInt();
}
public static String bytesToHex(ExecutionArrayList<?> bytesList) {
byte[] bytes = new byte[bytesList.size()];
for (int i = 0; i < bytesList.size(); i++) {
bytes[i] = Byte.parseByte(bytesList.get(i).toString());
}
return bytesToHex(bytes);
}
public static String bytesToHex(byte[] bytes) {
byte[] hexChars = new byte[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars, StandardCharsets.UTF_8);
}
public static double toFixed(double value, int precision) {
return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).doubleValue();
}
private static boolean isHexadecimal(String value) {
return value != null && (value.contains("0x") || value.contains("0X"));
}
private static String prepareNumberString(String value) {
if (value != null) {
value = value.trim();
if (isHexadecimal(value)) {
value = value.replace("0x", "");
value = value.replace("0X", "");
}
value = value.replace(",", ".");
}
return value;
}
}

6
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelInvokeService.java → common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelInvokeService.java

@ -13,16 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.mvel;
package org.thingsboard.script.api.tbel;
import org.thingsboard.script.api.ScriptInvokeService;
import org.thingsboard.server.common.data.script.ScriptLanguage;
public interface MvelInvokeService extends ScriptInvokeService {
public interface TbelInvokeService extends ScriptInvokeService {
@Override
default ScriptLanguage getLanguage() {
return ScriptLanguage.MVEL;
return ScriptLanguage.TBEL;
}
}

5
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScript.java → common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelScript.java

@ -13,16 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.mvel;
package org.thingsboard.script.api.tbel;
import lombok.Data;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
@Data
public class MvelScript {
public class TbelScript {
private final String scriptBody;
private final String[] argNames;

8
common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScriptExecutionTask.java → common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelScriptExecutionTask.java

@ -13,20 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.script.api.mvel;
package org.thingsboard.script.api.tbel;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data;
import lombok.Getter;
import org.mvel2.ExecutionContext;
import org.thingsboard.script.api.TbScriptExecutionTask;
public class MvelScriptExecutionTask extends TbScriptExecutionTask {
public class TbelScriptExecutionTask extends TbScriptExecutionTask {
private final ExecutionContext context;
public MvelScriptExecutionTask(ExecutionContext context, ListenableFuture<Object> resultFuture) {
public TbelScriptExecutionTask(ExecutionContext context, ListenableFuture<Object> resultFuture) {
super(resultFuture);
this.context = context;
}

98
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -0,0 +1,98 @@
/**
* Copyright © 2016-2022 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.script.api.tbel;
import org.junit.Assert;
import org.junit.Test;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
public class TbUtilsTest {
@Test
public void parseHexToInt() {
Assert.assertEquals(0xAB, TbUtils.parseHexToInt("AB"));
Assert.assertEquals(0xABBA, TbUtils.parseHexToInt("ABBA", true));
Assert.assertEquals(0xBAAB, TbUtils.parseHexToInt("ABBA", false));
Assert.assertEquals(0xAABBCC, TbUtils.parseHexToInt("AABBCC", true));
Assert.assertEquals(0xAABBCC, TbUtils.parseHexToInt("CCBBAA", false));
Assert.assertEquals(0xAABBCCDD, TbUtils.parseHexToInt("AABBCCDD", true));
Assert.assertEquals(0xAABBCCDD, TbUtils.parseHexToInt("DDCCBBAA", false));
Assert.assertEquals(0xDDCCBBAA, TbUtils.parseHexToInt("DDCCBBAA", true));
Assert.assertEquals(0xDDCCBBAA, TbUtils.parseHexToInt("AABBCCDD", false));
}
@Test
public void parseBytesToInt_checkPrimitives() {
int expected = 257;
byte[] data = ByteBuffer.allocate(4).putInt(expected).array();
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4));
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 2, 2, true));
Assert.assertEquals(1, TbUtils.parseBytesToInt(data, 3, 1, true));
expected = Integer.MAX_VALUE;
data = ByteBuffer.allocate(4).putInt(expected).array();
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true));
expected = 0xAABBCCDD;
data = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD};
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true));
data = new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA};
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, false));
expected = 0xAABBCC;
data = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC};
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, true));
data = new byte[]{(byte) 0xCC, (byte) 0xBB, (byte) 0xAA};
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, false));
}
@Test
public void parseBytesToInt_checkLists() {
int expected = 257;
List<Byte> data = toList(ByteBuffer.allocate(4).putInt(expected).array());
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4));
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 2, 2, true));
Assert.assertEquals(1, TbUtils.parseBytesToInt(data, 3, 1, true));
expected = Integer.MAX_VALUE;
data = toList(ByteBuffer.allocate(4).putInt(expected).array());
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true));
expected = 0xAABBCCDD;
data = toList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD});
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true));
data = toList(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA});
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, false));
expected = 0xAABBCC;
data = toList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC});
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, true));
data = toList(new byte[]{(byte) 0xCC, (byte) 0xBB, (byte) 0xAA});
Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, false));
}
private static List<Byte> toList(byte[] data) {
List<Byte> result = new ArrayList<>(data.length);
for (Byte b : data) {
result.add(b);
}
return result;
}
}

3
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -32,6 +32,7 @@ import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
@ -585,7 +586,7 @@ public class DefaultTransportService implements TransportService {
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("deviceName", sessionInfo.getDeviceName());
metaData.putValue("deviceType", sessionInfo.getDeviceType());
metaData.putValue("notifyDevice", "false");
metaData.putValue(DataConstants.NOTIFY_DEVICE_METADATA_KEY, "false");
CustomerId customerId = getCustomerId(sessionInfo);
sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST,
new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, customerId, msg.getKvList().size(), callback)));

2
dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java

@ -95,7 +95,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService<AssetPr
log.trace("Executing findAssetProfileByName [{}][{}]", tenantId, profileName);
Validator.validateString(profileName, INCORRECT_ASSET_PROFILE_NAME + profileName);
return cache.getAndPutInTransaction(AssetProfileCacheKey.fromName(tenantId, profileName),
() -> assetProfileDao.findByName(tenantId, profileName), true);
() -> assetProfileDao.findByName(tenantId, profileName), false);
}
@Override

27
dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java

@ -16,16 +16,16 @@
package org.thingsboard.server.dao.sqlts.insert.sql;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.dao.timeseries.SqlPartition;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -36,9 +36,6 @@ import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class SqlPartitioningRepository {
@PersistenceContext
private EntityManager entityManager;
@Autowired
private JdbcTemplate jdbcTemplate;
@ -50,12 +47,12 @@ public class SqlPartitioningRepository {
private final Map<String, Map<Long, SqlPartition>> tablesPartitions = new ConcurrentHashMap<>();
private final ReentrantLock partitionCreationLock = new ReentrantLock();
@Transactional
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void save(SqlPartition partition) {
entityManager.createNativeQuery(partition.getQuery()).executeUpdate();
jdbcTemplate.execute(partition.getQuery());
}
@Transactional
@Transactional(propagation = Propagation.NOT_SUPPORTED) // executing non-transactionally, so that parent transaction is not aborted on partition save error
public void createPartitionIfNotExists(String table, long entityTs, long partitionDurationMs) {
long partitionStartTs = calculatePartitionStartTime(entityTs, partitionDurationMs);
Map<Long, SqlPartition> partitions = tablesPartitions.computeIfAbsent(table, t -> new ConcurrentHashMap<>());
@ -64,19 +61,17 @@ public class SqlPartitioningRepository {
partitionCreationLock.lock();
try {
if (partitions.containsKey(partitionStartTs)) return;
log.trace("Saving partition: {}", partition);
log.info("Saving partition {}-{} for table {}", partition.getStart(), partition.getEnd(), table);
save(partition);
log.trace("Adding partition to map: {}", partition);
partitions.put(partition.getStart(), partition);
} catch (RuntimeException e) {
log.trace("Error occurred during partition save:", e);
String msg = ExceptionUtils.getRootCauseMessage(e);
if (msg.contains("would overlap partition")) {
log.warn("Couldn't save {} partition for {}, data will be saved to the default partition. SQL error: {}",
partition.getPartitionDate(), table, msg);
} catch (Exception e) {
String error = ExceptionUtils.getRootCauseMessage(e);
if (StringUtils.containsAny(error, "would overlap partition", "already exists")) {
partitions.put(partition.getStart(), partition);
log.debug("Couldn't save partition {}-{} for table {}: {}", partition.getStart(), partition.getEnd(), table, error);
} else {
throw e;
log.warn("Couldn't save partition {}-{} for table {}: {}", partition.getStart(), partition.getEnd(), table, error);
}
} finally {
partitionCreationLock.unlock();

32
docker/compose-utils.sh

@ -195,3 +195,35 @@ function checkFolders() {
done < <(echo "$PERMISSION_LIST")
return $EXIT_CODE
}
function composeVersion() {
#Checking whether "set -e" shell option should be restored after Compose version check
FLAG_SET=false
if [[ $SHELLOPTS =~ errexit ]]; then
set +e
FLAG_SET=true
fi
#Checking Compose V1 availablity
docker-compose version >/dev/null 2>&1
if [ $? -eq 0 ]; then status_v1=true; else status_v1=false; fi
#Checking Compose V2 availablity
docker compose version >/dev/null 2>&1
if [ $? -eq 0 ]; then status_v2=true; else status_v2=false; fi
COMPOSE_VERSION=""
if $status_v2 ; then
COMPOSE_VERSION="V2"
elif $status_v1 ; then
COMPOSE_VERSION="V1"
else
echo "Docker Compose plugin is not detected. Please check your environment." >&2
exit 1
fi
echo $COMPOSE_VERSION
if $FLAG_SET ; then set -e; fi
}

2
docker/docker-compose.aws-sqs.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
tb-js-executor:

6
docker/docker-compose.cassandra.volumes.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
cassandra:
@ -23,5 +23,5 @@ services:
volumes:
cassandra-volume:
external: true
name: ${CASSANDRA_DATA_VOLUME}
external:
name: ${CASSANDRA_DATA_VOLUME}

2
docker/docker-compose.confluent.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
tb-js-executor:

2
docker/docker-compose.hybrid.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
postgres:

2
docker/docker-compose.kafka.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
kafka:

6
docker/docker-compose.postgres.volumes.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
postgres:
@ -23,5 +23,5 @@ services:
volumes:
postgres-db-volume:
external: true
name: ${POSTGRES_DATA_VOLUME}
external:
name: ${POSTGRES_DATA_VOLUME}

2
docker/docker-compose.postgres.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
postgres:

2
docker/docker-compose.prometheus-grafana.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
volumes:
prometheus_data: {}

2
docker/docker-compose.pubsub.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
tb-js-executor:

2
docker/docker-compose.rabbitmq.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
tb-js-executor:

26
docker/docker-compose.redis-cluster.volumes.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
# Redis cluster
@ -39,20 +39,20 @@ services:
volumes:
redis-cluster-data-0:
external: true
name: ${REDIS_CLUSTER_DATA_VOLUME_0}
external:
name: ${REDIS_CLUSTER_DATA_VOLUME_0}
redis-cluster-data-1:
external: true
name: ${REDIS_CLUSTER_DATA_VOLUME_1}
external:
name: ${REDIS_CLUSTER_DATA_VOLUME_1}
redis-cluster-data-2:
external: true
name: ${REDIS_CLUSTER_DATA_VOLUME_2}
external:
name: ${REDIS_CLUSTER_DATA_VOLUME_2}
redis-cluster-data-3:
external: true
name: ${REDIS_CLUSTER_DATA_VOLUME_3}
external:
name: ${REDIS_CLUSTER_DATA_VOLUME_3}
redis-cluster-data-4:
external: true
name: ${REDIS_CLUSTER_DATA_VOLUME_4}
external:
name: ${REDIS_CLUSTER_DATA_VOLUME_4}
redis-cluster-data-5:
external: true
name: ${REDIS_CLUSTER_DATA_VOLUME_5}
external:
name: ${REDIS_CLUSTER_DATA_VOLUME_5}

2
docker/docker-compose.redis-cluster.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
# Redis cluster

6
docker/docker-compose.redis.volumes.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
redis:
@ -23,5 +23,5 @@ services:
volumes:
redis-data:
external: true
name: ${REDIS_DATA_VOLUME}
external:
name: ${REDIS_DATA_VOLUME}

2
docker/docker-compose.redis.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
# Redis standalone

2
docker/docker-compose.service-bus.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
tb-js-executor:

30
docker/docker-compose.volumes.yml

@ -14,7 +14,7 @@
# limitations under the License.
#
version: '2.2'
version: '3.0'
services:
tb-core1:
@ -59,23 +59,23 @@ services:
volumes:
tb-log-volume:
external: true
name: ${TB_LOG_VOLUME}
external:
name: ${TB_LOG_VOLUME}
tb-coap-transport-log-volume:
external: true
name: ${TB_COAP_TRANSPORT_LOG_VOLUME}
external:
name: ${TB_COAP_TRANSPORT_LOG_VOLUME}
tb-lwm2m-transport-log-volume:
external: true
name: ${TB_LWM2M_TRANSPORT_LOG_VOLUME}
external:
name: ${TB_LWM2M_TRANSPORT_LOG_VOLUME}
tb-http-transport-log-volume:
external: true
name: ${TB_HTTP_TRANSPORT_LOG_VOLUME}
external:
name: ${TB_HTTP_TRANSPORT_LOG_VOLUME}
tb-mqtt-transport-log-volume:
external: true
name: ${TB_MQTT_TRANSPORT_LOG_VOLUME}
external:
name: ${TB_MQTT_TRANSPORT_LOG_VOLUME}
tb-snmp-transport-log-volume:
external: true
name: ${TB_SNMP_TRANSPORT_LOG_VOLUME}
external:
name: ${TB_SNMP_TRANSPORT_LOG_VOLUME}
tb-vc-executor-log-volume:
external: true
name: ${TB_VC_EXECUTOR_LOG_VOLUME}
external:
name: ${TB_VC_EXECUTOR_LOG_VOLUME}

5
docker/docker-compose.yml

@ -15,7 +15,7 @@
#
version: '2.2'
version: '3.0'
services:
zookeeper:
@ -30,7 +30,8 @@ services:
tb-js-executor:
restart: always
image: "${DOCKER_REPO}/${JS_EXECUTOR_DOCKER_NAME}:${TB_VERSION}"
scale: 10
deploy:
replicas: 10
env_file:
- tb-js-executor.env
tb-core1:

41
docker/docker-install-tb.sh

@ -41,6 +41,8 @@ set -e
source compose-utils.sh
COMPOSE_VERSION=$(composeVersion) || exit $?
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
@ -52,14 +54,39 @@ ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
checkFolders --create || exit $?
if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
up -d $ADDITIONAL_STARTUP_SERVICES
COMPOSE_ARGS="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
up -d ${ADDITIONAL_STARTUP_SERVICES}"
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS
;;
V1)
docker-compose $COMPOSE_ARGS
;;
*)
# unknown option
;;
esac
fi
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} \
tb-core1
COMPOSE_ARGS="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} \
tb-core1"
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS
;;
V1)
docker-compose $COMPOSE_ARGS
;;
*)
# unknown option
;;
esac

20
docker/docker-remove-services.sh

@ -19,6 +19,8 @@ set -e
source compose-utils.sh
COMPOSE_VERSION=$(composeVersion) || exit $?
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
@ -27,6 +29,18 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $?
ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \
down -v
COMPOSE_ARGS="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} \
down -v"
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS
;;
V1)
docker-compose $COMPOSE_ARGS
;;
*)
# unknown option
;;
esac

20
docker/docker-start-services.sh

@ -19,6 +19,8 @@ set -e
source compose-utils.sh
COMPOSE_VERSION=$(composeVersion) || exit $?
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
@ -29,6 +31,18 @@ ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $?
checkFolders --create || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \
up -d
COMPOSE_ARGS="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} \
up -d"
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS
;;
V1)
docker-compose --compatibility $COMPOSE_ARGS
;;
*)
# unknown option
;;
esac

20
docker/docker-stop-services.sh

@ -19,6 +19,8 @@ set -e
source compose-utils.sh
COMPOSE_VERSION=$(composeVersion) || exit $?
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
@ -27,6 +29,18 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $?
ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \
stop
COMPOSE_ARGS="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} \
stop"
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS
;;
V1)
docker-compose $COMPOSE_ARGS
;;
*)
# unknown option
;;
esac

29
docker/docker-update-service.sh

@ -19,15 +19,32 @@ set -e
source compose-utils.sh
COMPOSE_VERSION=$(composeVersion) || exit $?
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
pull $@
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
up -d --no-deps --build $@
COMPOSE_ARGS_PULL="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
pull"
COMPOSE_ARGS_BUILD="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
up -d --no-deps --build"
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS_PULL $@
docker compose $COMPOSE_ARGS_BUILD $@
;;
V1)
docker-compose $COMPOSE_ARGS_PULL $@
docker-compose $COMPOSE_ARGS_BUILD $@
;;
*)
# unknown option
;;
esac

40
docker/docker-upgrade-tb.sh

@ -40,6 +40,8 @@ set -e
source compose-utils.sh
COMPOSE_VERSION=$(composeVersion) || exit $?
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
@ -50,16 +52,32 @@ ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
checkFolders --create || exit $?
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
pull \
tb-core1
COMPOSE_ARGS_PULL="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
pull \
tb-core1"
COMPOSE_ARGS_UP="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
up -d ${ADDITIONAL_STARTUP_SERVICES}"
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
up -d $ADDITIONAL_STARTUP_SERVICES
COMPOSE_ARGS_RUN="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \
run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} \
tb-core1"
docker-compose \
-f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \
run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} \
tb-core1
case $COMPOSE_VERSION in
V2)
docker compose $COMPOSE_ARGS_PULL
docker compose $COMPOSE_ARGS_UP
docker compose $COMPOSE_ARGS_RUN
;;
V1)
docker-compose $COMPOSE_ARGS_PULL
docker-compose $COMPOSE_ARGS_UP
docker-compose $COMPOSE_ARGS_RUN
;;
*)
# unknown option
;;
esac

10
msa/black-box-tests/pom.xml

@ -93,6 +93,10 @@
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.californium</groupId>
<artifactId>californium-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
@ -161,6 +165,12 @@
<artifactId>snmp</artifactId>
<type>docker-info</type>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>

34
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java

@ -26,7 +26,14 @@ import org.apache.http.ssl.SSLContexts;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.Listeners;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import java.net.URI;
@ -37,6 +44,9 @@ import java.util.Random;
@Slf4j
@Listeners(TestListener.class)
public abstract class AbstractContainerTest {
protected final static String TEST_PROVISION_DEVICE_KEY = "test_provision_key";
protected final static String TEST_PROVISION_DEVICE_SECRET = "test_provision_secret";
protected static long timeoutMultiplier = 1;
protected ObjectMapper mapper = new ObjectMapper();
private static final ContainerTestSuite containerTestSuite = ContainerTestSuite.getInstance();
@ -150,4 +160,28 @@ public abstract class AbstractContainerTest {
}
}
protected DeviceProfile updateDeviceProfileWithProvisioningStrategy(DeviceProfile deviceProfile, DeviceProfileProvisionType provisionType) {
DeviceProfileProvisionConfiguration provisionConfiguration;
String testProvisionDeviceKey = TEST_PROVISION_DEVICE_KEY;
deviceProfile.setProvisionType(provisionType);
switch(provisionType) {
case ALLOW_CREATE_NEW_DEVICES:
provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(TEST_PROVISION_DEVICE_SECRET);
break;
case CHECK_PRE_PROVISIONED_DEVICES:
provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(TEST_PROVISION_DEVICE_SECRET);
break;
default:
case DISABLED:
testProvisionDeviceKey = null;
provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
break;
}
DeviceProfileData deviceProfileData = deviceProfile.getProfileData();
deviceProfileData.setProvisionConfiguration(provisionConfiguration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setProvisionDeviceKey(testProvisionDeviceKey);
return testRestClient.postDeviceProfile(deviceProfile);
}
}

121
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java

@ -0,0 +1,121 @@
/**
* Copyright © 2016-2022 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.msa;
import java.io.IOException;
import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.elements.exception.ConnectorException;
import org.thingsboard.server.common.msg.session.FeatureType;
public class TestCoapClient {
private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/";
private static final long CLIENT_REQUEST_TIMEOUT = 60000L;
private final CoapClient client;
public TestCoapClient(){
this.client = createClient();
}
public TestCoapClient(String accessToken, FeatureType featureType) {
this.client = createClient(getFeatureTokenUrl(accessToken, featureType));
}
public TestCoapClient(String featureTokenUrl) {
this.client = createClient(featureTokenUrl);
}
public void connectToCoap(String accessToken) {
setURI(accessToken, null);
}
public void connectToCoap(String accessToken, FeatureType featureType) {
setURI(accessToken, featureType);
}
public void disconnect() {
if (client != null) {
client.shutdown();
}
}
public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException {
return this.postMethod(requestBody.getBytes());
}
public CoapResponse postMethod(byte[] requestBodyBytes) throws ConnectorException, IOException {
return client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(requestBodyBytes, MediaTypeRegistry.APPLICATION_JSON);
}
public void postMethod(CoapHandler handler, String payload, int format) {
client.post(handler, payload, format);
}
public void postMethod(CoapHandler handler, byte[] payload, int format) {
client.post(handler, payload, format);
}
public CoapResponse getMethod() throws ConnectorException, IOException {
return client.setTimeout(CLIENT_REQUEST_TIMEOUT).get();
}
public CoapObserveRelation getObserveRelation(TestCoapClientCallback callback){
Request request = Request.newGet().setObserve();
request.setType(CoAP.Type.CON);
return client.observe(request, callback);
}
public void setURI(String featureTokenUrl) {
if (client == null) {
throw new RuntimeException("Failed to connect! CoapClient is not initialized!");
}
client.setURI(featureTokenUrl);
}
public void setURI(String accessToken, FeatureType featureType) {
if (featureType == null){
featureType = FeatureType.ATTRIBUTES;
}
setURI(getFeatureTokenUrl(accessToken, featureType));
}
private CoapClient createClient() {
return new CoapClient();
}
private CoapClient createClient(String featureTokenUrl) {
return new CoapClient(featureTokenUrl);
}
public static String getFeatureTokenUrl(FeatureType featureType) {
return COAP_BASE_URL + featureType.name().toLowerCase();
}
public static String getFeatureTokenUrl(String token, FeatureType featureType) {
return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase();
}
public static String getFeatureTokenUrl(String token, FeatureType featureType, int requestId) {
return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase() + "/" + requestId;
}
}

68
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java

@ -0,0 +1,68 @@
/**
* Copyright © 2016-2022 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.msa;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import java.util.concurrent.CountDownLatch;
@Slf4j
@Data
public class TestCoapClientCallback implements CoapHandler {
protected final CountDownLatch latch;
protected Integer observe;
protected byte[] payloadBytes;
protected CoAP.ResponseCode responseCode;
public TestCoapClientCallback() {
this.latch = new CountDownLatch(1);
}
public TestCoapClientCallback(int subscribeCount) {
this.latch = new CountDownLatch(subscribeCount);
}
public Integer getObserve() {
return observe;
}
public byte[] getPayloadBytes() {
return payloadBytes;
}
public CoAP.ResponseCode getResponseCode() {
return responseCode;
}
@Override
public void onLoad(CoapResponse response) {
observe = response.getOptions().getObserve();
payloadBytes = response.getPayload();
responseCode = response.getCode();
latch.countDown();
}
@Override
public void onError() {
log.warn("Command Response Ack Error, No connect");
}
}

37
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -28,8 +28,10 @@ import io.restassured.response.ValidatableResponse;
import io.restassured.specification.RequestSpecification;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.page.PageData;
@ -95,6 +97,15 @@ public class TestRestClient {
.as(Device.class);
}
public Device getDeviceByName(String deviceName) {
return given().spec(requestSpec).pathParam("deviceName", deviceName)
.get("/api/tenant/devices?deviceName={deviceName}")
.then()
.statusCode(HTTP_OK)
.extract()
.as(Device.class);
}
public ValidatableResponse getDeviceById(DeviceId deviceId, int statusCode) {
return given().spec(requestSpec)
.pathParams("deviceId", deviceId.getId())
@ -164,6 +175,14 @@ public class TestRestClient {
.as(JsonNode.class);
}
public JsonPath postProvisionRequest(String provisionRequest) {
return given().spec(requestSpec)
.body(provisionRequest)
.post("/api/v1/provision")
.getBody()
.jsonPath();
}
public PageData<RuleChain> getRuleChains(PageLink pageLink) {
Map<String, String> params = new HashMap<>();
addPageLinkToParam(params, pageLink);
@ -259,6 +278,24 @@ public class TestRestClient {
.as(JsonNode.class);
}
public DeviceProfile getDeviceProfileById(DeviceProfileId deviceProfileId) {
return given().spec(requestSpec).get("/api/deviceProfile/{deviceProfileId}", deviceProfileId.getId())
.then()
.assertThat()
.statusCode(HTTP_OK)
.extract()
.as(DeviceProfile.class);
}
public DeviceProfile postDeviceProfile(DeviceProfile deviceProfile) {
return given().spec(requestSpec).body(deviceProfile)
.post("/api/deviceProfile")
.then()
.statusCode(HTTP_OK)
.extract()
.as(DeviceProfile.class);
}
public Customer postCustomer(Customer customer) {
return given().spec(requestSpec)
.body(customer)

120
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java

@ -0,0 +1,120 @@
/**
* Copyright © 2016-2022 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.msa.connectivity;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonObject;
import io.restassured.path.json.JsonPath;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.TestCoapClient;
import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype;
public class CoapClientTest extends AbstractContainerTest {
private TestCoapClient client;
private Device device;
@BeforeMethod
public void setUp() throws Exception {
testRestClient.login("tenant@thingsboard.org", "tenant");
device = testRestClient.postDevice("", defaultDevicePrototype("http_"));
}
@AfterMethod
public void tearDown() {
testRestClient.deleteDeviceIfExists(device.getId());
}
@Test
public void provisionRequestForDeviceWithPreProvisionedStrategy() throws Exception {
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
JsonNode provisionResponse = JacksonUtil.fromBytes(createCoapClientAndPublish(device.getName()));
assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithAllowToCreateNewDevicesStrategy() throws Exception {
String testDeviceName = "test_provision_device";
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
JsonNode provisionResponse = JacksonUtil.fromBytes(createCoapClientAndPublish(testDeviceName));
testRestClient.deleteDeviceIfExists(device.getId());
device = testRestClient.getDeviceByName(testDeviceName);
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception {
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish(null));
assertThat(response.get("status").asText()).isEqualTo("NOT_FOUND");
}
private byte[] createCoapClientAndPublish(String deviceName) throws Exception {
String provisionRequestMsg = createTestProvisionMessage(deviceName);
client = new TestCoapClient(TestCoapClient.getFeatureTokenUrl(FeatureType.PROVISION));
return client.postMethod(provisionRequestMsg.getBytes()).getPayload();
}
private String createTestProvisionMessage(String deviceName) {
ObjectNode provisionRequest = JacksonUtil.newObjectNode();
provisionRequest.put("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.put("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
if (deviceName != null) {
provisionRequest.put("deviceName", deviceName);
}
return provisionRequest.toString();
}
}

85
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java

@ -16,10 +16,14 @@
package org.thingsboard.server.msa.connectivity;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.gson.JsonObject;
import io.restassured.path.json.JsonPath;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.WsClient;
@ -69,8 +73,8 @@ public class HttpClientTest extends AbstractContainerTest {
String accessToken = testRestClient.getDeviceCredentialsByDeviceId(device.getId()).getCredentialsId();
assertThat(accessToken).isNotNull();
JsonNode sharedAattribute = mapper.readTree(createPayload().toString());
testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, sharedAattribute);
JsonNode sharedAttribute = mapper.readTree(createPayload().toString());
testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute);
JsonNode clientAttribute = mapper.readTree(createPayload().toString());
testRestClient.postAttribute(accessToken, clientAttribute);
@ -78,11 +82,11 @@ public class HttpClientTest extends AbstractContainerTest {
TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
JsonNode attributes = testRestClient.getAttributes(accessToken, null, null);
assertThat(attributes.get("shared")).isEqualTo(sharedAattribute);
assertThat(attributes.get("shared")).isEqualTo(sharedAttribute);
assertThat(attributes.get("client")).isEqualTo(clientAttribute);
JsonNode attributes2 = testRestClient.getAttributes(accessToken, null, "stringKey");
assertThat(attributes2.get("shared").get("stringKey")).isEqualTo(sharedAattribute.get("stringKey"));
assertThat(attributes2.get("shared").get("stringKey")).isEqualTo(sharedAttribute.get("stringKey"));
assertThat(attributes2.has("client")).isFalse();
JsonNode attributes3 = testRestClient.getAttributes(accessToken, "longKey,stringKey", null);
@ -91,4 +95,77 @@ public class HttpClientTest extends AbstractContainerTest {
assertThat(attributes3.get("client").get("longKey")).isEqualTo(clientAttribute.get("longKey"));
assertThat(attributes3.get("client").get("stringKey")).isEqualTo(clientAttribute.get("stringKey"));
}
@Test
public void provisionRequestForDeviceWithPreProvisionedStrategy() throws Exception {
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
provisionRequest.addProperty("deviceName", device.getName());
JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString());
String credentialsType = provisionResponse.get("credentialsType");
String credentialsValue = provisionResponse.get("credentialsValue");
String status = provisionResponse.get("status");
assertThat(credentialsType).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(credentialsValue).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(status).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithAllowToCreateNewDevicesStrategy() throws Exception {
String testDeviceName = "test_provision_device";
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
provisionRequest.addProperty("deviceName", testDeviceName);
JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString());
String credentialsType = provisionResponse.get("credentialsType");
String credentialsValue = provisionResponse.get("credentialsValue");
String status = provisionResponse.get("status");
testRestClient.deleteDeviceIfExists(device.getId());
device = testRestClient.getDeviceByName(testDeviceName);
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
assertThat(credentialsType).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(credentialsValue).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(status).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception {
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString());
String status = provisionResponse.get("status");
assertThat(status).isEqualTo("NOT_FOUND");
}
}

106
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java

@ -34,6 +34,8 @@ import org.thingsboard.mqtt.MqttClientConfig;
import org.thingsboard.mqtt.MqttHandler;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.page.PageData;
@ -322,6 +324,104 @@ public class MqttClientTest extends AbstractContainerTest {
assertThat(mqttClient.isConnected()).isFalse();
}
@Test
public void provisionRequestForDeviceWithPreProvisionedStrategy() throws Exception {
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES);
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
MqttMessageListener listener = new MqttMessageListener();
MqttClient mqttClient = getMqttClient("provision", listener);
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
provisionRequest.addProperty("deviceName", device.getName());
mqttClient.publish("/provision/request", Unpooled.wrappedBuffer(provisionRequest.toString().getBytes())).get();
//Wait for response
TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
MqttEvent provisionResponseMsg = listener.getEvents().poll(timeoutMultiplier, TimeUnit.SECONDS);
assertThat(provisionResponseMsg).isNotNull();
JsonNode provisionResponse = mapper.readTree(provisionResponseMsg.getMessage());
assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithAllowToCreateNewDevicesStrategy() throws Exception {
String testDeviceName = "test_provision_device";
DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId());
deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES);
MqttMessageListener listener = new MqttMessageListener();
MqttClient mqttClient = getMqttClient("provision", listener);
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
provisionRequest.addProperty("deviceName", testDeviceName);
mqttClient.publish("/provision/request", Unpooled.wrappedBuffer(provisionRequest.toString().getBytes())).get();
//Wait for response
TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
MqttEvent provisionResponseMsg = listener.getEvents().poll(timeoutMultiplier, TimeUnit.SECONDS);
assertThat(provisionResponseMsg).isNotNull();
JsonNode provisionResponse = mapper.readTree(provisionResponseMsg.getMessage());
testRestClient.deleteDeviceIfExists(device.getId());
device = testRestClient.getDeviceByName(testDeviceName);
DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId());
assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name());
assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId());
assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS");
updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED);
}
@Test
public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception {
MqttMessageListener listener = new MqttMessageListener();
MqttClient mqttClient = getMqttClient("provision", listener);
JsonObject provisionRequest = new JsonObject();
provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY);
provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET);
mqttClient.publish("/provision/request", Unpooled.wrappedBuffer(provisionRequest.toString().getBytes())).get();
//Wait for response
TimeUnit.SECONDS.sleep(3 * timeoutMultiplier);
MqttEvent provisionResponseMsg = listener.getEvents().poll(timeoutMultiplier, TimeUnit.SECONDS);
assertThat(provisionResponseMsg).isNotNull();
JsonNode provisionResponse = mapper.readTree(provisionResponseMsg.getMessage());
assertThat(provisionResponse.get("status").asText()).isEqualTo("NOT_FOUND");
}
private RuleChainId createRootRuleChainForRpcResponse() throws Exception {
RuleChain newRuleChain = new RuleChain();
newRuleChain.setName("testRuleChain");
@ -356,9 +456,13 @@ public class MqttClientTest extends AbstractContainerTest {
}
private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException {
return getMqttClient(deviceCredentials.getCredentialsId(), listener);
}
private MqttClient getMqttClient(String username, MqttMessageListener listener) throws InterruptedException, ExecutionException {
MqttClientConfig clientConfig = new MqttClientConfig();
clientConfig.setClientId("MQTT client from test");
clientConfig.setUsername(deviceCredentials.getCredentialsId());
clientConfig.setUsername(username);
MqttClient mqttClient = MqttClient.create(clientConfig, listener);
mqttClient.connect("localhost", 1883).get();
return mqttClient;

6
pom.xml

@ -77,7 +77,7 @@
<zookeeper.version>3.5.5</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version>
<mvel.version>2.4.25TB</mvel.version>
<tbel.version>1.0.3</tbel.version>
<lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version>
<netty.version>4.1.75.Final</netty.version>
@ -1578,8 +1578,8 @@
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>mvel2</artifactId>
<version>${mvel.version}</version>
<artifactId>tbel</artifactId>
<version>${tbel.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java

@ -64,6 +64,8 @@ public interface RuleEngineTelemetryService {
void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, FutureCallback<Void> callback);
void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List<String> keys, boolean notifyDevice, FutureCallback<Void> callback);
void deleteLatest(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback);
void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback<Collection<String>> callback);

7
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@ -52,8 +53,10 @@ public class DeviceAttributesEventNotificationMsg implements ToDeviceActorNotifi
return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, null, scope, values, false);
}
public static DeviceAttributesEventNotificationMsg onDelete(TenantId tenantId, DeviceId deviceId, Set<AttributeKey> keys) {
return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keys, null, null, true);
public static DeviceAttributesEventNotificationMsg onDelete(TenantId tenantId, DeviceId deviceId, String scope, List<String> keys) {
Set<AttributeKey> keysToNotify = new HashSet<>();
keys.forEach(key -> keysToNotify.add(new AttributeKey(scope, key)));
return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keysToNotify, null, null, true);
}
@Override

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java

@ -48,7 +48,7 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = loadAlarmNodeConfig(configuration);
scriptEngine = ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getAlarmDetailsBuildMvel() : config.getAlarmDetailsBuildJs());
ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getAlarmDetailsBuildTbel() : config.getAlarmDetailsBuildJs());
}
protected abstract C loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException;

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNodeConfiguration.java

@ -33,7 +33,7 @@ public abstract class TbAbstractAlarmNodeConfiguration {
"\n" +
"return details;";
static final String ALARM_DETAILS_BUILD_MVEL_TEMPLATE = "" +
static final String ALARM_DETAILS_BUILD_TBEL_TEMPLATE = "" +
"var details = {};\n" +
"if (metadata.prevAlarmDetails != null) {\n" +
" details = JSON.parse(metadata.prevAlarmDetails);\n" +
@ -49,6 +49,6 @@ public abstract class TbAbstractAlarmNodeConfiguration {
private String alarmType;
private ScriptLanguage scriptLang;
private String alarmDetailsBuildJs;
private String alarmDetailsBuildMvel;
private String alarmDetailsBuildTbel;
}

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java

@ -17,7 +17,6 @@ package org.thingsboard.rule.engine.action;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.script.ScriptLanguage;
@Data
@ -26,9 +25,9 @@ public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigurat
@Override
public TbClearAlarmNodeConfiguration defaultConfiguration() {
TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration();
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setScriptLang(ScriptLanguage.TBEL);
configuration.setAlarmDetailsBuildJs(ALARM_DETAILS_BUILD_JS_TEMPLATE);
configuration.setAlarmDetailsBuildMvel(ALARM_DETAILS_BUILD_MVEL_TEMPLATE);
configuration.setAlarmDetailsBuildTbel(ALARM_DETAILS_BUILD_TBEL_TEMPLATE);
configuration.setAlarmType("General Alarm");
return configuration;
}

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java

@ -39,9 +39,9 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
@Override
public TbCreateAlarmNodeConfiguration defaultConfiguration() {
TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration();
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setScriptLang(ScriptLanguage.TBEL);
configuration.setAlarmDetailsBuildJs(ALARM_DETAILS_BUILD_JS_TEMPLATE);
configuration.setAlarmDetailsBuildMvel(ALARM_DETAILS_BUILD_MVEL_TEMPLATE);
configuration.setAlarmDetailsBuildTbel(ALARM_DETAILS_BUILD_TBEL_TEMPLATE);
configuration.setAlarmType("General Alarm");
configuration.setSeverity(AlarmSeverity.CRITICAL.name());
configuration.setPropagate(false);

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java

@ -56,7 +56,7 @@ public class TbLogNode implements TbNode {
this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class);
this.standard = new TbLogNodeConfiguration().defaultConfiguration().getJsScript().equals(config.getJsScript());
this.scriptEngine = this.standard ? null : ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript());
ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript());
}
@Override

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java

@ -24,14 +24,14 @@ public class TbLogNodeConfiguration implements NodeConfiguration {
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
private String tbelScript;
@Override
public TbLogNodeConfiguration defaultConfiguration() {
TbLogNodeConfiguration configuration = new TbLogNodeConfiguration();
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setScriptLang(ScriptLanguage.TBEL);
configuration.setJsScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
configuration.setMvelScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
configuration.setTbelScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
return configuration;
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java

@ -96,7 +96,7 @@ public class TbMsgGeneratorNode implements TbNode {
if (ctx.isLocalEntity(originatorId)) {
if (initialized.compareAndSet(false, true)) {
this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
scheduleTickMsg(ctx);
}
} else if (initialized.compareAndSet(true, false)) {

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java

@ -35,16 +35,16 @@ public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgG
private EntityType originatorType;
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
private String tbelScript;
@Override
public TbMsgGeneratorNodeConfiguration defaultConfiguration() {
TbMsgGeneratorNodeConfiguration configuration = new TbMsgGeneratorNodeConfiguration();
configuration.setMsgCount(UNLIMITED_MSG_COUNT);
configuration.setPeriodInSeconds(1);
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setScriptLang(ScriptLanguage.TBEL);
configuration.setJsScript(DEFAULT_SCRIPT);
configuration.setMvelScript(DEFAULT_SCRIPT);
configuration.setTbelScript(DEFAULT_SCRIPT);
return configuration;
}
}

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNode.java

@ -23,7 +23,6 @@ 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.server.common.data.StringUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import org.thingsboard.server.common.msg.TbMsg;
@ -53,7 +52,7 @@ public class TbJsFilterNode implements TbNode {
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class);
scriptEngine = ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript());
ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript());
}
@Override

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java

@ -24,14 +24,14 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
private String tbelScript;
@Override
public TbJsFilterNodeConfiguration defaultConfiguration() {
TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setScriptLang(ScriptLanguage.TBEL);
configuration.setJsScript("return msg.temperature > 20;");
configuration.setMvelScript("return msg.temperature > 20;");
configuration.setTbelScript("return msg.temperature > 20;");
return configuration;
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java

@ -56,7 +56,7 @@ public class TbJsSwitchNode implements TbNode {
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class);
this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(),
ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript());
ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript());
}
@Override

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java

@ -15,13 +15,10 @@
*/
package org.thingsboard.rule.engine.filter;
import com.google.common.collect.Sets;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.script.ScriptLanguage;
import java.util.Set;
@Data
public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitchNodeConfiguration> {
@ -33,7 +30,7 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch
"}\n" +
"return nextRelation(metadata, msg);";
private static final String DEFAULT_MVEL_SCRIPT = "function nextRelation(metadata, msg) {\n" +
private static final String DEFAULT_TBEL_SCRIPT = "function nextRelation(metadata, msg) {\n" +
" return ['one','nine'];\n" +
"}\n" +
"if(msgType == 'POST_TELEMETRY_REQUEST') {\n" +
@ -43,14 +40,14 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch
private ScriptLanguage scriptLang;
private String jsScript;
private String mvelScript;
private String tbelScript;
@Override
public TbJsSwitchNodeConfiguration defaultConfiguration() {
TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
configuration.setScriptLang(ScriptLanguage.MVEL);
configuration.setScriptLang(ScriptLanguage.TBEL);
configuration.setJsScript(DEFAULT_JS_SCRIPT);
configuration.setMvelScript(DEFAULT_MVEL_SCRIPT);
configuration.setTbelScript(DEFAULT_TBEL_SCRIPT);
return configuration;
}
}

13
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbFetchDeviceCredentialsNode.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -78,16 +79,20 @@ public class TbFetchDeviceCredentialsNode implements TbNode {
}
TbMsg transformedMsg;
String credentialsType = deviceCredentials.getCredentialsType().name();
DeviceCredentialsType credentialsType = deviceCredentials.getCredentialsType();
JsonNode credentialsInfo = ctx.getDeviceCredentialsService().toCredentialsInfo(deviceCredentials);
if (fetchToMetadata) {
TbMsgMetaData metaData = msg.getMetaData();
metaData.putValue(CREDENTIALS_TYPE, credentialsType);
metaData.putValue(CREDENTIALS, JacksonUtil.toString(credentialsInfo));
metaData.putValue(CREDENTIALS_TYPE, credentialsType.name());
if (credentialsType.equals(DeviceCredentialsType.ACCESS_TOKEN) || credentialsType.equals(DeviceCredentialsType.X509_CERTIFICATE)) {
metaData.putValue(CREDENTIALS, credentialsInfo.asText());
} else {
metaData.putValue(CREDENTIALS, JacksonUtil.toString(credentialsInfo));
}
transformedMsg = TbMsg.transformMsg(msg, msg.getType(), originator, metaData, msg.getData());
} else {
ObjectNode data = (ObjectNode) JacksonUtil.toJsonNode(msg.getData());
data.put(CREDENTIALS_TYPE, credentialsType);
data.put(CREDENTIALS_TYPE, credentialsType.name());
data.set(CREDENTIALS, credentialsInfo);
transformedMsg = TbMsg.transformMsg(msg, msg.getType(), originator, msg.getMetaData(), JacksonUtil.toString(data));
}

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java

@ -24,8 +24,8 @@ import java.util.List;
public class AttributesUpdateNodeCallback extends TelemetryNodeCallback {
private String scope;
private List<AttributeKvEntry> attributes;
private final String scope;
private final List<AttributeKvEntry> attributes;
public AttributesUpdateNodeCallback(TbContext ctx, TbMsg msg, String scope, List<AttributeKvEntry> attributes) {
super(ctx, msg);

37
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -33,6 +33,10 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import java.util.ArrayList;
import java.util.List;
import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY;
import static org.thingsboard.server.common.data.DataConstants.SCOPE;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
@ -40,8 +44,9 @@ import java.util.List;
configClazz = TbMsgAttributesNodeConfiguration.class,
nodeDescription = "Saves attributes data",
nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type. " +
"If upsert(update/insert) operation is completed successfully, rule node will send the \"Attributes Updated\" " +
"event to the root chain of the message originator and send the incoming message via <b>Success</b> chain, otherwise, <b>Failure</b> chain is used.",
"If upsert(update/insert) operation is completed successfully rule node will send the incoming message via <b>Success</b> chain, otherwise, <b>Failure</b> chain is used. " +
"Additionally if checkbox <b>Send attributes updated notification</b> is set to true, rule node will put the \"Attributes Updated\" " +
"event for <b>SHARED_SCOPE</b> and <b>SERVER_SCOPE</b> attributes updates to the corresponding rule engine queue.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeAttributesConfig",
icon = "file_upload"
@ -65,20 +70,38 @@ public class TbMsgAttributesNode implements TbNode {
return;
}
String src = msg.getData();
List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(new JsonParser().parse(src)));
List<AttributeKvEntry> attributes = new ArrayList<>(JsonConverter.convertToAttributes(JsonParser.parseString(src)));
if (attributes.isEmpty()) {
ctx.tellSuccess(msg);
return;
}
String notifyDeviceStr = msg.getMetaData().getValue("notifyDevice");
String scope = getScope(msg.getMetaData().getValue(SCOPE));
boolean sendAttributesUpdateNotification = checkSendNotification(scope);
ctx.getTelemetryService().saveAndNotify(
ctx.getTenantId(),
msg.getOriginator(),
config.getScope(),
scope,
attributes,
config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceStr) || Boolean.parseBoolean(notifyDeviceStr),
new AttributesUpdateNodeCallback(ctx, msg, config.getScope(), attributes)
checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)),
sendAttributesUpdateNotification ?
new AttributesUpdateNodeCallback(ctx, msg, scope, attributes) :
new TelemetryNodeCallback(ctx, msg)
);
}
private boolean checkSendNotification(String scope) {
return config.isSendAttributesUpdatedNotification() && !CLIENT_SCOPE.equals(scope);
}
private boolean checkNotifyDevice(String notifyDeviceMdValue) {
return config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceMdValue) || Boolean.parseBoolean(notifyDeviceMdValue);
}
private String getScope(String mdScopeValue) {
if (StringUtils.isNotEmpty(mdScopeValue)) {
return mdScopeValue;
}
return config.getScope();
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java

@ -25,12 +25,14 @@ public class TbMsgAttributesNodeConfiguration implements NodeConfiguration<TbMsg
private String scope;
private Boolean notifyDevice;
private boolean sendAttributesUpdatedNotification;
@Override
public TbMsgAttributesNodeConfiguration defaultConfiguration() {
TbMsgAttributesNodeConfiguration configuration = new TbMsgAttributesNodeConfiguration();
configuration.setScope(DataConstants.SERVER_SCOPE);
configuration.setNotifyDevice(false);
configuration.setSendAttributesUpdatedNotification(false);
return configuration;
}
}

38
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributes.java → rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNode.java

@ -30,11 +30,15 @@ import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY;
import static org.thingsboard.server.common.data.DataConstants.SCOPE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "delete attributes",
configClazz = TbMsgDeleteAttributesConfiguration.class,
configClazz = TbMsgDeleteAttributesNodeConfiguration.class,
nodeDescription = "Delete attributes for Message Originator.",
nodeDetails = "Attempt to remove attributes by selected keys. If msg originator doesn't have an attribute with " +
" a key selected in the configuration, it will be ignored. If delete operation is completed successfully, " +
@ -44,16 +48,14 @@ import java.util.stream.Collectors;
configDirective = "tbActionNodeDeleteAttributesConfig",
icon = "remove_circle"
)
public class TbMsgDeleteAttributes implements TbNode {
public class TbMsgDeleteAttributesNode implements TbNode {
private TbMsgDeleteAttributesConfiguration config;
private String scope;
private TbMsgDeleteAttributesNodeConfiguration config;
private List<String> keys;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgDeleteAttributesConfiguration.class);
this.scope = config.getScope();
this.config = TbNodeUtils.convert(configuration, TbMsgDeleteAttributesNodeConfiguration.class);
this.keys = config.getKeys();
}
@ -67,7 +69,29 @@ public class TbMsgDeleteAttributes implements TbNode {
if (keysToDelete.isEmpty()) {
ctx.tellSuccess(msg);
} else {
ctx.getTelemetryService().deleteAndNotify(ctx.getTenantId(), msg.getOriginator(), scope, keysToDelete, new AttributesDeleteNodeCallback(ctx, msg, scope, keysToDelete));
String scope = getScope(msg.getMetaData().getValue(SCOPE));
ctx.getTelemetryService().deleteAndNotify(
ctx.getTenantId(),
msg.getOriginator(),
scope,
keysToDelete,
checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY), scope),
config.isSendAttributesDeletedNotification() ?
new AttributesDeleteNodeCallback(ctx, msg, scope, keysToDelete) :
new TelemetryNodeCallback(ctx, msg)
);
}
}
private String getScope(String mdScopeValue) {
if (StringUtils.isNotEmpty(mdScopeValue)) {
return mdScopeValue;
}
return config.getScope();
}
private boolean checkNotifyDevice(String notifyDeviceMdValue, String scope) {
return SHARED_SCOPE.equals(scope) && (config.isNotifyDevice() || Boolean.parseBoolean(notifyDeviceMdValue));
}
}

10
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesConfiguration.java → rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeConfiguration.java

@ -23,16 +23,20 @@ import java.util.Collections;
import java.util.List;
@Data
public class TbMsgDeleteAttributesConfiguration implements NodeConfiguration<TbMsgDeleteAttributesConfiguration> {
public class TbMsgDeleteAttributesNodeConfiguration implements NodeConfiguration<TbMsgDeleteAttributesNodeConfiguration> {
private String scope;
private List<String> keys;
private boolean sendAttributesDeletedNotification;
private boolean notifyDevice;
@Override
public TbMsgDeleteAttributesConfiguration defaultConfiguration() {
TbMsgDeleteAttributesConfiguration configuration = new TbMsgDeleteAttributesConfiguration();
public TbMsgDeleteAttributesNodeConfiguration defaultConfiguration() {
TbMsgDeleteAttributesNodeConfiguration configuration = new TbMsgDeleteAttributesNodeConfiguration();
configuration.setScope(DataConstants.SERVER_SCOPE);
configuration.setKeys(Collections.emptyList());
configuration.setSendAttributesDeletedNotification(false);
configuration.setNotifyDevice(false);
return configuration;
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java

@ -70,7 +70,7 @@ public class TbJsonPathNode implements TbNode {
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
if (!TbJsonPathNodeConfiguration.DEFAULT_JSON_PATH.equals(this.jsonPathValue)) {
try {
JsonNode jsonPathData = jsonPath.read(msg.getData(), this.configurationJsonPath);
Object jsonPathData = jsonPath.read(msg.getData(), this.configurationJsonPath);
ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(jsonPathData)));
} catch (PathNotFoundException e) {
ctx.tellFailure(msg, e);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save