Browse Source

Merge remote-tracking branch 'upstream/master'

pull/2282/head
Vladyslav_Prykhodko 7 years ago
parent
commit
5a9f4e001e
  1. 2
      application/pom.xml
  2. 2
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  3. 5
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  4. 15
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  5. 3
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  6. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  7. 3
      application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
  8. 13
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  9. 9
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  10. 3
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
  11. 108
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  12. 46
      application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java
  13. 5
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  14. 3
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  15. 39
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  16. 5
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  17. 3
      application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java
  18. 3
      application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
  19. 3
      application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java
  20. 9
      application/src/main/resources/thingsboard.yml
  21. 2
      common/dao-api/pom.xml
  22. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  23. 2
      common/data/pom.xml
  24. 4
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  25. 2
      common/message/pom.xml
  26. 2
      common/pom.xml
  27. 2
      common/queue/pom.xml
  28. 2
      common/transport/coap/pom.xml
  29. 2
      common/transport/http/pom.xml
  30. 2
      common/transport/mqtt/pom.xml
  31. 2
      common/transport/pom.xml
  32. 6
      common/transport/transport-api/pom.xml
  33. 27
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
  34. 3
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java
  35. 1
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java
  36. 2
      common/util/pom.xml
  37. 54
      common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java
  38. 2
      dao/pom.xml
  39. 36
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  40. 3
      dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java
  41. 12
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  42. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  43. 27
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java
  44. 19
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java
  45. 3
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java
  46. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java
  47. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java
  48. 62
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvInsertRepository.java
  49. 38
      dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java
  50. 33
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java
  51. 4
      dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java
  52. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java
  53. 58
      dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java
  54. 33
      dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java
  55. 5
      dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
  56. 1
      dao/src/main/resources/cassandra/schema-entities.cql
  57. 1
      dao/src/main/resources/sql/schema-entities.sql
  58. 2
      msa/black-box-tests/pom.xml
  59. 23
      msa/js-executor/api/jsInvokeMessageProcessor.js
  60. 1
      msa/js-executor/config/custom-environment-variables.yml
  61. 1
      msa/js-executor/config/default.yml
  62. 2
      msa/js-executor/package.json
  63. 2
      msa/js-executor/pom.xml
  64. 2
      msa/pom.xml
  65. 2
      msa/tb-node/pom.xml
  66. 2
      msa/tb/pom.xml
  67. 2
      msa/transport/coap/pom.xml
  68. 2
      msa/transport/http/pom.xml
  69. 2
      msa/transport/mqtt/pom.xml
  70. 2
      msa/transport/pom.xml
  71. 2
      msa/web-ui/package.json
  72. 2
      msa/web-ui/pom.xml
  73. 4
      netty-mqtt/pom.xml
  74. 11
      pom.xml
  75. 2
      rule-engine/pom.xml
  76. 2
      rule-engine/rule-engine-api/pom.xml
  77. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java
  78. 2
      rule-engine/rule-engine-components/pom.xml
  79. 15
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java
  80. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
  81. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
  82. 10
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  83. 38
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java
  84. 2
      tools/pom.xml
  85. 29
      tools/src/main/java/org/thingsboard/client/tools/RestClient.java
  86. 2
      transport/coap/pom.xml
  87. 2
      transport/http/pom.xml
  88. 2
      transport/mqtt/pom.xml
  89. 2
      transport/pom.xml
  90. 2
      ui/package.json
  91. 2
      ui/pom.xml
  92. 37
      ui/src/app/api/alarm.service.js
  93. 6
      ui/src/app/api/subscription.js
  94. 8
      ui/src/app/components/widget/widget-config.directive.js
  95. 75
      ui/src/app/components/widget/widget-config.tpl.html
  96. 4
      ui/src/app/components/widget/widget.controller.js
  97. 8
      ui/src/app/locale/locale.constant-en_US.json
  98. 8
      ui/src/app/locale/locale.constant-ru_RU.json
  99. 8
      ui/src/app/locale/locale.constant-uk_UA.json

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>

2
application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java

@ -95,8 +95,6 @@ public class DefaultActorService implements ActorService {
private ActorRef rpcManagerActor; private ActorRef rpcManagerActor;
private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@PostConstruct @PostConstruct
public void initActorSystem() { public void initActorSystem() {
log.info("Initializing Actor system."); log.info("Initializing Actor system.");

5
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -82,7 +82,8 @@ public class DeviceController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device", method = RequestMethod.POST) @RequestMapping(value = "/device", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device saveDevice(@RequestBody Device device) throws ThingsboardException { public Device saveDevice(@RequestBody Device device,
@RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException {
try { try {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
@ -91,7 +92,7 @@ public class DeviceController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation,
device.getId(), device); device.getId(), device);
Device savedDevice = checkNotNull(deviceService.saveDevice(device)); Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));
actorService actorService
.onDeviceNameOrTypeUpdate( .onDeviceNameOrTypeUpdate(

15
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -76,20 +77,6 @@ public class RpcController extends BaseController {
@Autowired @Autowired
private AccessValidator accessValidator; private AccessValidator accessValidator;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -37,6 +37,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -108,7 +109,7 @@ public class TelemetryController extends BaseController {
@PostConstruct @PostConstruct
public void initExecutor() { public void initExecutor() {
executor = Executors.newSingleThreadExecutor(); executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller"));
} }
@PreDestroy @PreDestroy

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

@ -123,6 +123,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ..."); log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ...");
databaseUpgradeService.upgradeDatabase("2.4.1"); databaseUpgradeService.upgradeDatabase("2.4.1");
case "2.4.2.1":
log.info("Upgrading ThingsBoard from version 2.4.2.1 to 2.4.3 ...");
databaseUpgradeService.upgradeDatabase("2.4.2.1");
log.info("Updating system data..."); log.info("Updating system data...");

3
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java

@ -41,6 +41,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
@ -114,7 +115,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms")); Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms")); Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms"));
reconnectExecutorService = Executors.newSingleThreadExecutor(); reconnectExecutorService = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("zk-discovery"));
log.info("Initializing discovery service using ZK connect string: {}", zkUrl); log.info("Initializing discovery service using ZK connect string: {}", zkUrl);

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

@ -271,8 +271,21 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Updating schema ..."); log.info("Updating schema ...");
String updateAssetTableStmt = "alter table asset add label text"; String updateAssetTableStmt = "alter table asset add label text";
try { try {
log.info("Updating assets ...");
cluster.getSession().execute(updateAssetTableStmt); cluster.getSession().execute(updateAssetTableStmt);
Thread.sleep(2500); Thread.sleep(2500);
log.info("Assets updated.");
} catch (InvalidQueryException e) {}
log.info("Schema updated.");
break;
case "2.4.2.1":
log.info("Updating schema ...");
String updateAlarmTableStmt = "alter table alarm add propagate_relation_types text";
try {
log.info("Updating alarms ...");
cluster.getSession().execute(updateAlarmTableStmt);
Thread.sleep(2500);
log.info("Alarms updated.");
} catch (InvalidQueryException e) {} } catch (InvalidQueryException e) {}
log.info("Schema updated."); log.info("Schema updated.");
break; break;

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

@ -196,6 +196,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Schema updated."); log.info("Schema updated.");
} }
break; break;
case "2.4.2.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_relation_types varchar"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
log.info("Schema updated.");
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

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

@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
@ -83,7 +84,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
@PostConstruct @PostConstruct
public void initExecutor() { public void initExecutor() {
rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor(); rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("rpc-callback"));
} }
@PreDestroy @PreDestroy

108
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java

@ -15,21 +15,34 @@
*/ */
package org.thingsboard.server.service.script; package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import delight.nashornsandbox.NashornSandbox; import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes; import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import javax.script.Invocable; import javax.script.Invocable;
import javax.script.ScriptEngine; import javax.script.ScriptEngine;
import javax.script.ScriptException; import javax.script.ScriptException;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService {
@ -37,9 +50,46 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
private NashornSandbox sandbox; private NashornSandbox sandbox;
private ScriptEngine engine; private ScriptEngine engine;
private ExecutorService monitorExecutorService; private ExecutorService monitorExecutorService;
private ScheduledExecutorService timeoutExecutorService;
private final AtomicInteger jsPushedMsgs = new AtomicInteger(0);
private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0);
private final AtomicInteger jsEvalMsgs = new AtomicInteger(0);
private final AtomicInteger jsFailedMsgs = new AtomicInteger(0);
private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0);
private final FutureCallback<UUID> evalCallback = new JsStatCallback<UUID>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs);
private final FutureCallback<Object> invokeCallback = new JsStatCallback<Object>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs);
@Autowired
@Getter
private JsExecutorService jsExecutor;
@Value("${js.local.max_requests_timeout:0}")
private long maxRequestsTimeout;
@Value("${js.local.stats.enabled:false}")
private boolean statsEnabled;
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms:10000}")
public void printStats() {
if (statsEnabled) {
int pushedMsgs = jsPushedMsgs.getAndSet(0);
int invokeMsgs = jsInvokeMsgs.getAndSet(0);
int evalMsgs = jsEvalMsgs.getAndSet(0);
int failed = jsFailedMsgs.getAndSet(0);
int timedOut = jsTimeoutMsgs.getAndSet(0);
if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
}
}
}
@PostConstruct @PostConstruct
public void init() { public void init() {
if (maxRequestsTimeout > 0) {
timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout"));
}
if (useJsSandbox()) { if (useJsSandbox()) {
sandbox = NashornSandboxes.create(); sandbox = NashornSandboxes.create();
monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize()); monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize());
@ -59,6 +109,9 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
if (monitorExecutorService != null) { if (monitorExecutorService != null) {
monitorExecutorService.shutdownNow(); monitorExecutorService.shutdownNow();
} }
if (timeoutExecutorService != null) {
timeoutExecutorService.shutdownNow();
}
} }
protected abstract boolean useJsSandbox(); protected abstract boolean useJsSandbox();
@ -69,34 +122,49 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
@Override @Override
protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) { protected ListenableFuture<UUID> doEval(UUID scriptId, String functionName, String jsScript) {
try { jsPushedMsgs.incrementAndGet();
if (useJsSandbox()) { ListenableFuture<UUID> result = jsExecutor.executeAsync(() -> {
sandbox.eval(jsScript); try {
} else { if (useJsSandbox()) {
engine.eval(jsScript); sandbox.eval(jsScript);
} else {
engine.eval(jsScript);
}
scriptIdToNameMap.put(scriptId, functionName);
return scriptId;
} catch (Exception e) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
throw new ExecutionException(e);
} }
scriptIdToNameMap.put(scriptId, functionName); });
} catch (Exception e) { if (maxRequestsTimeout > 0) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e); result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
return Futures.immediateFailedFuture(e);
} }
return Futures.immediateFuture(scriptId); Futures.addCallback(result, evalCallback);
return result;
} }
@Override @Override
protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) { protected ListenableFuture<Object> doInvokeFunction(UUID scriptId, String functionName, Object[] args) {
try { jsPushedMsgs.incrementAndGet();
Object result; ListenableFuture<Object> result = jsExecutor.executeAsync(() -> {
if (useJsSandbox()) { try {
result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args); if (useJsSandbox()) {
} else { return sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
result = ((Invocable) engine).invokeFunction(functionName, args); } else {
return ((Invocable) engine).invokeFunction(functionName, args);
}
} catch (Exception e) {
onScriptExecutionError(scriptId);
throw new ExecutionException(e);
} }
return Futures.immediateFuture(result); });
} catch (Exception e) {
onScriptExecutionError(scriptId); if (maxRequestsTimeout > 0) {
return Futures.immediateFailedFuture(e); result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
} }
Futures.addCallback(result, invokeCallback);
return result;
} }
protected void doRelease(UUID scriptId, String functionName) throws ScriptException { protected void doRelease(UUID scriptId, String functionName) throws ScriptException {

46
application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2019 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.script;
import com.google.common.util.concurrent.FutureCallback;
import lombok.AllArgsConstructor;
import javax.annotation.Nullable;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
@AllArgsConstructor
public class JsStatCallback<T> implements FutureCallback<T> {
private final AtomicInteger jsSuccessMsgs;
private final AtomicInteger jsTimeoutMsgs;
private final AtomicInteger jsFailedMsgs;
@Override
public void onSuccess(@Nullable T result) {
jsSuccessMsgs.incrementAndGet();
}
@Override
public void onFailure(Throwable t) {
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
jsTimeoutMsgs.incrementAndGet();
} else {
jsFailedMsgs.incrementAndGet();
}
}
}

5
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -139,6 +139,11 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
return executeScript(msg); return executeScript(msg);
} }
@Override
public ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException {
return executeScriptAsync(msg);
}
@Override @Override
public String executeToString(TbMsg msg) throws ScriptException { public String executeToString(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg); JsonNode result = executeScript(msg);

3
application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java

@ -24,6 +24,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
@ -105,7 +106,7 @@ public class AccessValidator {
@PostConstruct @PostConstruct
public void initExecutor() { public void initExecutor() {
executor = Executors.newSingleThreadExecutor(); executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("access-validator"));
} }
@PreDestroy @PreDestroy

39
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -31,6 +31,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -140,11 +141,13 @@ public class DefaultDeviceStateService implements DeviceStateService {
private ListeningScheduledExecutorService queueExecutor; private ListeningScheduledExecutorService queueExecutor;
private ConcurrentMap<TenantId, Set<DeviceId>> tenantDevices = new ConcurrentHashMap<>(); private ConcurrentMap<TenantId, Set<DeviceId>> tenantDevices = new ConcurrentHashMap<>();
private ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); private ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
private ConcurrentMap<DeviceId, Long> deviceLastReportedActivity = new ConcurrentHashMap<>();
private ConcurrentMap<DeviceId, Long> deviceLastSavedActivity = new ConcurrentHashMap<>();
@PostConstruct @PostConstruct
public void init() { public void init() {
// Should be always single threaded due to absence of locks. // Should be always single threaded due to absence of locks.
queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor()); queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state")));
queueExecutor.submit(this::initStateFromDB); queueExecutor.submit(this::initStateFromDB);
queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); queueExecutor.scheduleAtFixedRate(this::updateState, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
//TODO: schedule persistence in v2.1; //TODO: schedule persistence in v2.1;
@ -174,6 +177,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
@Override @Override
public void onDeviceActivity(DeviceId deviceId) { public void onDeviceActivity(DeviceId deviceId) {
deviceLastReportedActivity.put(deviceId, System.currentTimeMillis());
queueExecutor.submit(() -> onDeviceActivitySync(deviceId)); queueExecutor.submit(() -> onDeviceActivitySync(deviceId));
} }
@ -244,6 +248,8 @@ public class DefaultDeviceStateService implements DeviceStateService {
tenantDeviceSet.remove(device.getId()); tenantDeviceSet.remove(device.getId());
} }
deviceStates.remove(device.getId()); deviceStates.remove(device.getId());
deviceLastReportedActivity.remove(device.getId());
deviceLastSavedActivity.remove(device.getId());
} }
} }
try { try {
@ -289,7 +295,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
private void updateState() { private void updateState() {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet()); Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet());
log.info("Calculating state updates for {} devices", deviceStates.size()); log.debug("Calculating state updates for {} devices", deviceStates.size());
for (DeviceId deviceId : deviceIds) { for (DeviceId deviceId : deviceIds) {
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (stateData != null) { if (stateData != null) {
@ -304,6 +310,8 @@ public class DefaultDeviceStateService implements DeviceStateService {
} else { } else {
log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId); log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId);
deviceStates.remove(deviceId); deviceStates.remove(deviceId);
deviceLastReportedActivity.remove(deviceId);
deviceLastSavedActivity.remove(deviceId);
} }
} }
} }
@ -329,17 +337,20 @@ public class DefaultDeviceStateService implements DeviceStateService {
} }
private void onDeviceActivitySync(DeviceId deviceId) { private void onDeviceActivitySync(DeviceId deviceId) {
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); long lastReportedActivity = deviceLastReportedActivity.getOrDefault(deviceId, 0L);
if (stateData != null) { long lastSavedActivity = deviceLastSavedActivity.getOrDefault(deviceId, 0L);
DeviceState state = stateData.getState(); if (lastReportedActivity > 0 && lastReportedActivity > lastSavedActivity) {
long ts = System.currentTimeMillis(); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
stateData.getState().setLastActivityTime(ts); if (stateData != null) {
pushRuleEngineMessage(stateData, ACTIVITY_EVENT); DeviceState state = stateData.getState();
save(deviceId, LAST_ACTIVITY_TIME, ts); stateData.getState().setLastActivityTime(lastReportedActivity);
pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
if (!state.isActive()) { save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity);
state.setActive(true); deviceLastSavedActivity.put(deviceId, lastReportedActivity);
save(deviceId, ACTIVITY_STATE, state.isActive()); if (!state.isActive()) {
state.setActive(true);
save(deviceId, ACTIVITY_STATE, state.isActive());
}
} }
} }
} }
@ -430,6 +441,8 @@ public class DefaultDeviceStateService implements DeviceStateService {
Optional<ServerAddress> address = routingService.resolveById(deviceId); Optional<ServerAddress> address = routingService.resolveById(deviceId);
if (!address.isPresent()) { if (!address.isPresent()) {
deviceStates.remove(deviceId); deviceStates.remove(deviceId);
deviceLastReportedActivity.remove(deviceId);
deviceLastSavedActivity.remove(deviceId);
Set<DeviceId> deviceIds = tenantDevices.get(tenantId); Set<DeviceId> deviceIds = tenantDevices.get(tenantId);
if (deviceIds != null) { if (deviceIds != null) {
deviceIds.remove(deviceId); deviceIds.remove(deviceId);

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

@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
@ -110,8 +111,8 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@PostConstruct @PostConstruct
public void initExecutor() { public void initExecutor() {
tsCallBackExecutor = Executors.newSingleThreadExecutor(); tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ts-sub-callback"));
wsCallBackExecutor = Executors.newSingleThreadExecutor(); wsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ws-sub-callback"));
} }
@PreDestroy @PreDestroy

3
application/src/main/java/org/thingsboard/server/service/transaction/BaseRuleChainTransactionService.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.RuleChainTransactionService; import org.thingsboard.rule.engine.api.RuleChainTransactionService;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
@ -71,7 +72,7 @@ public class BaseRuleChainTransactionService implements RuleChainTransactionServ
@PostConstruct @PostConstruct
public void init() { public void init() {
timeoutExecutor = Executors.newSingleThreadExecutor(); timeoutExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("rule-chain-transaction"));
executeOnTimeout(); executeOnTimeout();
} }

3
application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java

@ -32,6 +32,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceActorToTransportMsg;
@ -103,7 +104,7 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ
private TBKafkaConsumerTemplate<ToRuleEngineMsg> ruleEngineConsumer; private TBKafkaConsumerTemplate<ToRuleEngineMsg> ruleEngineConsumer;
private TBKafkaProducerTemplate<ToTransportMsg> notificationsProducer; private TBKafkaProducerTemplate<ToTransportMsg> notificationsProducer;
private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(); private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-main-consumer"));
private volatile boolean stopped = false; private volatile boolean stopped = false;

3
application/src/main/java/org/thingsboard/server/service/update/DefaultUpdateService.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.UpdateMessage;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -50,7 +51,7 @@ public class DefaultUpdateService implements UpdateService {
@Value("${updates.enabled}") @Value("${updates.enabled}")
private boolean updatesEnabled; private boolean updatesEnabled;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("tb-update-service"));
private ScheduledFuture checkUpdatesFuture = null; private ScheduledFuture checkUpdatesFuture = null;
private RestTemplate restClient = new RestTemplate(); private RestTemplate restClient = new RestTemplate();

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

@ -195,10 +195,6 @@ cassandra:
# SQL configuration parameters # SQL configuration parameters
sql: sql:
# Specify executor service type used to perform timeseries insert tasks: SINGLE or FIXED
ts_inserts_executor_type: "${SQL_TS_INSERTS_EXECUTOR_TYPE:fixed}"
# Specify thread pool size for FIXED executor service type
ts_inserts_fixed_thread_pool_size: "${SQL_TS_INSERTS_FIXED_THREAD_POOL_SIZE:200}"
# Specify batch size for persisting attribute updates # Specify batch size for persisting attribute updates
attributes: attributes:
batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
@ -464,6 +460,11 @@ js:
max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:3000}" max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:3000}"
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}" max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}"
# JS Eval max request timeout. 0 - no timeout
max_requests_timeout: "${LOCAL_JS_MAX_REQUEST_TIMEOUT:0}"
stats:
enabled: "${TB_JS_LOCAL_STATS_ENABLED:false}"
print_interval_ms: "${TB_JS_LOCAL_STATS_PRINT_INTERVAL_MS:10000}"
# Remote JavaScript environment properties # Remote JavaScript environment properties
remote: remote:
# JS Eval request topic # JS Eval request topic

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java

@ -37,6 +37,8 @@ public interface DeviceService {
Device saveDevice(Device device); Device saveDevice(Device device);
Device saveDeviceWithAccessToken(Device device, String accessToken);
Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId); Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId);
Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId); Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId);

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

4
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import java.util.List;
/** /**
* Created by ashvayka on 11.05.17. * Created by ashvayka on 11.05.17.
*/ */
@ -45,6 +47,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId {
private long clearTs; private long clearTs;
private transient JsonNode details; private transient JsonNode details;
private boolean propagate; private boolean propagate;
private List<String> propagateRelationTypes;
public Alarm() { public Alarm() {
super(); super();
@ -68,6 +71,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId {
this.clearTs = alarm.getClearTs(); this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails(); this.details = alarm.getDetails();
this.propagate = alarm.isPropagate(); this.propagate = alarm.isPropagate();
this.propagateRelationTypes = alarm.getPropagateRelationTypes();
} }
@Override @Override

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

6
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>
@ -44,6 +44,10 @@
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId> <artifactId>message</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>util</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId> <artifactId>queue</artifactId>

27
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.transport.service;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -27,6 +28,7 @@ import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.Random;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -176,10 +178,23 @@ public abstract class AbstractTransportService implements TransportService {
sessions.remove(uuid); sessions.remove(uuid);
sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance());
} else { } else {
process(sessionMD.getSessionInfo(), TransportProtos.SubscriptionInfoProto.newBuilder() if (sessionMD.getLastActivityTime() > sessionMD.getLastReportedActivityTime()) {
.setAttributeSubscription(sessionMD.isSubscribedToAttributes()) final long lastActivityTime = sessionMD.getLastActivityTime();
.setRpcSubscription(sessionMD.isSubscribedToRPC()) process(sessionMD.getSessionInfo(), TransportProtos.SubscriptionInfoProto.newBuilder()
.setLastActivityTime(sessionMD.getLastActivityTime()).build(), null); .setAttributeSubscription(sessionMD.isSubscribedToAttributes())
.setRpcSubscription(sessionMD.isSubscribedToRPC())
.setLastActivityTime(sessionMD.getLastActivityTime()).build(), new TransportServiceCallback<Void>() {
@Override
public void onSuccess(Void msg) {
sessionMD.setLastReportedActivityTime(lastActivityTime);
}
@Override
public void onError(Throwable e) {
log.warn("[{}] Failed to report last activity time", uuid, e);
}
});
}
} }
}); });
} }
@ -286,9 +301,9 @@ public abstract class AbstractTransportService implements TransportService {
new TbRateLimits(perTenantLimitsConf); new TbRateLimits(perTenantLimitsConf);
new TbRateLimits(perDevicesLimitsConf); new TbRateLimits(perDevicesLimitsConf);
} }
this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(); this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler"));
this.transportCallbackExecutor = Executors.newWorkStealingPool(20); this.transportCallbackExecutor = Executors.newWorkStealingPool(20);
this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, sessionReportTimeout, sessionReportTimeout, TimeUnit.MILLISECONDS); this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS);
} }
public void destroy() { public void destroy() {

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

@ -25,6 +25,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
@ -100,7 +101,7 @@ public class RemoteTransportService extends AbstractTransportService {
private TBKafkaProducerTemplate<ToRuleEngineMsg> ruleEngineProducer; private TBKafkaProducerTemplate<ToRuleEngineMsg> ruleEngineProducer;
private TBKafkaConsumerTemplate<ToTransportMsg> mainConsumer; private TBKafkaConsumerTemplate<ToTransportMsg> mainConsumer;
private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(); private ExecutorService mainConsumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("remote-transport-consumer"));
private volatile boolean stopped = false; private volatile boolean stopped = false;

1
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java

@ -34,6 +34,7 @@ class SessionMetaData {
private ScheduledFuture scheduledFuture; private ScheduledFuture scheduledFuture;
private volatile long lastActivityTime; private volatile long lastActivityTime;
private volatile long lastReportedActivityTime;
private volatile boolean subscribedToAttributes; private volatile boolean subscribedToAttributes;
private volatile boolean subscribedToRPC; private volatile boolean subscribedToRPC;

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

54
common/util/src/main/java/org/thingsboard/common/util/ThingsBoardThreadFactory.java

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2019 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.common.util;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Copy of Executors.DefaultThreadFactory but with ability to set name of the pool
*/
public class ThingsBoardThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public static ThingsBoardThreadFactory forName(String name) {
return new ThingsBoardThreadFactory(name);
}
private ThingsBoardThreadFactory(String name) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = name + "-" +
poolNumber.getAndIncrement() +
"-thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

36
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -23,8 +23,9 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmId;
@ -52,6 +53,7 @@ import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -59,6 +61,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -81,7 +84,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@PostConstruct @PostConstruct
public void startExecutor() { public void startExecutor() {
readResultsProcessingExecutor = Executors.newCachedThreadPool(); readResultsProcessingExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("alarm-service"));
} }
@PreDestroy @PreDestroy
@ -154,8 +157,14 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private List<EntityId> getParentEntities(Alarm alarm) throws InterruptedException, ExecutionException { private List<EntityId> getParentEntities(Alarm alarm) throws InterruptedException, ExecutionException {
EntityRelationsQuery query = new EntityRelationsQuery(); EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE, false)); RelationsSearchParameters parameters = new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE, false);
return relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList()); query.setParameters(parameters);
List<String> propagateRelationTypes = alarm.getPropagateRelationTypes();
Stream<EntityRelation> relations = relationService.findByQuery(alarm.getTenantId(), query).get().stream();
if (!CollectionUtils.isEmpty(propagateRelationTypes)) {
relations = relations.filter(entityRelation -> propagateRelationTypes.contains(entityRelation.getType()));
}
return relations.map(EntityRelation::getFrom).collect(Collectors.toList());
} }
private ListenableFuture<Alarm> updateAlarm(Alarm update) { private ListenableFuture<Alarm> updateAlarm(Alarm update) {
@ -360,13 +369,30 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
existing.setSeverity(alarm.getSeverity()); existing.setSeverity(alarm.getSeverity());
existing.setDetails(alarm.getDetails()); existing.setDetails(alarm.getDetails());
existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); existing.setPropagate(existing.isPropagate() || alarm.isPropagate());
List<String> existingPropagateRelationTypes = existing.getPropagateRelationTypes();
List<String> newRelationTypes = alarm.getPropagateRelationTypes();
if (!CollectionUtils.isEmpty(newRelationTypes)) {
if (!CollectionUtils.isEmpty(existingPropagateRelationTypes)) {
existing.setPropagateRelationTypes(Stream.concat(existingPropagateRelationTypes.stream(), newRelationTypes.stream())
.distinct()
.collect(Collectors.toList()));
} else {
existing.setPropagateRelationTypes(newRelationTypes);
}
}
return existing; return existing;
} }
private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) { private void updateRelations(Alarm alarm, AlarmStatus oldStatus, AlarmStatus newStatus) {
try { try {
List<EntityRelation> relations = relationService.findByToAsync(alarm.getTenantId(), alarm.getId(), RelationTypeGroup.ALARM).get(); List<EntityRelation> relations = relationService.findByToAsync(alarm.getTenantId(), alarm.getId(), RelationTypeGroup.ALARM).get();
Set<EntityId> parents = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toSet());
List<String> propagateRelationTypes = alarm.getPropagateRelationTypes();
Stream<EntityRelation> relationStream = relations.stream();
if (!CollectionUtils.isEmpty(propagateRelationTypes)) {
relationStream = relationStream.filter(entityRelation -> propagateRelationTypes.contains(entityRelation.getType()));
}
Set<EntityId> parents = relationStream.map(EntityRelation::getFrom).collect(Collectors.toSet());
for (EntityId parentId : parents) { for (EntityId parentId : parents) {
updateAlarmRelation(alarm.getTenantId(), parentId, alarm.getId(), oldStatus, newStatus); updateAlarmRelation(alarm.getTenantId(), parentId, alarm.getId(), oldStatus, newStatus);
} }

3
dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java

@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.audit.AuditLog;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -117,7 +118,7 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!"); throw new RuntimeException("Failed to parse partitioning property: " + partitioning + "!");
} }
} }
readResultsProcessingExecutor = Executors.newCachedThreadPool(); readResultsProcessingExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("audit-log"));
} }
@PreDestroy @PreDestroy

12
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -119,9 +119,19 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
return deviceOpt.orElse(null); return deviceOpt.orElse(null);
} }
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
@Override
public Device saveDeviceWithAccessToken(Device device, String accessToken) {
return doSaveDevice(device, accessToken);
}
@CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
@Override @Override
public Device saveDevice(Device device) { public Device saveDevice(Device device) {
return doSaveDevice(device, null);
}
private Device doSaveDevice(Device device, String accessToken) {
log.trace("Executing saveDevice [{}]", device); log.trace("Executing saveDevice [{}]", device);
deviceValidator.validate(device, Device::getTenantId); deviceValidator.validate(device, Device::getTenantId);
Device savedDevice; Device savedDevice;
@ -143,7 +153,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
DeviceCredentials deviceCredentials = new DeviceCredentials(); DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId()));
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(RandomStringUtils.randomAlphanumeric(20)); deviceCredentials.setCredentialsId(!StringUtils.isEmpty(accessToken) ? accessToken : RandomStringUtils.randomAlphanumeric(20));
deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials);
} }
return savedDevice; return savedDevice;

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

@ -231,6 +231,7 @@ public class ModelConstants {
public static final String ALARM_ACK_TS_PROPERTY = "ack_ts"; public static final String ALARM_ACK_TS_PROPERTY = "ack_ts";
public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts"; public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
public static final String ALARM_PROPAGATE_PROPERTY = "propagate"; public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types";
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";

27
dao/src/main/java/org/thingsboard/server/dao/model/nosql/AlarmEntity.java

@ -23,6 +23,8 @@ import com.datastax.driver.mapping.annotations.Table;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmId;
@ -36,6 +38,8 @@ import org.thingsboard.server.dao.model.type.AlarmStatusCodec;
import org.thingsboard.server.dao.model.type.EntityTypeCodec; import org.thingsboard.server.dao.model.type.EntityTypeCodec;
import org.thingsboard.server.dao.model.type.JsonCodec; import org.thingsboard.server.dao.model.type.JsonCodec;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
@ -46,6 +50,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPE
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_RELATION_TYPES;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
@ -102,6 +107,9 @@ public final class AlarmEntity implements BaseEntity<Alarm> {
@Column(name = ALARM_PROPAGATE_PROPERTY) @Column(name = ALARM_PROPAGATE_PROPERTY)
private Boolean propagate; private Boolean propagate;
@Column(name = ALARM_PROPAGATE_RELATION_TYPES)
private String propagateRelationTypes;
public AlarmEntity() { public AlarmEntity() {
super(); super();
} }
@ -125,6 +133,12 @@ public final class AlarmEntity implements BaseEntity<Alarm> {
this.ackTs = alarm.getAckTs(); this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs(); this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails(); this.details = alarm.getDetails();
this.details = alarm.getDetails();
if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) {
this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes());
} else {
this.propagateRelationTypes = null;
}
} }
public UUID getId() { public UUID getId() {
@ -231,6 +245,14 @@ public final class AlarmEntity implements BaseEntity<Alarm> {
this.propagate = propagate; this.propagate = propagate;
} }
public String getPropagateRelationTypes() {
return propagateRelationTypes;
}
public void setPropagateRelationTypes(String propagateRelationTypes) {
this.propagateRelationTypes = propagateRelationTypes;
}
@Override @Override
public Alarm toData() { public Alarm toData() {
Alarm alarm = new Alarm(new AlarmId(id)); Alarm alarm = new Alarm(new AlarmId(id));
@ -248,6 +270,11 @@ public final class AlarmEntity implements BaseEntity<Alarm> {
alarm.setAckTs(ackTs); alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs); alarm.setClearTs(clearTs);
alarm.setDetails(details); alarm.setDetails(details);
if (!StringUtils.isEmpty(propagateRelationTypes)) {
alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(",")));
} else {
alarm.setPropagateRelationTypes(Collections.emptyList());
}
return alarm; return alarm;
} }

19
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java

@ -21,6 +21,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type; import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDef;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
@ -40,6 +42,9 @@ import javax.persistence.EnumType;
import javax.persistence.Enumerated; import javax.persistence.Enumerated;
import javax.persistence.Table; import javax.persistence.Table;
import java.util.Arrays;
import java.util.Collections;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
@ -47,6 +52,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPE
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_RELATION_TYPES;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
@ -99,6 +105,9 @@ public final class AlarmEntity extends BaseSqlEntity<Alarm> implements BaseEntit
@Column(name = ALARM_PROPAGATE_PROPERTY) @Column(name = ALARM_PROPAGATE_PROPERTY)
private Boolean propagate; private Boolean propagate;
@Column(name = ALARM_PROPAGATE_RELATION_TYPES)
private String propagateRelationTypes;
public AlarmEntity() { public AlarmEntity() {
super(); super();
} }
@ -122,6 +131,11 @@ public final class AlarmEntity extends BaseSqlEntity<Alarm> implements BaseEntit
this.ackTs = alarm.getAckTs(); this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs(); this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails(); this.details = alarm.getDetails();
if (!CollectionUtils.isEmpty(alarm.getPropagateRelationTypes())) {
this.propagateRelationTypes = String.join(",", alarm.getPropagateRelationTypes());
} else {
this.propagateRelationTypes = null;
}
} }
@Override @Override
@ -141,6 +155,11 @@ public final class AlarmEntity extends BaseSqlEntity<Alarm> implements BaseEntit
alarm.setAckTs(ackTs); alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs); alarm.setClearTs(clearTs);
alarm.setDetails(details); alarm.setDetails(details);
if(!StringUtils.isEmpty(propagateRelationTypes)) {
alarm.setPropagateRelationTypes(Arrays.asList(propagateRelationTypes.split(",")));
} else {
alarm.setPropagateRelationTypes(Collections.emptyList());
}
return alarm; return alarm;
} }

3
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractAsyncDao.java

@ -20,6 +20,7 @@ import com.datastax.driver.core.ResultSetFuture;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -36,7 +37,7 @@ public abstract class CassandraAbstractAsyncDao extends CassandraAbstractDao {
@PostConstruct @PostConstruct
public void startExecutor() { public void startExecutor() {
readResultsProcessingExecutor = Executors.newCachedThreadPool(); readResultsProcessingExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("cassandra-callback"));
} }
@PreDestroy @PreDestroy

3
dao/src/main/java/org/thingsboard/server/dao/sql/ScheduledLogExecutorComponent.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.sql; package org.thingsboard.server.dao.sql;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -30,7 +31,7 @@ public class ScheduledLogExecutorComponent {
@PostConstruct @PostConstruct
public void init() { public void init() {
schedulerLogExecutor = Executors.newSingleThreadScheduledExecutor(); schedulerLogExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("sql-log"));
} }
@PreDestroy @PreDestroy

3
dao/src/main/java/org/thingsboard/server/dao/sql/TbSqlBlockingQueue.java

@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -49,7 +50,7 @@ public class TbSqlBlockingQueue<E> implements TbSqlQueue<E> {
@Override @Override
public void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction) { public void init(ScheduledLogExecutorComponent logExecutor, Consumer<List<E>> saveFunction) {
this.logExecutor = logExecutor; this.logExecutor = logExecutor;
executor = Executors.newSingleThreadExecutor(); executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("sql-queue-" + params.getLogName().toLowerCase()));
executor.submit(() -> { executor.submit(() -> {
String logName = params.getLogName(); String logName = params.getLogName();
int batchSize = params.getBatchSize(); int batchSize = params.getBatchSize();

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

@ -143,31 +143,32 @@ public abstract class AttributeKvInsertRepository {
int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() {
@Override @Override
public void setValues(PreparedStatement ps, int i) throws SQLException { public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, replaceNullChars(entities.get(i).getStrValue())); AttributeKvEntity kvEntity = entities.get(i);
ps.setString(1, replaceNullChars(kvEntity.getStrValue()));
if (entities.get(i).getLongValue() != null) { if (kvEntity.getLongValue() != null) {
ps.setLong(2, entities.get(i).getLongValue()); ps.setLong(2, kvEntity.getLongValue());
} else { } else {
ps.setNull(2, Types.BIGINT); ps.setNull(2, Types.BIGINT);
} }
if (entities.get(i).getDoubleValue() != null) { if (kvEntity.getDoubleValue() != null) {
ps.setDouble(3, entities.get(i).getDoubleValue()); ps.setDouble(3, kvEntity.getDoubleValue());
} else { } else {
ps.setNull(3, Types.DOUBLE); ps.setNull(3, Types.DOUBLE);
} }
if (entities.get(i).getBooleanValue() != null) { if (kvEntity.getBooleanValue() != null) {
ps.setBoolean(4, entities.get(i).getBooleanValue()); ps.setBoolean(4, kvEntity.getBooleanValue());
} else { } else {
ps.setNull(4, Types.BOOLEAN); ps.setNull(4, Types.BOOLEAN);
} }
ps.setLong(5, entities.get(i).getLastUpdateTs()); ps.setLong(5, kvEntity.getLastUpdateTs());
ps.setString(6, entities.get(i).getId().getEntityType().name()); ps.setString(6, kvEntity.getId().getEntityType().name());
ps.setString(7, entities.get(i).getId().getEntityId()); ps.setString(7, kvEntity.getId().getEntityId());
ps.setString(8, entities.get(i).getId().getAttributeType()); ps.setString(8, kvEntity.getId().getAttributeType());
ps.setString(9, entities.get(i).getId().getAttributeKey()); ps.setString(9, kvEntity.getId().getAttributeKey());
} }
@Override @Override
@ -193,39 +194,40 @@ public abstract class AttributeKvInsertRepository {
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override @Override
public void setValues(PreparedStatement ps, int i) throws SQLException { public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, insertEntities.get(i).getId().getEntityType().name()); AttributeKvEntity kvEntity = insertEntities.get(i);
ps.setString(2, insertEntities.get(i).getId().getEntityId()); ps.setString(1, kvEntity.getId().getEntityType().name());
ps.setString(3, insertEntities.get(i).getId().getAttributeType()); ps.setString(2, kvEntity.getId().getEntityId());
ps.setString(4, insertEntities.get(i).getId().getAttributeKey()); ps.setString(3, kvEntity.getId().getAttributeType());
ps.setString(5, replaceNullChars(insertEntities.get(i).getStrValue())); ps.setString(4, kvEntity.getId().getAttributeKey());
ps.setString(10, replaceNullChars(insertEntities.get(i).getStrValue())); ps.setString(5, replaceNullChars(kvEntity.getStrValue()));
ps.setString(10, replaceNullChars(kvEntity.getStrValue()));
if (insertEntities.get(i).getLongValue() != null) {
ps.setLong(6, insertEntities.get(i).getLongValue()); if (kvEntity.getLongValue() != null) {
ps.setLong(11, insertEntities.get(i).getLongValue()); ps.setLong(6, kvEntity.getLongValue());
ps.setLong(11, kvEntity.getLongValue());
} else { } else {
ps.setNull(6, Types.BIGINT); ps.setNull(6, Types.BIGINT);
ps.setNull(11, Types.BIGINT); ps.setNull(11, Types.BIGINT);
} }
if (insertEntities.get(i).getDoubleValue() != null) { if (kvEntity.getDoubleValue() != null) {
ps.setDouble(7, insertEntities.get(i).getDoubleValue()); ps.setDouble(7, kvEntity.getDoubleValue());
ps.setDouble(12, insertEntities.get(i).getDoubleValue()); ps.setDouble(12, kvEntity.getDoubleValue());
} else { } else {
ps.setNull(7, Types.DOUBLE); ps.setNull(7, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE); ps.setNull(12, Types.DOUBLE);
} }
if (insertEntities.get(i).getBooleanValue() != null) { if (kvEntity.getBooleanValue() != null) {
ps.setBoolean(8, insertEntities.get(i).getBooleanValue()); ps.setBoolean(8, kvEntity.getBooleanValue());
ps.setBoolean(13, insertEntities.get(i).getBooleanValue()); ps.setBoolean(13, kvEntity.getBooleanValue());
} else { } else {
ps.setNull(8, Types.BOOLEAN); ps.setNull(8, Types.BOOLEAN);
ps.setNull(13, Types.BOOLEAN); ps.setNull(13, Types.BOOLEAN);
} }
ps.setLong(9, insertEntities.get(i).getLastUpdateTs()); ps.setLong(9, kvEntity.getLastUpdateTs());
ps.setLong(14, insertEntities.get(i).getLastUpdateTs()); ps.setLong(14, kvEntity.getLastUpdateTs());
} }
@Override @Override

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

@ -44,44 +44,6 @@ public abstract class AbstractSqlTimeseriesDao extends JpaAbstractDaoListeningEx
private static final String DESC_ORDER = "DESC"; private static final String DESC_ORDER = "DESC";
@Value("${sql.ts_inserts_executor_type}")
private String insertExecutorType;
@Value("${sql.ts_inserts_fixed_thread_pool_size}")
private int insertFixedThreadPoolSize;
@Value("${spring.datasource.hikari.maximumPoolSize}")
private int maximumPoolSize;
protected ListeningExecutorService insertService;
@PostConstruct
void init() {
Optional<TsInsertExecutorType> executorTypeOptional = TsInsertExecutorType.parse(insertExecutorType);
TsInsertExecutorType executorType;
executorType = executorTypeOptional.orElse(TsInsertExecutorType.FIXED);
switch (executorType) {
case SINGLE:
insertService = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor());
break;
case FIXED:
case CACHED:
int poolSize = insertFixedThreadPoolSize;
if (poolSize <= 0) {
poolSize = maximumPoolSize * 4;
}
insertService = MoreExecutors.listeningDecorator(Executors.newWorkStealingPool(poolSize));
break;
}
}
@PreDestroy
void preDestroy() {
if (insertService != null) {
insertService.shutdown();
}
}
protected ListenableFuture<List<TsKvEntry>> processFindAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) { protected ListenableFuture<List<TsKvEntry>> processFindAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries List<ListenableFuture<List<TsKvEntry>>> futures = queries
.stream() .stream()

33
dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleInsertRepository.java

@ -57,34 +57,35 @@ public class TimescaleInsertRepository extends AbstractTimeseriesInsertRepositor
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override @Override
public void setValues(PreparedStatement ps, int i) throws SQLException { public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, entities.get(i).getTenantId()); TimescaleTsKvEntity tsKvEntity = entities.get(i);
ps.setString(2, entities.get(i).getEntityId()); ps.setString(1, tsKvEntity.getTenantId());
ps.setString(3, entities.get(i).getKey()); ps.setString(2, tsKvEntity.getEntityId());
ps.setLong(4, entities.get(i).getTs()); ps.setString(3, tsKvEntity.getKey());
ps.setLong(4, tsKvEntity.getTs());
if (entities.get(i).getBooleanValue() != null) {
ps.setBoolean(5, entities.get(i).getBooleanValue()); if (tsKvEntity.getBooleanValue() != null) {
ps.setBoolean(9, entities.get(i).getBooleanValue()); ps.setBoolean(5, tsKvEntity.getBooleanValue());
ps.setBoolean(9, tsKvEntity.getBooleanValue());
} else { } else {
ps.setNull(5, Types.BOOLEAN); ps.setNull(5, Types.BOOLEAN);
ps.setNull(9, Types.BOOLEAN); ps.setNull(9, Types.BOOLEAN);
} }
ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); ps.setString(6, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); ps.setString(10, replaceNullChars(tsKvEntity.getStrValue()));
if (entities.get(i).getLongValue() != null) { if (tsKvEntity.getLongValue() != null) {
ps.setLong(7, entities.get(i).getLongValue()); ps.setLong(7, tsKvEntity.getLongValue());
ps.setLong(11, entities.get(i).getLongValue()); ps.setLong(11, tsKvEntity.getLongValue());
} else { } else {
ps.setNull(7, Types.BIGINT); ps.setNull(7, Types.BIGINT);
ps.setNull(11, Types.BIGINT); ps.setNull(11, Types.BIGINT);
} }
if (entities.get(i).getDoubleValue() != null) { if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(8, entities.get(i).getDoubleValue()); ps.setDouble(8, tsKvEntity.getDoubleValue());
ps.setDouble(12, entities.get(i).getDoubleValue()); ps.setDouble(12, tsKvEntity.getDoubleValue());
} else { } else {
ps.setNull(8, Types.DOUBLE); ps.setNull(8, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE); ps.setNull(12, Types.DOUBLE);

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

@ -170,12 +170,12 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements
@Override @Override
public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) {
return insertService.submit(() -> null); return Futures.immediateFuture(null);
} }
@Override @Override
public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
return insertService.submit(() -> null); return Futures.immediateFuture(null);
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/JpaTimeseriesDao.java

@ -334,7 +334,7 @@ public class JpaTimeseriesDao extends AbstractSqlTimeseriesDao implements Timese
@Override @Override
public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { public ListenableFuture<Void> savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) {
return insertService.submit(() -> null); return Futures.immediateFuture(null);
} }
@Override @Override

58
dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlLatestInsertRepository.java

@ -65,31 +65,32 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository {
int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() {
@Override @Override
public void setValues(PreparedStatement ps, int i) throws SQLException { public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, entities.get(i).getTs()); TsKvLatestEntity tsKvLatestEntity = entities.get(i);
ps.setLong(1, tsKvLatestEntity.getTs());
if (entities.get(i).getBooleanValue() != null) { if (tsKvLatestEntity.getBooleanValue() != null) {
ps.setBoolean(2, entities.get(i).getBooleanValue()); ps.setBoolean(2, tsKvLatestEntity.getBooleanValue());
} else { } else {
ps.setNull(2, Types.BOOLEAN); ps.setNull(2, Types.BOOLEAN);
} }
ps.setString(3, replaceNullChars(entities.get(i).getStrValue())); ps.setString(3, replaceNullChars(tsKvLatestEntity.getStrValue()));
if (entities.get(i).getLongValue() != null) { if (tsKvLatestEntity.getLongValue() != null) {
ps.setLong(4, entities.get(i).getLongValue()); ps.setLong(4, tsKvLatestEntity.getLongValue());
} else { } else {
ps.setNull(4, Types.BIGINT); ps.setNull(4, Types.BIGINT);
} }
if (entities.get(i).getDoubleValue() != null) { if (tsKvLatestEntity.getDoubleValue() != null) {
ps.setDouble(5, entities.get(i).getDoubleValue()); ps.setDouble(5, tsKvLatestEntity.getDoubleValue());
} else { } else {
ps.setNull(5, Types.DOUBLE); ps.setNull(5, Types.DOUBLE);
} }
ps.setString(6, entities.get(i).getEntityType().name()); ps.setString(6, tsKvLatestEntity.getEntityType().name());
ps.setString(7, entities.get(i).getEntityId()); ps.setString(7, tsKvLatestEntity.getEntityId());
ps.setString(8, entities.get(i).getKey()); ps.setString(8, tsKvLatestEntity.getKey());
} }
@Override @Override
@ -115,35 +116,36 @@ public class PsqlLatestInsertRepository extends AbstractLatestInsertRepository {
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override @Override
public void setValues(PreparedStatement ps, int i) throws SQLException { public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, insertEntities.get(i).getEntityType().name()); TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i);
ps.setString(2, insertEntities.get(i).getEntityId()); ps.setString(1, tsKvLatestEntity.getEntityType().name());
ps.setString(3, insertEntities.get(i).getKey()); ps.setString(2, tsKvLatestEntity.getEntityId());
ps.setLong(4, insertEntities.get(i).getTs()); ps.setString(3, tsKvLatestEntity.getKey());
ps.setLong(9, insertEntities.get(i).getTs()); ps.setLong(4, tsKvLatestEntity.getTs());
ps.setLong(9, tsKvLatestEntity.getTs());
if (insertEntities.get(i).getBooleanValue() != null) {
ps.setBoolean(5, insertEntities.get(i).getBooleanValue()); if (tsKvLatestEntity.getBooleanValue() != null) {
ps.setBoolean(10, insertEntities.get(i).getBooleanValue()); ps.setBoolean(5, tsKvLatestEntity.getBooleanValue());
ps.setBoolean(10, tsKvLatestEntity.getBooleanValue());
} else { } else {
ps.setNull(5, Types.BOOLEAN); ps.setNull(5, Types.BOOLEAN);
ps.setNull(10, Types.BOOLEAN); ps.setNull(10, Types.BOOLEAN);
} }
ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); ps.setString(6, replaceNullChars(tsKvLatestEntity.getStrValue()));
ps.setString(11, replaceNullChars(entities.get(i).getStrValue())); ps.setString(11, replaceNullChars(tsKvLatestEntity.getStrValue()));
if (insertEntities.get(i).getLongValue() != null) { if (tsKvLatestEntity.getLongValue() != null) {
ps.setLong(7, insertEntities.get(i).getLongValue()); ps.setLong(7, tsKvLatestEntity.getLongValue());
ps.setLong(12, insertEntities.get(i).getLongValue()); ps.setLong(12, tsKvLatestEntity.getLongValue());
} else { } else {
ps.setNull(7, Types.BIGINT); ps.setNull(7, Types.BIGINT);
ps.setNull(12, Types.BIGINT); ps.setNull(12, Types.BIGINT);
} }
if (insertEntities.get(i).getDoubleValue() != null) { if (tsKvLatestEntity.getDoubleValue() != null) {
ps.setDouble(8, insertEntities.get(i).getDoubleValue()); ps.setDouble(8, tsKvLatestEntity.getDoubleValue());
ps.setDouble(13, insertEntities.get(i).getDoubleValue()); ps.setDouble(13, tsKvLatestEntity.getDoubleValue());
} else { } else {
ps.setNull(8, Types.DOUBLE); ps.setNull(8, Types.DOUBLE);
ps.setNull(13, Types.DOUBLE); ps.setNull(13, Types.DOUBLE);

33
dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/PsqlTimeseriesInsertRepository.java

@ -99,34 +99,35 @@ public class PsqlTimeseriesInsertRepository extends AbstractTimeseriesInsertRepo
jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() {
@Override @Override
public void setValues(PreparedStatement ps, int i) throws SQLException { public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, entities.get(i).getEntityType().name()); TsKvEntity tsKvEntity = entities.get(i);
ps.setString(2, entities.get(i).getEntityId()); ps.setString(1, tsKvEntity.getEntityType().name());
ps.setString(3, entities.get(i).getKey()); ps.setString(2, tsKvEntity.getEntityId());
ps.setLong(4, entities.get(i).getTs()); ps.setString(3, tsKvEntity.getKey());
ps.setLong(4, tsKvEntity.getTs());
if (entities.get(i).getBooleanValue() != null) {
ps.setBoolean(5, entities.get(i).getBooleanValue()); if (tsKvEntity.getBooleanValue() != null) {
ps.setBoolean(9, entities.get(i).getBooleanValue()); ps.setBoolean(5, tsKvEntity.getBooleanValue());
ps.setBoolean(9, tsKvEntity.getBooleanValue());
} else { } else {
ps.setNull(5, Types.BOOLEAN); ps.setNull(5, Types.BOOLEAN);
ps.setNull(9, Types.BOOLEAN); ps.setNull(9, Types.BOOLEAN);
} }
ps.setString(6, replaceNullChars(entities.get(i).getStrValue())); ps.setString(6, replaceNullChars(tsKvEntity.getStrValue()));
ps.setString(10, replaceNullChars(entities.get(i).getStrValue())); ps.setString(10, replaceNullChars(tsKvEntity.getStrValue()));
if (entities.get(i).getLongValue() != null) { if (tsKvEntity.getLongValue() != null) {
ps.setLong(7, entities.get(i).getLongValue()); ps.setLong(7, tsKvEntity.getLongValue());
ps.setLong(11, entities.get(i).getLongValue()); ps.setLong(11, tsKvEntity.getLongValue());
} else { } else {
ps.setNull(7, Types.BIGINT); ps.setNull(7, Types.BIGINT);
ps.setNull(11, Types.BIGINT); ps.setNull(11, Types.BIGINT);
} }
if (entities.get(i).getDoubleValue() != null) { if (tsKvEntity.getDoubleValue() != null) {
ps.setDouble(8, entities.get(i).getDoubleValue()); ps.setDouble(8, tsKvEntity.getDoubleValue());
ps.setDouble(12, entities.get(i).getDoubleValue()); ps.setDouble(12, tsKvEntity.getDoubleValue());
} else { } else {
ps.setNull(8, Types.DOUBLE); ps.setNull(8, Types.DOUBLE);
ps.setNull(12, Types.DOUBLE); ps.setNull(12, Types.DOUBLE);

5
dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java

@ -21,6 +21,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.CassandraStatementTask;
@ -67,9 +68,9 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
this.concurrencyLimit = concurrencyLimit; this.concurrencyLimit = concurrencyLimit;
this.printQueriesFreq = printQueriesFreq; this.printQueriesFreq = printQueriesFreq;
this.queue = new LinkedBlockingDeque<>(queueLimit); this.queue = new LinkedBlockingDeque<>(queueLimit);
this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads); this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-dispatcher"));
this.callbackExecutor = Executors.newWorkStealingPool(callbackThreads); this.callbackExecutor = Executors.newWorkStealingPool(callbackThreads);
this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(); this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout"));
this.perTenantLimitsEnabled = perTenantLimitsEnabled; this.perTenantLimitsEnabled = perTenantLimitsEnabled;
this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; this.perTenantLimitsConfiguration = perTenantLimitsConfiguration;
for (int i = 0; i < dispatcherThreads; i++) { for (int i = 0; i < dispatcherThreads; i++) {

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

@ -306,6 +306,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.alarm (
clear_ts bigint, clear_ts bigint,
details text, details text,
propagate boolean, propagate boolean,
propagate_relation_types text,
PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id) PRIMARY KEY ((tenant_id, originator_id, originator_type), type, id)
) WITH CLUSTERING ORDER BY ( type ASC, id DESC); ) WITH CLUSTERING ORDER BY ( type ASC, id DESC);

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

@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS alarm (
start_ts bigint, start_ts bigint,
status varchar(255), status varchar(255),
tenant_id varchar(31), tenant_id varchar(31),
propagate_relation_types varchar,
type varchar(255) type varchar(255)
); );

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

@ -21,7 +21,7 @@
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

23
msa/js-executor/api/jsInvokeMessageProcessor.js

@ -27,11 +27,13 @@ const config = require('config'),
const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_frequency')); const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_frequency'));
const useSandbox = config.get('script.use_sandbox') === 'true'; const useSandbox = config.get('script.use_sandbox') === 'true';
const maxActiveScripts = Number(config.get('script.max_active_scripts'));
function JsInvokeMessageProcessor(producer) { function JsInvokeMessageProcessor(producer) {
this.producer = producer; this.producer = producer;
this.executor = new JsExecutor(useSandbox); this.executor = new JsExecutor(useSandbox);
this.scriptMap = {}; this.scriptMap = {};
this.scriptIds = [];
this.executedScriptsCounter = 0; this.executedScriptsCounter = 0;
} }
@ -70,7 +72,7 @@ JsInvokeMessageProcessor.prototype.processCompileRequest = function(requestId, r
this.executor.compileScript(compileRequest.scriptBody).then( this.executor.compileScript(compileRequest.scriptBody).then(
(script) => { (script) => {
this.scriptMap[scriptId] = script; this.cacheScript(scriptId, script);
var compileResponse = createCompileResponse(scriptId, true); var compileResponse = createCompileResponse(scriptId, true);
logger.debug('[%s] Sending success compile response, scriptId: [%s]', requestId, scriptId); logger.debug('[%s] Sending success compile response, scriptId: [%s]', requestId, scriptId);
this.sendResponse(requestId, responseTopic, scriptId, compileResponse); this.sendResponse(requestId, responseTopic, scriptId, compileResponse);
@ -126,6 +128,10 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function(requestId, r
var scriptId = getScriptId(releaseRequest); var scriptId = getScriptId(releaseRequest);
logger.debug('[%s] Processing release request, scriptId: [%s]', requestId, scriptId); logger.debug('[%s] Processing release request, scriptId: [%s]', requestId, scriptId);
if (this.scriptMap[scriptId]) { if (this.scriptMap[scriptId]) {
var index = this.scriptIds.indexOf(scriptId);
if (index > -1) {
this.scriptIds.splice(index, 1);
}
delete this.scriptMap[scriptId]; delete this.scriptMap[scriptId];
} }
var releaseResponse = createReleaseResponse(scriptId, true); var releaseResponse = createReleaseResponse(scriptId, true);
@ -165,7 +171,7 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip
} else { } else {
self.executor.compileScript(scriptBody).then( self.executor.compileScript(scriptBody).then(
(script) => { (script) => {
self.scriptMap[scriptId] = script; self.cacheScript(scriptId, script);
resolve(script); resolve(script);
}, },
(err) => { (err) => {
@ -176,6 +182,19 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip
}); });
} }
JsInvokeMessageProcessor.prototype.cacheScript = function(scriptId, script) {
if (!this.scriptMap[scriptId]) {
this.scriptIds.push(scriptId);
while (this.scriptIds.length > maxActiveScripts) {
logger.info('Active scripts count [%s] exceeds maximum limit [%s]', this.scriptIds.length, maxActiveScripts);
const prevScriptId = this.scriptIds.shift();
logger.info('Removing active script with id [%s]', prevScriptId);
delete this.scriptMap[prevScriptId];
}
}
this.scriptMap[scriptId] = script;
}
function createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse) { function createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse) {
const requestIdBits = Utils.UUIDToBits(requestId); const requestIdBits = Utils.UUIDToBits(requestId);
return { return {

1
msa/js-executor/config/custom-environment-variables.yml

@ -27,3 +27,4 @@ logger:
script: script:
use_sandbox: "SCRIPT_USE_SANDBOX" use_sandbox: "SCRIPT_USE_SANDBOX"
script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY"
max_active_scripts: "MAX_ACTIVE_SCRIPTS"

1
msa/js-executor/config/default.yml

@ -28,3 +28,4 @@ logger:
script: script:
use_sandbox: "true" use_sandbox: "true"
script_body_trace_frequency: "1000" script_body_trace_frequency: "1000"
max_active_scripts: "1000"

2
msa/js-executor/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-js-executor", "name": "thingsboard-js-executor",
"private": true, "private": true,
"version": "2.4.2", "version": "2.5.0",
"description": "ThingsBoard JavaScript Executor Microservice", "description": "ThingsBoard JavaScript Executor Microservice",
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",

2
msa/js-executor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>

2
msa/tb-node/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/tb/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa.transport</groupId> <groupId>org.thingsboard.msa.transport</groupId>

2
msa/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

2
msa/web-ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard-web-ui", "name": "thingsboard-web-ui",
"private": true, "private": true,
"version": "2.4.2", "version": "2.5.0",
"description": "ThingsBoard Web UI Microservice", "description": "ThingsBoard Web UI Microservice",
"main": "server.js", "main": "server.js",
"bin": "server.js", "bin": "server.js",

2
msa/web-ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>msa</artifactId> <artifactId>msa</artifactId>
</parent> </parent>
<groupId>org.thingsboard.msa</groupId> <groupId>org.thingsboard.msa</groupId>

4
netty-mqtt/pom.xml

@ -19,12 +19,12 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>netty-mqtt</artifactId> <artifactId>netty-mqtt</artifactId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>Netty MQTT Client</name> <name>Netty MQTT Client</name>

11
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<name>Thingsboard</name> <name>Thingsboard</name>
@ -115,6 +115,15 @@
<activeByDefault>true</activeByDefault> <activeByDefault>true</activeByDefault>
</activation> </activation>
</profile> </profile>
<!-- download sources under target/dependencies -->
<!-- mvn package -Pdownload-dependencies -Dclassifier=sources dependency:copy-dependencies -->
<profile>
<id>download-dependencies</id>
<properties>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</properties>
</profile>
</profiles> </profiles>
<build> <build>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>

2
rule-engine/rule-engine-api/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

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

@ -38,6 +38,8 @@ public interface ScriptEngine {
JsonNode executeJson(TbMsg msg) throws ScriptException; JsonNode executeJson(TbMsg msg) throws ScriptException;
ListenableFuture<JsonNode> executeJsonAsync(TbMsg msg) throws ScriptException;
String executeToString(TbMsg msg) throws ScriptException; String executeToString(TbMsg msg) throws ScriptException;
void destroy(); void destroy();

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId> <artifactId>rule-engine</artifactId>
</parent> </parent>
<groupId>org.thingsboard.rule-engine</groupId> <groupId>org.thingsboard.rule-engine</groupId>

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

@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.action;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.ScriptEngine;
@ -27,6 +28,9 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
import static org.thingsboard.common.util.DonAsynchron.withCallback; import static org.thingsboard.common.util.DonAsynchron.withCallback;
@ -67,21 +71,24 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared");
} }
}, },
t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); t -> ctx.tellFailure(msg, t)
, ctx.getDbCallbackExecutor());
} }
protected abstract ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg); protected abstract ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg);
protected ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) { protected ListenableFuture<JsonNode> buildAlarmDetails(TbContext ctx, TbMsg msg, JsonNode previousDetails) {
return ctx.getJsExecutor().executeAsync(() -> { try {
TbMsg dummyMsg = msg; TbMsg dummyMsg = msg;
if (previousDetails != null) { if (previousDetails != null) {
TbMsgMetaData metaData = msg.getMetaData().copy(); TbMsgMetaData metaData = msg.getMetaData().copy();
metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails)); metaData.putValue(PREV_ALARM_DETAILS, mapper.writeValueAsString(previousDetails));
dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData()); dummyMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), metaData, msg.getData());
} }
return buildDetailsJsEngine.executeJson(dummyMsg); return buildDetailsJsEngine.executeJsonAsync(dummyMsg);
}); } catch (Exception e) {
return Futures.immediateFailedFuture(e);
}
} }
private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) { private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) {

11
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java

@ -21,11 +21,11 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException; import java.io.IOException;
import java.util.List;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -53,10 +54,13 @@ import java.io.IOException;
public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> { public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
private static ObjectMapper mapper = new ObjectMapper(); private static ObjectMapper mapper = new ObjectMapper();
private List<String> relationTypes;
@Override @Override
protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
return TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class); TbCreateAlarmNodeConfiguration nodeConfiguration = TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class);
relationTypes = nodeConfiguration.getRelationTypes();
return nodeConfiguration;
} }
@Override @Override
@ -125,9 +129,11 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
if (msgAlarm != null) { if (msgAlarm != null) {
existingAlarm.setSeverity(msgAlarm.getSeverity()); existingAlarm.setSeverity(msgAlarm.getSeverity());
existingAlarm.setPropagate(msgAlarm.isPropagate()); existingAlarm.setPropagate(msgAlarm.isPropagate());
existingAlarm.setPropagateRelationTypes(msgAlarm.getPropagateRelationTypes());
} else { } else {
existingAlarm.setSeverity(config.getSeverity()); existingAlarm.setSeverity(config.getSeverity());
existingAlarm.setPropagate(config.isPropagate()); existingAlarm.setPropagate(config.isPropagate());
existingAlarm.setPropagateRelationTypes(relationTypes);
} }
existingAlarm.setDetails(details); existingAlarm.setDetails(details);
existingAlarm.setEndTs(System.currentTimeMillis()); existingAlarm.setEndTs(System.currentTimeMillis());
@ -145,6 +151,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
.severity(config.getSeverity()) .severity(config.getSeverity())
.propagate(config.isPropagate()) .propagate(config.isPropagate())
.type(TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData())) .type(TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData()))
.propagateRelationTypes(relationTypes)
//todo-vp: alarm date should be taken from Message or current Time should be used? //todo-vp: alarm date should be taken from Message or current Time should be used?
// .startTs(System.currentTimeMillis()) // .startTs(System.currentTimeMillis())
// .endTs(System.currentTimeMillis()) // .endTs(System.currentTimeMillis())

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

@ -19,6 +19,9 @@ import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import java.util.Collections;
import java.util.List;
@Data @Data
public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbCreateAlarmNodeConfiguration> { public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfiguration implements NodeConfiguration<TbCreateAlarmNodeConfiguration> {
@ -26,18 +29,21 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
private boolean propagate; private boolean propagate;
private boolean useMessageAlarmData; private boolean useMessageAlarmData;
private List<String> relationTypes;
@Override @Override
public TbCreateAlarmNodeConfiguration defaultConfiguration() { public TbCreateAlarmNodeConfiguration defaultConfiguration() {
TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration();
configuration.setAlarmDetailsBuildJs("var details = {};\n" + configuration.setAlarmDetailsBuildJs("var details = {};\n" +
"if (metadata.prevAlarmDetails) {\n" + "if (metadata.prevAlarmDetails) {\n" +
" details = JSON.parse(metadata.prevAlarmDetails);\n" + " details = JSON.parse(metadata.prevAlarmDetails);\n" +
"}\n"+ "}\n" +
"return details;"); "return details;");
configuration.setAlarmType("General Alarm"); configuration.setAlarmType("General Alarm");
configuration.setSeverity(AlarmSeverity.CRITICAL); configuration.setSeverity(AlarmSeverity.CRITICAL);
configuration.setPropagate(false); configuration.setPropagate(false);
configuration.setUseMessageAlarmData(false); configuration.setUseMessageAlarmData(false);
configuration.setRelationTypes(Collections.emptyList());
return configuration; return configuration;
} }
} }

10
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

38
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java

@ -26,6 +26,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer; import org.mockito.stubbing.Answer;
import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.common.util.ListeningExecutor;
@ -60,8 +61,6 @@ public class TbAlarmNodeTest {
@Mock @Mock
private TbContext ctx; private TbContext ctx;
@Mock @Mock
private ListeningExecutor executor;
@Mock
private AlarmService alarmService; private AlarmService alarmService;
@Mock @Mock
@ -102,7 +101,7 @@ public class TbAlarmNodeTest {
metaData.putValue("key", "value"); metaData.putValue("key", "value");
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
when(detailsJs.executeJson(msg)).thenReturn(null); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
@ -136,8 +135,6 @@ public class TbAlarmNodeTest {
.build(); .build();
assertEquals(expectedAlarm, actualAlarm); assertEquals(expectedAlarm, actualAlarm);
verify(executor, times(1)).executeAsync(any(Callable.class));
} }
@Test @Test
@ -146,7 +143,7 @@ public class TbAlarmNodeTest {
metaData.putValue("key", "value"); metaData.putValue("key", "value");
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
when(detailsJs.executeJson(msg)).thenThrow(new NotImplementedException("message")); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFailedFuture(new NotImplementedException("message")));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
@ -154,7 +151,6 @@ public class TbAlarmNodeTest {
verifyError(msg, "message", NotImplementedException.class); verifyError(msg, "message", NotImplementedException.class);
verify(ctx).createJsScriptEngine("DETAILS"); verify(ctx).createJsScriptEngine("DETAILS");
verify(ctx, times(1)).getJsExecutor();
verify(ctx).getAlarmService(); verify(ctx).getAlarmService();
verify(ctx, times(3)).getDbCallbackExecutor(); verify(ctx, times(3)).getDbCallbackExecutor();
verify(ctx).logJsEvalRequest(); verify(ctx).logJsEvalRequest();
@ -172,7 +168,7 @@ public class TbAlarmNodeTest {
Alarm clearedAlarm = Alarm.builder().status(CLEARED_ACK).build(); Alarm clearedAlarm = Alarm.builder().status(CLEARED_ACK).build();
when(detailsJs.executeJson(msg)).thenReturn(null); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(clearedAlarm)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(clearedAlarm));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class));
@ -207,8 +203,6 @@ public class TbAlarmNodeTest {
.build(); .build();
assertEquals(expectedAlarm, actualAlarm); assertEquals(expectedAlarm, actualAlarm);
verify(executor, times(1)).executeAsync(any(Callable.class));
} }
@Test @Test
@ -220,7 +214,7 @@ public class TbAlarmNodeTest {
long oldEndDate = System.currentTimeMillis(); long oldEndDate = System.currentTimeMillis();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build(); Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
when(detailsJs.executeJson(msg)).thenReturn(null); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm));
doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm); doAnswer((Answer<Alarm>) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(activeAlarm);
@ -256,8 +250,6 @@ public class TbAlarmNodeTest {
.build(); .build();
assertEquals(expectedAlarm, actualAlarm); assertEquals(expectedAlarm, actualAlarm);
verify(executor, times(1)).executeAsync(any(Callable.class));
} }
@Test @Test
@ -269,7 +261,7 @@ public class TbAlarmNodeTest {
long oldEndDate = System.currentTimeMillis(); long oldEndDate = System.currentTimeMillis();
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build(); Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build();
// when(detailsJs.executeJson(msg)).thenReturn(null); when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null));
when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm)); when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(activeAlarm));
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), org.mockito.Mockito.any(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true)); when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), org.mockito.Mockito.any(JsonNode.class), anyLong())).thenReturn(Futures.immediateFuture(true));
when(alarmService.findAlarmByIdAsync(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()))).thenReturn(Futures.immediateFuture(activeAlarm)); when(alarmService.findAlarmByIdAsync(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()))).thenReturn(Futures.immediateFuture(activeAlarm));
@ -320,12 +312,9 @@ public class TbAlarmNodeTest {
when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getJsExecutor()).thenReturn(executor);
when(ctx.getAlarmService()).thenReturn(alarmService); when(ctx.getAlarmService()).thenReturn(alarmService);
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor);
mockJsExecutor();
node = new TbCreateAlarmNode(); node = new TbCreateAlarmNode();
node.init(ctx, nodeConfiguration); node.init(ctx, nodeConfiguration);
} catch (TbNodeException ex) { } catch (TbNodeException ex) {
@ -344,12 +333,9 @@ public class TbAlarmNodeTest {
when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs);
when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getTenantId()).thenReturn(tenantId);
when(ctx.getJsExecutor()).thenReturn(executor);
when(ctx.getAlarmService()).thenReturn(alarmService); when(ctx.getAlarmService()).thenReturn(alarmService);
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor);
mockJsExecutor();
node = new TbClearAlarmNode(); node = new TbClearAlarmNode();
node.init(ctx, nodeConfiguration); node.init(ctx, nodeConfiguration);
} catch (TbNodeException ex) { } catch (TbNodeException ex) {
@ -357,18 +343,6 @@ public class TbAlarmNodeTest {
} }
} }
private void mockJsExecutor() {
when(ctx.getJsExecutor()).thenReturn(executor);
doAnswer((Answer<ListenableFuture<Boolean>>) invocationOnMock -> {
try {
Callable task = (Callable) (invocationOnMock.getArguments())[0];
return Futures.immediateFuture((Boolean) task.call());
} catch (Throwable th) {
return Futures.immediateFailedFuture(th);
}
}).when(executor).executeAsync(any(Callable.class));
}
private void verifyError(TbMsg msg, String message, Class expectedClass) { private void verifyError(TbMsg msg, String message, Class expectedClass) {
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
verify(ctx).tellFailure(same(msg), captor.capture()); verify(ctx).tellFailure(same(msg), captor.capture());

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>tools</artifactId> <artifactId>tools</artifactId>

29
tools/src/main/java/org/thingsboard/client/tools/RestClient.java

@ -225,13 +225,6 @@ public class RestClient implements ClientHttpRequestInterceptor {
return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody();
} }
public Device createDevice(String name, String type) {
Device device = new Device();
device.setName(name);
device.setType(type);
return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody();
}
public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) { public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) {
DeviceCredentials deviceCredentials = getCredentials(deviceId); DeviceCredentials deviceCredentials = getCredentials(deviceId);
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
@ -239,10 +232,30 @@ public class RestClient implements ClientHttpRequestInterceptor {
return saveDeviceCredentials(deviceCredentials); return saveDeviceCredentials(deviceCredentials);
} }
public Device createDevice(String name, String type) {
Device device = new Device();
device.setName(name);
device.setType(type);
return doCreateDevice(device, null);
}
public Device createDevice(Device device) { public Device createDevice(Device device) {
return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); return doCreateDevice(device, null);
} }
public Device createDevice(Device device, String accessToken) {
return doCreateDevice(device, accessToken);
}
private Device doCreateDevice(Device device, String accessToken) {
Map<String, String> params = new HashMap<>();
String deviceCreationUrl = "/api/device";
if (!StringUtils.isEmpty(accessToken)) {
deviceCreationUrl = deviceCreationUrl + "?accessToken={accessToken}";
params.put("accessToken", accessToken);
}
return restTemplate.postForEntity(baseURL + deviceCreationUrl, device, Device.class, params).getBody();
}
public Asset createAsset(Asset asset) { public Asset createAsset(Asset asset) {
return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody();
} }

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.transport</groupId> <groupId>org.thingsboard.transport</groupId>

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>

2
ui/package.json

@ -1,7 +1,7 @@
{ {
"name": "thingsboard", "name": "thingsboard",
"private": true, "private": true,
"version": "2.4.2", "version": "2.5.0",
"description": "ThingsBoard UI", "description": "ThingsBoard UI",
"licenses": [ "licenses": [
{ {

2
ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.4.2-SNAPSHOT</version> <version>2.5.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>

37
ui/src/app/api/alarm.service.js

@ -183,7 +183,7 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
return deferred.promise; return deferred.promise;
} }
function fetchAlarms(alarmsQuery, pageLink, deferred, alarmsList) { function fetchAlarms(alarmsQuery, pageLink, deferred, leftToLoad, alarmsList) {
getAlarms(alarmsQuery.entityType, alarmsQuery.entityId, getAlarms(alarmsQuery.entityType, alarmsQuery.entityId,
pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus, pageLink, alarmsQuery.alarmSearchStatus, alarmsQuery.alarmStatus,
alarmsQuery.fetchOriginator, false, {ignoreLoading: true}).then( alarmsQuery.fetchOriginator, false, {ignoreLoading: true}).then(
@ -192,8 +192,19 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
alarmsList = []; alarmsList = [];
} }
alarmsList = alarmsList.concat(alarms.data); alarmsList = alarmsList.concat(alarms.data);
if (angular.isDefined(leftToLoad)) {
leftToLoad -= pageLink.limit;
if (leftToLoad === 0) {
alarmsList = $filter('orderBy')(alarmsList, ['-createdTime']);
deferred.resolve(alarmsList);
return;
}
if (leftToLoad < pageLink.limit) {
alarms.nextPageLink.limit = leftToLoad;
}
}
if (alarms.hasNext && !alarmsQuery.limit) { if (alarms.hasNext && !alarmsQuery.limit) {
fetchAlarms(alarmsQuery, alarms.nextPageLink, deferred, alarmsList); fetchAlarms(alarmsQuery, alarms.nextPageLink, deferred, leftToLoad, alarmsList);
} else { } else {
alarmsList = $filter('orderBy')(alarmsList, ['-createdTime']); alarmsList = $filter('orderBy')(alarmsList, ['-createdTime']);
deferred.resolve(alarmsList); deferred.resolve(alarmsList);
@ -209,26 +220,34 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
var deferred = $q.defer(); var deferred = $q.defer();
var time = Date.now(); var time = Date.now();
var pageLink; var pageLink;
var leftToLoad;
if (alarmsQuery.limit) { if (alarmsQuery.limit) {
pageLink = { pageLink = {
limit: alarmsQuery.limit limit: alarmsQuery.limit
}; };
} else if (alarmsQuery.interval) { } else if (alarmsQuery.interval) {
pageLink = { pageLink = {
limit: 100, limit: alarmsQuery.alarmsFetchSize || 100,
startTime: time - alarmsQuery.interval startTime: time - alarmsQuery.interval
}; };
} else if (alarmsQuery.startTime) { } else if (alarmsQuery.startTime) {
pageLink = { pageLink = {
limit: 100, limit: alarmsQuery.alarmsFetchSize || 100,
startTime: Math.round(alarmsQuery.startTime) startTime: Math.round(alarmsQuery.startTime)
} };
if (alarmsQuery.endTime) { if (alarmsQuery.endTime) {
pageLink.endTime = Math.round(alarmsQuery.endTime); pageLink.endTime = Math.round(alarmsQuery.endTime);
} }
} }
fetchAlarms(alarmsQuery, pageLink, deferred); if (angular.isDefined(alarmsQuery.alarmsMaxCountLoad) && alarmsQuery.alarmsMaxCountLoad !== 0) {
leftToLoad = alarmsQuery.alarmsMaxCountLoad;
if (leftToLoad < pageLink.limit) {
pageLink.limit = leftToLoad;
}
}
fetchAlarms(alarmsQuery, pageLink, deferred, leftToLoad);
return deferred.promise; return deferred.promise;
} }
@ -276,8 +295,10 @@ function AlarmService($http, $q, $interval, $filter, $timeout, utils, types) {
entityType: alarmSource.entityType, entityType: alarmSource.entityType,
entityId: alarmSource.entityId, entityId: alarmSource.entityId,
alarmSearchStatus: alarmSourceListener.alarmSearchStatus, alarmSearchStatus: alarmSourceListener.alarmSearchStatus,
alarmStatus: null alarmStatus: null,
} alarmsMaxCountLoad: alarmSourceListener.alarmsMaxCountLoad,
alarmsFetchSize: alarmSourceListener.alarmsFetchSize
};
var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'}); var originatorKeys = $filter('filter')(alarmSource.dataKeys, {name: 'originator'});
if (originatorKeys && originatorKeys.length) { if (originatorKeys && originatorKeys.length) {
alarmSourceListener.alarmsQuery.fetchOriginator = true; alarmSourceListener.alarmsQuery.fetchOriginator = true;

6
ui/src/app/api/subscription.js

@ -75,6 +75,10 @@ export default class Subscription {
options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any; options.alarmSearchStatus : this.ctx.types.alarmSearchStatus.any;
this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ? this.alarmsPollingInterval = angular.isDefined(options.alarmsPollingInterval) ?
options.alarmsPollingInterval : 5000; options.alarmsPollingInterval : 5000;
this.alarmsMaxCountLoad = angular.isDefined(options.alarmsMaxCountLoad) ?
options.alarmsMaxCountLoad : 0;
this.alarmsFetchSize = angular.isDefined(options.alarmsFetchSize) ?
options.alarmsFetchSize : 100;
this.alarmSourceListener = null; this.alarmSourceListener = null;
this.alarms = []; this.alarms = [];
@ -915,6 +919,8 @@ export default class Subscription {
alarmSource: this.alarmSource, alarmSource: this.alarmSource,
alarmSearchStatus: this.alarmSearchStatus, alarmSearchStatus: this.alarmSearchStatus,
alarmsPollingInterval: this.alarmsPollingInterval, alarmsPollingInterval: this.alarmsPollingInterval,
alarmsMaxCountLoad: this.alarmsMaxCountLoad,
alarmsFetchSize: this.alarmsFetchSize,
alarmsUpdated: function(alarms, apply) { alarmsUpdated: function(alarms, apply) {
subscription.alarmsUpdated(alarms, apply); subscription.alarmsUpdated(alarms, apply);
} }

8
ui/src/app/components/widget/widget-config.directive.js

@ -173,6 +173,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
config.alarmSearchStatus : types.alarmSearchStatus.any; config.alarmSearchStatus : types.alarmSearchStatus.any;
scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ? scope.alarmsPollingInterval = angular.isDefined(config.alarmsPollingInterval) ?
config.alarmsPollingInterval : 5; config.alarmsPollingInterval : 5;
scope.alarmsMaxCountLoad = angular.isDefined(config.alarmsMaxCountLoad) ?
config.alarmsMaxCountLoad : 0;
scope.alarmsFetchSize = angular.isDefined(config.alarmsFetchSize) ?
config.alarmsFetchSize : 100;
if (config.alarmSource) { if (config.alarmSource) {
scope.alarmSource.value = config.alarmSource; scope.alarmSource.value = config.alarmSource;
} else { } else {
@ -243,7 +247,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
scope.$watch('title + showTitleIcon + titleIcon + iconColor + iconSize + titleTooltip + showTitle + dropShadow + enableFullscreen + backgroundColor + ' + scope.$watch('title + showTitleIcon + titleIcon + iconColor + iconSize + titleTooltip + showTitle + dropShadow + enableFullscreen + backgroundColor + ' +
'color + padding + margin + widgetStyle + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' + 'color + padding + margin + widgetStyle + titleStyle + mobileOrder + mobileHeight + units + decimals + useDashboardTimewindow + ' +
'displayTimewindow + alarmSearchStatus + alarmsPollingInterval + showLegend', function () { 'displayTimewindow + alarmSearchStatus + alarmsPollingInterval + alarmsMaxCountLoad + alarmsFetchSize + showLegend', function () {
if (ngModelCtrl.$viewValue) { if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue; var value = ngModelCtrl.$viewValue;
if (value.config) { if (value.config) {
@ -277,6 +281,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $translate, $timeout
config.displayTimewindow = scope.displayTimewindow; config.displayTimewindow = scope.displayTimewindow;
config.alarmSearchStatus = scope.alarmSearchStatus; config.alarmSearchStatus = scope.alarmSearchStatus;
config.alarmsPollingInterval = scope.alarmsPollingInterval; config.alarmsPollingInterval = scope.alarmsPollingInterval;
config.alarmsMaxCountLoad = scope.alarmsMaxCountLoad;
config.alarmsFetchSize = scope.alarmsFetchSize;
config.showLegend = scope.showLegend; config.showLegend = scope.showLegend;
} }
if (value.layout) { if (value.layout) {

75
ui/src/app/components/widget/widget-config.tpl.html

@ -37,29 +37,58 @@
is-edit="true" flex ng-model="timewindow"></tb-timewindow> is-edit="true" flex ng-model="timewindow"></tb-timewindow>
</section> </section>
</div> </div>
<div ng-show="widgetType === types.widgetType.alarm.value" layout='column' layout-align="center" <div ng-show="widgetType === types.widgetType.alarm.value" layout='column' layout-align="center">
layout-gt-sm='row' layout-align-gt-sm="start center"> <div layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container class="md-block" flex> <md-input-container class="md-block" flex>
<label translate>alarm.alarm-status</label> <label translate>alarm.alarm-status</label>
<md-select ng-model="alarmSearchStatus" style="padding-bottom: 24px;"> <md-select ng-model="alarmSearchStatus" style="padding-bottom: 24px;">
<md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus"> <md-option ng-repeat="searchStatus in types.alarmSearchStatus" ng-value="searchStatus">
{{ ('alarm.search-status.' + searchStatus) | translate }} {{ ('alarm.search-status.' + searchStatus) | translate }}
</md-option> </md-option>
</md-select> </md-select>
</md-input-container> </md-input-container>
<md-input-container flex class="md-block"> <md-input-container flex class="md-block">
<label translate>alarm.polling-interval</label> <label translate>alarm.polling-interval</label>
<input ng-required="widgetType === types.widgetType.alarm.value" <input ng-required="widgetType === types.widgetType.alarm.value"
type="number" type="number"
step="1" step="1"
min="1" min="1"
name="alarmsPollingInterval" name="alarmsPollingInterval"
ng-model="alarmsPollingInterval"/> ng-model="alarmsPollingInterval"/>
<div ng-messages="theForm.alarmsPollingInterval.$error" multiple md-auto-hide="false"> <div ng-messages="theForm.alarmsPollingInterval.$error" multiple md-auto-hide="false">
<div ng-message="required" translate>alarm.polling-interval-required</div> <div ng-message="required" translate>alarm.polling-interval-required</div>
<div ng-message="min" translate>alarm.min-polling-interval-message</div> <div ng-message="min" translate>alarm.min-polling-interval-message</div>
</div> </div>
</md-input-container> </md-input-container>
</div>
<div layout-gt-sm='row' layout-align-gt-sm="start center">
<md-input-container flex class="md-block">
<label translate>alarm.max-count-load</label>
<input ng-required="widgetType === types.widgetType.alarm.value"
type="number"
step="1"
min="0"
name="alarmsMaxCountLoad"
ng-model="alarmsMaxCountLoad"/>
<div ng-messages="theForm.alarmsMaxCountLoad.$error" multiple md-auto-hide="false">
<div ng-message="required" translate>alarm.max-count-load-required</div>
<div ng-message="min" translate>alarm.max-count-load-error-min</div>
</div>
</md-input-container>
<md-input-container flex class="md-block">
<label translate>alarm.fetch-size</label>
<input ng-required="widgetType === types.widgetType.alarm.value"
type="number"
step="1"
min="10"
name="alarmsFetchSize"
ng-model="alarmsFetchSize"/>
<div ng-messages="theForm.alarmsFetchSize.$error" multiple md-auto-hide="false">
<div ng-message="required" translate>alarm.fetch-size-required</div>
<div ng-message="min" translate>alarm.fetch-size-error-min</div>
</div>
</md-input-container>
</div>
</div> </div>
<v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
ng-show="widgetType !== types.widgetType.rpc.value ng-show="widgetType !== types.widgetType.rpc.value

4
ui/src/app/components/widget/widget.controller.js

@ -367,6 +367,10 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL
widget.config.alarmSearchStatus : types.alarmSearchStatus.any; widget.config.alarmSearchStatus : types.alarmSearchStatus.any;
options.alarmsPollingInterval = angular.isDefined(widget.config.alarmsPollingInterval) ? options.alarmsPollingInterval = angular.isDefined(widget.config.alarmsPollingInterval) ?
widget.config.alarmsPollingInterval * 1000 : 5000; widget.config.alarmsPollingInterval * 1000 : 5000;
options.alarmsMaxCountLoad = angular.isDefined(widget.config.alarmsMaxCountLoad) ?
widget.config.alarmsMaxCountLoad : 0;
options.alarmsFetchSize = angular.isDefined(widget.config.alarmsFetchSize) ?
widget.config.alarmsFetchSize : 100;
} else { } else {
options.datasources = angular.copy(widget.config.datasources) options.datasources = angular.copy(widget.config.datasources)
} }

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

@ -162,7 +162,13 @@
"clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?", "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?",
"clear-alarm-title": "Clear Alarm", "clear-alarm-title": "Clear Alarm",
"clear-alarm-text": "Are you sure you want to clear Alarm?", "clear-alarm-text": "Are you sure you want to clear Alarm?",
"alarm-status-filter": "Alarm Status Filter" "alarm-status-filter": "Alarm Status Filter",
"max-count-load": "Maximum number of alarms to load (0 - unlimited)",
"max-count-load-required": "Maximum number of alarms to load is required.",
"max-count-load-error-min": "Minimum value is 0.",
"fetch-size": "Fetch size",
"fetch-size-required": "Fetch size is required.",
"fetch-size-error-min": "Minimum value is 10."
}, },
"alias": { "alias": {
"add": "Add alias", "add": "Add alias",

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

@ -162,7 +162,13 @@
"clear-alarms-text": "Вы точно хотите сбросить { count, plural, 1 {1 оповещение} other {# оповещений} }?", "clear-alarms-text": "Вы точно хотите сбросить { count, plural, 1 {1 оповещение} other {# оповещений} }?",
"clear-alarm-title": "Сбросить оповещение", "clear-alarm-title": "Сбросить оповещение",
"clear-alarm-text": "Вы точно хотите сбросить оповещение?", "clear-alarm-text": "Вы точно хотите сбросить оповещение?",
"alarm-status-filter": "Фильтр оповещений" "alarm-status-filter": "Фильтр оповещений",
"max-count-load": "Максимальное количество оповещений для загрузки (0 - неограниченно)",
"max-count-load-required": "Максимальное количество оповещений для загрузки обязателен.",
"max-count-load-error-min": "Минимальное значение 0.",
"fetch-size": "Размер пакета для загрузки",
"fetch-size-required": "Размер пакета для загрузки обязателен.",
"fetch-size-error-min": "Минимальное значение 10."
}, },
"alias": { "alias": {
"add": "Добавить псевдоним", "add": "Добавить псевдоним",

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

@ -178,7 +178,13 @@
"clear-alarms-text": "Ви впевнені, що хочете деактивувати { count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} }?", "clear-alarms-text": "Ви впевнені, що хочете деактивувати { count, plural, 1 {1 сигнал тривоги} other {# сигнали тривоги} }?",
"clear-alarm-title": "Деактивувати сигнал тривоги", "clear-alarm-title": "Деактивувати сигнал тривоги",
"clear-alarm-text": "Ви впевнені, що хочете деактивувати сигнал тривоги?", "clear-alarm-text": "Ви впевнені, що хочете деактивувати сигнал тривоги?",
"alarm-status-filter": "Фільтр статусу сигналу тривоги" "alarm-status-filter": "Фільтр статусу сигналу тривоги",
"max-count-load": "Максимальна кількість сигналів тривоги для завантаження (0 - необмежено)",
"max-count-load-required": "Необхідно задати максимальну кількість сигналів тривоги для завантаження.",
"max-count-load-error-min": "Мінімальне значення 0.",
"fetch-size": "Розмір пакету для завантаження",
"fetch-size-required": "Необхідно задати розмір пакету для завантаження.",
"fetch-size-error-min": "Мінімальне значення 10."
}, },
"alias": { "alias": {
"add": "Додати псевдонім ", "add": "Додати псевдонім ",

Loading…
Cancel
Save