Browse Source

Merge remote-tracking branch 'origin/feature/edge' into feature/edge

pull/2436/head
deaflynx 6 years ago
parent
commit
eacb7dcb2e
  1. 16
      application/pom.xml
  2. 1
      application/src/main/conf/logback.xml
  3. 2
      application/src/main/data/json/demo/rule_chains/thermostat_alarms.json
  4. 2
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  5. 59
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 37
      application/src/main/java/org/thingsboard/server/actors/TbEntityTypeActorIdPredicate.java
  7. 104
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  8. 25
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  9. 14
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java
  10. 32
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  11. 48
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  12. 38
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  13. 95
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  14. 57
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
  15. 28
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
  16. 16
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  17. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java
  18. 57
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  19. 45
      application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java
  20. 6
      application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java
  21. 92
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  22. 36
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  23. 21
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  24. 40
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  25. 10
      application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java
  26. 134
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  27. 12
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  28. 11
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  29. 76
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  30. 47
      application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
  31. 10
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  32. 13
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  33. 7
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  34. 9
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  35. 13
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  36. 32
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  37. 84
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  38. 36
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  39. 4
      application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
  40. 323
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  41. 19
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  42. 38
      application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java
  43. 709
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  44. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetUpdateMsgConstructor.java
  45. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardUpdateMsgConstructor.java
  46. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceUpdateMsgConstructor.java
  47. 56
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java
  48. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewUpdateMsgConstructor.java
  49. 9
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainUpdateMsgConstructor.java
  50. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserUpdateMsgConstructor.java
  51. 255
      application/src/main/java/org/thingsboard/server/service/edge/rpc/init/DefaultSyncEdgeService.java
  52. 2
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  53. 25
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  54. 14
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  55. 6
      application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java
  56. 5
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingResult.java
  57. 6
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java
  58. 7
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java
  59. 2
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
  60. 2
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java
  61. 139
      application/src/main/resources/actor-system.conf
  62. 1
      application/src/main/resources/logback.xml
  63. 13
      application/src/main/resources/thingsboard.yml
  64. 5
      application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java
  65. 99
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  66. 3
      application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java
  67. 3
      application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java
  68. 4
      application/src/test/java/org/thingsboard/server/mqtt/MqttSqlTestSuite.java
  69. 4
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  70. 8
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  71. 6
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  72. 4
      application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java
  73. 2
      application/src/test/resources/logback.xml
  74. 75
      common/actor/pom.xml
  75. 34
      common/actor/src/main/java/org/thingsboard/server/actors/AbstractTbActor.java
  76. 215
      common/actor/src/main/java/org/thingsboard/server/actors/DefaultTbActorSystem.java
  77. 13
      common/actor/src/main/java/org/thingsboard/server/actors/Dispatcher.java
  78. 45
      common/actor/src/main/java/org/thingsboard/server/actors/InitFailureStrategy.java
  79. 38
      common/actor/src/main/java/org/thingsboard/server/actors/ProcessFailureStrategy.java
  80. 43
      common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java
  81. 24
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorCreator.java
  82. 41
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorCtx.java
  83. 23
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorException.java
  84. 20
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorId.java
  85. 209
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java
  86. 29
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorNotRegisteredException.java
  87. 28
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorRef.java
  88. 54
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorSystem.java
  89. 27
      common/actor/src/main/java/org/thingsboard/server/actors/TbActorSystemSettings.java
  90. 49
      common/actor/src/main/java/org/thingsboard/server/actors/TbEntityActorId.java
  91. 34
      common/actor/src/main/java/org/thingsboard/server/actors/TbStringActorId.java
  92. 224
      common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java
  93. 39
      common/actor/src/test/java/org/thingsboard/server/actors/ActorTestCtx.java
  94. 72
      common/actor/src/test/java/org/thingsboard/server/actors/FailedToInitActor.java
  95. 35
      common/actor/src/test/java/org/thingsboard/server/actors/IntTbActorMsg.java
  96. 53
      common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java
  97. 57
      common/actor/src/test/java/org/thingsboard/server/actors/SlowInitActor.java
  98. 87
      common/actor/src/test/java/org/thingsboard/server/actors/TestRootActor.java
  99. 14
      common/actor/src/test/resources/logback.xml
  100. 2
      common/dao-api/pom.xml

16
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.5.1-SNAPSHOT</version>
<version>2.5.3-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>
@ -57,6 +57,10 @@
<!-- Explicitly bring in the linux classifier, test may fail on 32-bit linux -->
<classifier>linux-x86_64</classifier>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>actor</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>util</artifactId>
@ -177,14 +181,6 @@
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_${scala.version}</artifactId>
</dependency>
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-slf4j_${scala.version}</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
@ -375,7 +371,7 @@
<repository>
<id>jenkins</id>
<name>Jenkins Repository</name>
<url>http://repo.jenkins-ci.org/releases</url>
<url>https://repo.jenkins-ci.org/releases</url>
<snapshots>
<enabled>false</enabled>
</snapshots>

1
application/src/main/conf/logback.xml

@ -35,7 +35,6 @@
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<logger name="akka" level="INFO" />
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
<root level="INFO">

2
application/src/main/data/json/demo/rule_chains/thermostat_alarms.json

@ -2,7 +2,7 @@
"ruleChain": {
"additionalInfo": null,
"name": "Thermostat Alarms",
"type": "SYSTEM",
"type": "CORE",
"firstRuleNodeId": null,
"root": false,
"debugMode": false,

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

@ -2,7 +2,7 @@
"ruleChain": {
"additionalInfo": null,
"name": "Root Rule Chain",
"type": "SYSTEM",
"type": "CORE",
"firstRuleNodeId": null,
"root": true,
"debugMode": false,

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

@ -15,9 +15,6 @@
*/
package org.thingsboard.server.actors;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Scheduler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -25,8 +22,6 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@ -58,6 +53,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
@ -92,12 +88,13 @@ import java.io.StringWriter;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Component
public class ActorSystemContext {
private static final String AKKA_CONF_FILE_NAME = "actor-system.conf";
protected final ObjectMapper mapper = new ObjectMapper();
@ -258,6 +255,11 @@ public class ActorSystemContext {
@Getter
private EdgeService edgeService;
@Lazy
@Autowired
@Getter
private EdgeEventService edgeEventService;
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter
private long maxConcurrentSessionsPerDevice;
@ -266,14 +268,6 @@ public class ActorSystemContext {
@Getter
private long syncSessionTimeout;
@Value("${actors.queue.enabled}")
@Getter
private boolean queuePersistenceEnabled;
@Value("${actors.queue.timeout}")
@Getter
private long queuePersistenceTimeout;
@Value("${actors.rule.chain.error_persist_frequency}")
@Getter
private long ruleChainErrorPersistFrequency;
@ -333,17 +327,14 @@ public class ActorSystemContext {
@Getter
@Setter
private ActorSystem actorSystem;
private TbActorSystem actorSystem;
@Setter
private ActorRef appActor;
private TbActorRef appActor;
@Getter
@Setter
private ActorRef statsActor;
@Getter
private final Config config;
private TbActorRef statsActor;
@Autowired(required = false)
@Getter
@ -357,14 +348,8 @@ public class ActorSystemContext {
@Getter
private RedisTemplate<String, Object> redisTemplate;
public ActorSystemContext() {
config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
}
public Scheduler getScheduler() {
return actorSystem.scheduler();
public ScheduledExecutorService getScheduler() {
return actorSystem.getScheduler();
}
public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) {
@ -537,7 +522,21 @@ public class ActorSystemContext {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}
public void tell(TbActorMsg tbActorMsg, ActorRef sender) {
appActor.tell(tbActorMsg, sender);
public void tell(TbActorMsg tbActorMsg) {
appActor.tell(tbActorMsg);
}
public void tellWithHighPriority(TbActorMsg tbActorMsg) {
appActor.tellWithHighPriority(tbActorMsg);
}
public void schedulePeriodicMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
getScheduler().scheduleWithFixedDelay(() -> ctx.tell(msg), delayInMs, periodInMs, TimeUnit.MILLISECONDS);
}
public void scheduleMsgWithDelay(TbActorRef ctx, TbActorMsg msg, long delayInMs) {
log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS);
}
}

37
application/src/main/java/org/thingsboard/server/actors/TbEntityTypeActorIdPredicate.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.RequiredArgsConstructor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.function.Predicate;
@RequiredArgsConstructor
public class TbEntityTypeActorIdPredicate implements Predicate<TbActorId> {
private final EntityType entityType;
@Override
public boolean test(TbActorId actorId) {
return actorId instanceof TbEntityActorId && testEntityId(((TbEntityActorId) actorId).getEntityId());
}
protected boolean testEntityId(EntityId entityId) {
return entityId.getEntityType().equals(entityType);
}
}

104
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -15,21 +15,19 @@
*/
package org.thingsboard.server.actors.app;
import akka.actor.ActorRef;
import akka.actor.LocalActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.Terminated;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
@ -43,38 +41,27 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import scala.concurrent.duration.Duration;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@Slf4j
public class AppActor extends ContextAwareActor {
private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
private final TenantService tenantService;
private final BiMap<TenantId, ActorRef> tenantActors;
private final Set<TenantId> deletedTenants;
private boolean ruleChainsInitialized;
private AppActor(ActorSystemContext systemContext) {
super(systemContext);
this.tenantService = systemContext.getTenantService();
this.tenantActors = HashBiMap.create();
this.deletedTenants = new HashSet<>();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
@Override
public void preStart() {
}
@Override
protected boolean process(TbActorMsg msg) {
protected boolean doProcess(TbActorMsg msg) {
if (!ruleChainsInitialized) {
initTenantActors();
ruleChainsInitialized = true;
@ -86,7 +73,7 @@ public class AppActor extends ContextAwareActor {
case APP_INIT_MSG:
break;
case PARTITION_CHANGE_MSG:
broadcast(msg);
ctx.broadcastToChildren(msg);
break;
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
@ -95,12 +82,14 @@ public class AppActor extends ContextAwareActor {
onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);
break;
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg, false);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg);
onToDeviceActorMsg((TenantAwareMsg) msg, true);
break;
default:
return false;
@ -145,19 +134,15 @@ public class AppActor extends ContextAwareActor {
msg.getTbMsg().getCallback().onFailure(new RuleEngineException("Message has system tenant id!"));
} else {
if (!deletedTenants.contains(msg.getTenantId())) {
getOrCreateTenantActor(msg.getTenantId()).tell(msg, self());
getOrCreateTenantActor(msg.getTenantId()).tell(msg);
} else {
msg.getTbMsg().getCallback().onSuccess();
}
}
}
protected void broadcast(Object msg) {
tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
ActorRef target = null;
TbActorRef target = null;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
log.warn("Message has system tenant id: {}", msg);
} else {
@ -166,25 +151,26 @@ public class AppActor extends ContextAwareActor {
log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg);
TenantId tenantId = new TenantId(msg.getEntityId().getId());
deletedTenants.add(tenantId);
ActorRef tenantActor = tenantActors.get(tenantId);
if (tenantActor != null) {
log.debug("[{}] Deleting tenant actor: {}", msg.getTenantId(), tenantActor);
context().stop(tenantActor);
}
ctx.stop(new TbEntityActorId(tenantId));
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
}
if (target != null) {
target.tell(msg, ActorRef.noSender());
target.tellWithHighPriority(msg);
} else {
log.debug("[{}] Invalid component lifecycle msg: {}", msg.getTenantId(), msg);
}
}
private void onToDeviceActorMsg(TenantAwareMsg msg) {
private void onToDeviceActorMsg(TenantAwareMsg msg, boolean priority) {
if (!deletedTenants.contains(msg.getTenantId())) {
getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
TbActorRef tenantActor = getOrCreateTenantActor(msg.getTenantId());
if (priority) {
tenantActor.tellWithHighPriority(msg);
} else {
tenantActor.tell(msg);
}
} else {
if (msg instanceof TransportToDeviceActorMsgWrapper) {
((TransportToDeviceActorMsgWrapper) msg).getCallback().onSuccess();
@ -192,49 +178,27 @@ public class AppActor extends ContextAwareActor {
}
}
private ActorRef getOrCreateTenantActor(TenantId tenantId) {
return tenantActors.computeIfAbsent(tenantId, k -> {
log.info("[{}] Creating tenant actor.", tenantId);
ActorRef tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString());
context().watch(tenantActor);
log.info("[{}] Created tenant actor: {}.", tenantId, tenantActor);
return tenantActor;
});
private TbActorRef getOrCreateTenantActor(TenantId tenantId) {
return ctx.getOrCreateChildActor(new TbEntityActorId(tenantId),
() -> DefaultActorService.TENANT_DISPATCHER_NAME,
() -> new TenantActor.ActorCreator(systemContext, tenantId));
}
@Override
protected void processTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
boolean removed = tenantActors.inverse().remove(terminated) != null;
if (removed) {
log.debug("[{}] Removed actor:", terminated);
}
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
}
public static class ActorCreator extends ContextBasedCreator<AppActor> {
private static final long serialVersionUID = 1L;
public static class ActorCreator extends ContextBasedCreator {
public ActorCreator(ActorSystemContext context) {
super(context);
}
@Override
public AppActor create() {
public TbActorId createActorId() {
return new TbEntityActorId(new TenantId(EntityId.NULL_UUID));
}
@Override
public TbActor createActor() {
return new AppActor(context);
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
log.warn("Unknown failure", t);
if (t instanceof RuntimeException) {
return SupervisorStrategy.restart();
} else {
return SupervisorStrategy.stop();
}
});
}

25
application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java

@ -15,9 +15,12 @@
*/
package org.thingsboard.server.actors.device;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -26,6 +29,7 @@ import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeout
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
@Slf4j
public class DeviceActor extends ContextAwareActor {
private final DeviceActorMessageProcessor processor;
@ -36,29 +40,26 @@ public class DeviceActor extends ContextAwareActor {
}
@Override
public void preStart() {
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId);
try {
processor.initSessionTimeout(context());
processor.initSessionTimeout(ctx);
log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId);
} catch (Exception e) {
log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e);
throw new TbActorException("Failed to initialize device actor", e);
}
}
@Override
public void postStop() {
}
@Override
protected boolean process(TbActorMsg msg) {
protected boolean doProcess(TbActorMsg msg) {
switch (msg.getMsgType()) {
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
processor.process(context(), (TransportToDeviceActorMsgWrapper) msg);
processor.process(ctx, (TransportToDeviceActorMsgWrapper) msg);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
processor.processAttributesUpdate(ctx, (DeviceAttributesEventNotificationMsg) msg);
break;
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
processor.processCredentialsUpdate();
@ -67,10 +68,10 @@ public class DeviceActor extends ContextAwareActor {
processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
break;
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
processor.processRpcRequest(context(), (ToDeviceRpcRequestActorMsg) msg);
processor.processRpcRequest(ctx, (ToDeviceRpcRequestActorMsg) msg);
break;
case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG:
processor.processServerSideRpcTimeout(context(), (DeviceActorServerSideRpcTimeoutMsg) msg);
processor.processServerSideRpcTimeout(ctx, (DeviceActorServerSideRpcTimeoutMsg) msg);
break;
case SESSION_TIMEOUT_MSG:
processor.checkSessionsTimeout();

14
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java

@ -16,12 +16,14 @@
package org.thingsboard.server.actors.device;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
public class DeviceActorCreator extends ContextBasedCreator<DeviceActor> {
private static final long serialVersionUID = 1L;
public class DeviceActorCreator extends ContextBasedCreator {
private final TenantId tenantId;
private final DeviceId deviceId;
@ -33,7 +35,13 @@ public class DeviceActorCreator extends ContextBasedCreator<DeviceActor> {
}
@Override
public DeviceActor create() {
public TbActorId createActorId() {
return new TbEntityActorId(deviceId);
}
@Override
public TbActor createActor() {
return new DeviceActor(context, tenantId, deviceId);
}
}

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

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.actors.device;
import akka.actor.ActorContext;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -27,6 +26,7 @@ import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
@ -127,7 +127,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
ToDeviceRpcRequest request = msg.getMsg();
ToDeviceRpcRequestBody body = request.getBody();
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
@ -162,13 +162,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
}
void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) {
void processServerSideRpcTimeout(TbActorCtx context, DeviceActorServerSideRpcTimeoutMsg msg) {
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
if (requestMd != null) {
log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
@ -177,7 +177,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) {
private void sendPendingRequests(TbActorCtx context, UUID sessionId, SessionInfoProto sessionInfo) {
SessionType sessionType = getSessionType(sessionId);
if (!toDeviceRpcPendingMap.isEmpty()) {
log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
@ -198,7 +198,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
}
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) {
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(TbActorCtx context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) {
return entry -> {
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
ToDeviceRpcRequestBody body = request.getBody();
@ -212,7 +212,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
};
}
void process(ActorContext context, TransportToDeviceActorMsgWrapper wrapper) {
void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) {
TransportToDeviceActorMsg msg = wrapper.getMsg();
TbCallback callback = wrapper.getCallback();
if (msg.hasSessionEvent()) {
@ -239,7 +239,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
callback.onSuccess();
}
private void handleClaimDeviceMsg(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
}
@ -252,7 +252,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
systemContext.getDeviceStateService().onDeviceDisconnect(deviceId);
}
private void handleGetAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
int requestId = request.getRequestId();
Futures.addCallback(getAttributesKvEntries(request), new FutureCallback<List<List<AttributeKvEntry>>>() {
@Override
@ -310,7 +310,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
return sessions.containsKey(sessionId) ? SessionType.ASYNC : SessionType.SYNC;
}
void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
void processAttributesUpdate(TbActorCtx context, DeviceAttributesEventNotificationMsg msg) {
if (attributeSubscriptions.size() > 0) {
boolean hasNotificationData = false;
AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
@ -349,7 +349,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) {
private void processRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) {
UUID sessionId = getSessionId(sessionInfo);
log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
@ -362,7 +362,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
@ -383,7 +383,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
}
private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) {
private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId);
@ -433,7 +433,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
}
private void handleSessionActivity(ActorContext context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) {
private void handleSessionActivity(TbActorCtx context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) {
UUID sessionId = getSessionId(sessionInfoProto);
SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId,
id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), 0L));
@ -612,8 +612,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.addAllSessions(sessionsList).build().toByteArray());
}
void initSessionTimeout(ActorContext context) {
schedulePeriodicMsgWithDelay(context, SessionTimeoutCheckMsg.instance(), systemContext.getSessionInactivityTimeout(), systemContext.getSessionInactivityTimeout());
void initSessionTimeout(TbActorCtx ctx) {
schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionInactivityTimeout(), systemContext.getSessionInactivityTimeout());
}
void checkSessionsTimeout() {

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

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorRef;
import com.datastax.driver.core.ResultSetFuture;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -30,6 +29,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
@ -51,6 +52,7 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
@ -63,7 +65,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import scala.concurrent.duration.Duration;
import java.util.Collections;
import java.util.Set;
@ -105,7 +106,7 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null), nodeCtx.getSelfActor());
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
}
@Override
@ -131,7 +132,7 @@ class DefaultTbContext implements TbContext {
.setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
.setTbMsg(TbMsg.toByteString(tbMsg)).build();
mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, onFailure));
mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg, new SimpleTbQueueCallback(onSuccess, onFailure));
}
@Override
@ -188,7 +189,7 @@ class DefaultTbContext implements TbContext {
if (failureMessage != null) {
msg.setFailureMessage(failureMessage);
}
mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, onFailure));
mainCtx.getClusterService().pushMsgToRuleEngine(tpi, tbMsg.getId(), msg.build(), new SimpleTbQueueCallback(onSuccess, onFailure));
}
@Override
@ -204,8 +205,8 @@ class DefaultTbContext implements TbContext {
return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), entityId).isMyPartition();
}
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
mainCtx.getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, mainCtx.getActorSystem().dispatcher(), nodeCtx.getSelfActor());
private void scheduleMsgWithDelay(TbActorMsg msg, long delayInMs, TbActorRef target) {
mainCtx.scheduleMsgWithDelay(target, msg, delayInMs);
}
@Override
@ -214,7 +215,7 @@ class DefaultTbContext implements TbContext {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th);
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE),
msg, th != null ? th.getMessage() : null), nodeCtx.getSelfActor());
msg, th != null ? th.getMessage() : null));
}
public void updateSelf(RuleNode self) {
@ -222,8 +223,8 @@ class DefaultTbContext implements TbContext {
}
@Override
public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return TbMsg.newMsg(type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
public TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return TbMsg.newMsg(queueName, type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
}
@Override
@ -248,28 +249,8 @@ class DefaultTbContext implements TbContext {
}
public <E, I extends EntityId> TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) {
return entityCRUDMsg(entity, id, ruleNodeId, DataConstants.ENTITY_CREATED);
}
public <E, I extends EntityId> TbMsg entityCRUDMsg(E entity, I id, RuleNodeId ruleNodeId, String actionType) {
try {
return TbMsg.newMsg(actionType, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)));
} catch (JsonProcessingException | IllegalArgumentException e) {
throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + actionType + " msg: " + e);
}
}
public TbMsg alarmUpdatedMsg(Alarm alarm, RuleNodeId ruleNodeId) {
return entityCRUDMsg(alarm, alarm.getId(), ruleNodeId, DataConstants.ENTITY_UPDATED);
}
public TbMsg alarmClearedMsg(Alarm alarm, RuleNodeId ruleNodeId) {
return entityCRUDMsg(alarm, alarm.getId(), ruleNodeId, DataConstants.ALARM_CLEAR);
}
public <E, I extends EntityId> TbMsg alarmMsg(E entity, I id, RuleNodeId ruleNodeId, String actionType) {
try {
return TbMsg.newMsg(actionType, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)));
return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)));
} catch (JsonProcessingException | IllegalArgumentException e) {
throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e);
}
@ -406,6 +387,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getEdgeService();
}
@Override
public EdgeEventService getEdgeEventService() {
return mainCtx.getEdgeEventService();
}
@Override
public EventLoopGroup getSharedEventLoop() {
return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();

38
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java

@ -15,9 +15,11 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.RuleChainId;
@ -27,18 +29,24 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import scala.concurrent.duration.Duration;
public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMessageProcessor> {
private final RuleChain ruleChain;
private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChain ruleChain) {
super(systemContext, tenantId, ruleChain.getId());
setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChain, systemContext,
context().parent(), context().self()));
this.ruleChain = ruleChain;
}
@Override
protected boolean process(TbActorMsg msg) {
protected RuleChainActorMessageProcessor createProcessor(TbActorCtx ctx) {
return new RuleChainActorMessageProcessor(tenantId, ruleChain, systemContext,
ctx.getParentRef(), ctx);
}
@Override
protected boolean doProcess(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
@ -64,7 +72,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
return true;
}
public static class ActorCreator extends ContextBasedCreator<RuleChainActor> {
public static class ActorCreator extends ContextBasedCreator {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
@ -77,7 +85,12 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
}
@Override
public RuleChainActor create() {
public TbActorId createActorId() {
return new TbEntityActorId(ruleChain.getId());
}
@Override
public TbActor createActor() {
return new RuleChainActor(context, tenantId, ruleChain);
}
}
@ -87,13 +100,4 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
return systemContext.getRuleChainErrorPersistFrequency();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
logAndPersist("Unknown Failure", ActorSystemContext.toException(t));
return SupervisorStrategy.resume();
});
}

95
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -15,17 +15,14 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import com.google.common.util.concurrent.FutureCallback;
import com.sun.istack.Nullable;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
@ -45,7 +42,6 @@ import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.queue.TbQueueCallback;
@ -67,20 +63,20 @@ import java.util.stream.Collectors;
@Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private final ActorRef parent;
private final ActorRef self;
private final TbActorRef parent;
private final TbActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes;
private final RuleChainService service;
private final TbClusterService clusterService;
private final EdgeService edgeService;
private String ruleChainName;
private RuleNodeId firstId;
private RuleNodeCtx firstNode;
private boolean started;
RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext
, ActorRef parent, ActorRef self) {
, TbActorRef parent, TbActorRef self) {
super(systemContext, tenantId, ruleChain.getId());
this.ruleChainName = ruleChain.getName();
this.parent = parent;
@ -89,7 +85,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
this.nodeRoutes = new HashMap<>();
this.service = systemContext.getRuleChainService();
this.clusterService = systemContext.getClusterService();
this.edgeService = systemContext.getEdgeService();
}
@Override
@ -98,17 +93,17 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
@Override
public void start(ActorContext context) {
public void start(TbActorCtx context) {
if (!started) {
RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
if (ruleChain != null) {
if (ruleChain.getType().equals(RuleChainType.SYSTEM)) {
if (ruleChain.getType().equals(RuleChainType.CORE)) {
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
TbActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
}
initRoutes(ruleChain, ruleNodeList);
@ -121,10 +116,10 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
@Override
public void onUpdate(ActorContext context) {
public void onUpdate(TbActorCtx context) {
RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
if (ruleChain != null) {
if (ruleChain.getType().equals(RuleChainType.SYSTEM)) {
if (ruleChain.getType().equals(RuleChainType.CORE)) {
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
@ -132,22 +127,22 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
TbActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
} else {
log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
existing.setSelf(ruleNode);
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
existing.getSelfActor().tellWithHighPriority(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED));
}
}
Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
removedRules.forEach(ruleNodeId -> {
log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
});
Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
removedRules.forEach(ruleNodeId -> {
log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
removed.getSelfActor().tellWithHighPriority(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED));
});
initRoutes(ruleChain, ruleNodeList);
} else if (ruleChain.getType().equals(RuleChainType.EDGE)) {
@ -157,26 +152,23 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
@Override
public void stop(ActorContext context) {
public void stop(TbActorCtx ctx) {
log.trace("[{}][{}] Stopping rule chain with {} nodes", tenantId, entityId, nodeActors.size());
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop);
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).map(TbActorRef::getActorId).forEach(ctx::stop);
nodeActors.clear();
nodeRoutes.clear();
context.stop(self);
started = false;
}
@Override
public void onPartitionChangeMsg(PartitionChangeMsg msg) {
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(actorRef -> actorRef.tell(msg, self));
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(actorRef -> actorRef.tellWithHighPriority(msg));
}
private ActorRef createRuleNodeActor(ActorContext context, RuleNode ruleNode) {
String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ?
DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
return context.actorOf(
Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getName(), ruleNode.getId()))
.withDispatcher(dispatcherName), ruleNode.getId().toString());
private TbActorRef createRuleNodeActor(TbActorCtx ctx, RuleNode ruleNode) {
return ctx.getOrCreateChildActor(new TbEntityActorId(ruleNode.getId()),
() -> DefaultActorService.RULE_DISPATCHER_NAME,
() -> new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getName(), ruleNode.getId()));
}
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
@ -224,7 +216,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
if (targetCtx != null) {
log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
pushMsgToNode(targetCtx, msg, "");
pushUpdatesToEdges(msg);
} else {
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess();
@ -260,7 +251,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
try {
checkActive(msg);
EntityId entityId = msg.getOriginator();
TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, msg.getQueueName(), tenantId, entityId);
List<RuleNodeRelation> relations = nodeRoutes.get(originatorNodeId).stream()
.filter(r -> contains(relationTypes, r.getType()))
.collect(Collectors.toList());
@ -316,7 +307,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg, fromRelationType);
break;
case RULE_CHAIN:
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, fromRelationType), self);
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, fromRelationType));
break;
}
} else {
@ -347,37 +338,13 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg, String fromRelationType) {
if (nodeCtx != null) {
nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self);
nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType));
} else {
log.error("[{}][{}] RuleNodeCtx is empty", entityId, ruleChainName);
msg.getCallback().onFailure(new RuleEngineException("Rule Node CTX is empty"));
}
}
private void pushUpdatesToEdges(TbMsg msg) {
switch (msg.getType()) {
case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_UPDATED:
case DataConstants.ENTITY_DELETED:
case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE:
case DataConstants.ALARM_ACK:
case DataConstants.ALARM_CLEAR:
edgeService.pushEventToEdge(tenantId, msg, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void aVoid) {
log.debug("Event saved successfully!");
}
@Override
public void onFailure(Throwable t) {
log.debug("Failure during event save", t);
}
});
}
}
@Override
protected RuleNodeException getInactiveException() {
RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;

57
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java

@ -15,21 +15,22 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -38,67 +39,61 @@ import java.util.function.Function;
/**
* Created by ashvayka on 15.03.18.
*/
@Slf4j
public abstract class RuleChainManagerActor extends ContextAwareActor {
protected final TenantId tenantId;
private final RuleChainService ruleChainService;
private final BiMap<RuleChainId, ActorRef> actors;
@Getter
protected RuleChain rootChain;
@Getter
protected ActorRef rootChainActor;
protected TbActorRef rootChainActor;
public RuleChainManagerActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
this.tenantId = tenantId;
this.actors = HashBiMap.create();
this.ruleChainService = systemContext.getRuleChainService();
}
protected void initRuleChains() {
for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.SYSTEM, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
RuleChainId ruleChainId = ruleChain.getId();
log.debug("[{}|{}] Creating rule chain actor", ruleChainId.getEntityType(), ruleChain.getId());
//TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
ActorRef actorRef = getOrCreateActor(this.context(), ruleChainId, id -> ruleChain);
TbActorRef actorRef = getOrCreateActor(ruleChainId, id -> ruleChain);
visit(ruleChain, actorRef);
log.debug("[{}|{}] Rule Chain actor created.", ruleChainId.getEntityType(), ruleChainId.getId());
}
}
protected void visit(RuleChain entity, ActorRef actorRef) {
if (entity != null && entity.isRoot() && entity.getType().equals(RuleChainType.SYSTEM)) {
protected void visit(RuleChain entity, TbActorRef actorRef) {
if (entity != null && entity.isRoot() && entity.getType().equals(RuleChainType.CORE)) {
rootChain = entity;
rootChainActor = actorRef;
}
}
public ActorRef getOrCreateActor(akka.actor.ActorContext context, RuleChainId ruleChainId) {
return getOrCreateActor(context, ruleChainId, eId -> ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, eId));
protected TbActorRef getOrCreateActor(RuleChainId ruleChainId) {
return getOrCreateActor(ruleChainId, eId -> ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, eId));
}
public ActorRef getOrCreateActor(akka.actor.ActorContext context, RuleChainId ruleChainId, Function<RuleChainId, RuleChain> provider) {
return actors.computeIfAbsent(ruleChainId, eId -> {
RuleChain ruleChain = provider.apply(eId);
return context.actorOf(Props.create(new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain))
.withDispatcher(DefaultActorService.TENANT_RULE_DISPATCHER_NAME), eId.toString());
});
protected TbActorRef getOrCreateActor(RuleChainId ruleChainId, Function<RuleChainId, RuleChain> provider) {
return ctx.getOrCreateChildActor(new TbEntityActorId(ruleChainId),
() -> DefaultActorService.RULE_DISPATCHER_NAME,
() -> {
RuleChain ruleChain = provider.apply(ruleChainId);
return new RuleChainActor.ActorCreator(systemContext, tenantId, ruleChain);
});
}
protected ActorRef getEntityActorRef(EntityId entityId) {
ActorRef target = null;
protected TbActorRef getEntityActorRef(EntityId entityId) {
TbActorRef target = null;
if (entityId.getEntityType() == EntityType.RULE_CHAIN) {
target = getOrCreateActor(this.context(), (RuleChainId) entityId);
target = getOrCreateActor((RuleChainId) entityId);
}
return target;
}
protected void broadcast(Object msg) {
actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
protected void broadcast(TbActorMsg msg) {
ctx.broadcastToChildren(msg, new TbEntityTypeActorIdPredicate(EntityType.RULE_CHAIN));
}
public ActorRef get(RuleChainId id) {
return actors.get(id);
}
}

28
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java

@ -15,31 +15,43 @@
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
@Slf4j
public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> {
private final String ruleChainName;
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, String ruleChainName, RuleNodeId ruleNodeId) {
super(systemContext, tenantId, ruleNodeId);
this.ruleChainName = ruleChainName;
this.ruleChainId = ruleChainId;
setProcessor(new RuleNodeActorMessageProcessor(tenantId, this.ruleChainName, ruleNodeId, systemContext,
context().parent(), context().self()));
this.ruleNodeId = ruleNodeId;
}
@Override
protected boolean process(TbActorMsg msg) {
protected RuleNodeActorMessageProcessor createProcessor(TbActorCtx ctx) {
return new RuleNodeActorMessageProcessor(tenantId, this.ruleChainName, ruleNodeId, systemContext, ctx.getParentRef(), ctx);
}
@Override
protected boolean doProcess(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
@ -93,8 +105,7 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
logAndPersist("onRuleMsg", ActorSystemContext.toException(msg.getError()));
}
public static class ActorCreator extends ContextBasedCreator<RuleNodeActor> {
private static final long serialVersionUID = 1L;
public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId;
private final RuleChainId ruleChainId;
@ -111,7 +122,12 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
}
@Override
public RuleNodeActor create() throws Exception {
public TbActorId createActorId() {
return new TbEntityActorId(ruleNodeId);
}
@Override
public TbActor createActor() {
return new RuleNodeActor(context, tenantId, ruleChainId, ruleChainName, ruleNodeId);
}
}

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

@ -15,11 +15,11 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
@ -34,13 +34,13 @@ import org.thingsboard.server.common.msg.queue.RuleNodeException;
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> {
private final String ruleChainName;
private final ActorRef self;
private final TbActorRef self;
private RuleNode ruleNode;
private TbNode tbNode;
private DefaultTbContext defaultCtx;
RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, ActorRef parent, ActorRef self) {
, TbActorRef parent, TbActorRef self) {
super(systemContext, tenantId, ruleNodeId);
this.ruleChainName = ruleChainName;
this.self = self;
@ -49,7 +49,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
@Override
public void start(ActorContext context) throws Exception {
public void start(TbActorCtx context) throws Exception {
tbNode = initComponent(ruleNode);
if (tbNode != null) {
state = ComponentLifecycleState.ACTIVE;
@ -57,7 +57,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
@Override
public void onUpdate(ActorContext context) throws Exception {
public void onUpdate(TbActorCtx context) throws Exception {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
!(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
@ -72,11 +72,11 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
@Override
public void stop(ActorContext context) {
public void stop(TbActorCtx context) {
if (tbNode != null) {
tbNode.destroy();
state = ComponentLifecycleState.SUSPENDED;
}
context.stop(self);
}
@Override

6
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java

@ -15,9 +15,9 @@
*/
package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorRef;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode;
@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
@AllArgsConstructor
final class RuleNodeCtx {
private final TenantId tenantId;
private final ActorRef chainActor;
private final ActorRef selfActor;
private final TbActorRef chainActor;
private final TbActorRef selfActor;
private RuleNode self;
}

57
application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java

@ -15,19 +15,23 @@
*/
package org.thingsboard.server.actors.service;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.actors.stats.StatsPersistMsg;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
/**
* @author Andrew Shvayka
*/
@Slf4j
public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgProcessor<T>> extends ContextAwareActor {
private long lastPersistedErrorTs = 0L;
@ -43,30 +47,34 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
this.id = id;
}
protected void setProcessor(P processor) {
this.processor = processor;
}
abstract protected P createProcessor(TbActorCtx ctx);
@Override
public void preStart() {
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
this.processor = createProcessor(ctx);
initProcessor(ctx);
}
protected void initProcessor(TbActorCtx ctx) throws TbActorException {
try {
log.debug("[{}][{}][{}] Starting processor.", tenantId, id, id.getEntityType());
processor.start(context());
processor.start(ctx);
logLifecycleEvent(ComponentLifecycleEvent.STARTED);
if (systemContext.isStatisticsEnabled()) {
scheduleStatsPersistTick();
}
} catch (Exception e) {
log.warn("[{}][{}] Failed to start {} processor.", tenantId, id, id.getEntityType());
log.warn("Error:", e);
log.debug("[{}][{}] Failed to start {} processor.", tenantId, id, id.getEntityType(), e);
logAndPersist("OnStart", e, true);
logLifecycleEvent(ComponentLifecycleEvent.STARTED, e);
throw new TbActorException("Failed to init actor", e);
}
}
private void scheduleStatsPersistTick() {
try {
processor.scheduleStatsPersistTick(context(), systemContext.getStatisticsPersistFrequency());
processor.scheduleStatsPersistTick(ctx, systemContext.getStatisticsPersistFrequency());
} catch (Exception e) {
log.error("[{}][{}] Failed to schedule statistics store message. No statistics is going to be stored: {}", tenantId, id, e.getMessage());
logAndPersist("onScheduleStatsPersistMsg", e);
@ -74,10 +82,12 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
}
@Override
public void postStop() {
public void destroy() {
try {
log.debug("[{}][{}] Stopping processor.", tenantId, id, id.getEntityType());
processor.stop(context());
log.debug("[{}][{}][{}] Stopping processor.", tenantId, id, id.getEntityType());
if (processor != null) {
processor.stop(ctx);
}
logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
} catch (Exception e) {
log.warn("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
@ -91,19 +101,20 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
try {
switch (msg.getEvent()) {
case CREATED:
processor.onCreated(context());
processor.onCreated(ctx);
break;
case UPDATED:
processor.onUpdate(context());
processor.onUpdate(ctx);
break;
case ACTIVATED:
processor.onActivate(context());
processor.onActivate(ctx);
break;
case SUSPENDED:
processor.onSuspend(context());
processor.onSuspend(ctx);
break;
case DELETED:
processor.onStop(context());
processor.onStop(ctx);
ctx.stop(ctx.getSelf());
break;
default:
break;
@ -125,7 +136,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
protected void onStatsPersistTick(EntityId entityId) {
try {
systemContext.getStatsActor().tell(new StatsPersistMsg(messagesProcessed, errorsOccurred, tenantId, entityId), ActorRef.noSender());
systemContext.getStatsActor().tell(new StatsPersistMsg(messagesProcessed, errorsOccurred, tenantId, entityId));
resetStatsCounters();
} catch (Exception e) {
logAndPersist("onStatsPersistTick", e);
@ -149,11 +160,11 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
errorsOccurred++;
String componentName = processor != null ? processor.getComponentName() : "Unknown";
if (critical) {
log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, componentName, method);
log.warn("Critical Error: ", e);
log.debug("[{}][{}][{}] Failed to process method: {}", id, tenantId, componentName, method);
log.debug("Critical Error: ", e);
} else {
log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, componentName, method);
log.debug("Debug Error: ", e);
log.trace("[{}][{}][{}] Failed to process method: {}", id, tenantId, componentName, method);
log.trace("Debug Error: ", e);
}
long ts = System.currentTimeMillis();
if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) {

45
application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java

@ -15,17 +15,18 @@
*/
package org.thingsboard.server.actors.service;
import akka.actor.Terminated;
import akka.actor.UntypedAbstractActor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.server.actors.AbstractTbActor;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ProcessFailureStrategy;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.common.msg.TbActorMsg;
public abstract class ContextAwareActor extends UntypedAbstractActor {
protected final Logger log = LoggerFactory.getLogger(getClass());
@Slf4j
public abstract class ContextAwareActor extends AbstractTbActor {
public static final int ENTITY_PACK_LIMIT = 1024;
@ -37,27 +38,29 @@ public abstract class ContextAwareActor extends UntypedAbstractActor {
}
@Override
public void onReceive(Object msg) {
public boolean process(TbActorMsg msg) {
if (log.isDebugEnabled()) {
log.debug("Processing msg: {}", msg);
}
if (msg instanceof TbActorMsg) {
try {
if (!process((TbActorMsg) msg)) {
log.warn("Unknown message: {}!", msg);
}
} catch (Exception e) {
throw e;
}
} else if (msg instanceof Terminated) {
processTermination((Terminated) msg);
} else {
log.warn("Unknown message: {}!", msg);
if (!doProcess(msg)) {
log.warn("Unprocessed message: {}!", msg);
}
return false;
}
protected void processTermination(Terminated msg) {
protected abstract boolean doProcess(TbActorMsg msg);
@Override
public ProcessFailureStrategy onProcessFailure(Throwable t) {
log.debug("[{}] Processing failure: ", getActorRef().getActorId(), t);
return doProcessFailure(t);
}
protected abstract boolean process(TbActorMsg msg);
protected ProcessFailureStrategy doProcessFailure(Throwable t) {
if (t instanceof Error) {
return ProcessFailureStrategy.stop();
} else {
return ProcessFailureStrategy.resume();
}
}
}

6
application/src/main/java/org/thingsboard/server/actors/service/ContextBasedCreator.java

@ -15,12 +15,10 @@
*/
package org.thingsboard.server.actors.service;
import akka.japi.Creator;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCreator;
public abstract class ContextBasedCreator<T> implements Creator<T> {
private static final long serialVersionUID = 1L;
public abstract class ContextBasedCreator implements TbActorCreator {
protected final transient ActorSystemContext context;

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

@ -15,85 +15,121 @@
*/
package org.thingsboard.server.actors.service;
import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Props;
import akka.actor.Terminated;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.DefaultTbActorSystem;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbActorSystem;
import org.thingsboard.server.actors.TbActorSystemSettings;
import org.thingsboard.server.actors.app.AppActor;
import org.thingsboard.server.actors.app.AppInitMsg;
import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import scala.concurrent.Await;
import scala.concurrent.Future;
import scala.concurrent.duration.Duration;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Service
@Slf4j
public class DefaultActorService implements ActorService {
private static final String ACTOR_SYSTEM_NAME = "Akka";
public static final String APP_DISPATCHER_NAME = "app-dispatcher";
public static final String CORE_DISPATCHER_NAME = "core-dispatcher";
public static final String SYSTEM_RULE_DISPATCHER_NAME = "system-rule-dispatcher";
public static final String TENANT_RULE_DISPATCHER_NAME = "rule-dispatcher";
public static final String TENANT_DISPATCHER_NAME = "tenant-dispatcher";
public static final String DEVICE_DISPATCHER_NAME = "device-dispatcher";
public static final String RULE_DISPATCHER_NAME = "rule-dispatcher";
@Autowired
private ActorSystemContext actorContext;
private ActorSystem system;
private TbActorSystem system;
private TbActorRef appActor;
@Value("${actors.system.throughput:5}")
private int actorThroughput;
@Value("${actors.system.max_actor_init_attempts:10}")
private int maxActorInitAttempts;
@Value("${actors.system.scheduler_pool_size:1}")
private int schedulerPoolSize;
@Value("${actors.system.app_dispatcher_pool_size:1}")
private int appDispatcherSize;
@Value("${actors.system.tenant_dispatcher_pool_size:2}")
private int tenantDispatcherSize;
private ActorRef appActor;
@Value("${actors.system.device_dispatcher_pool_size:4}")
private int deviceDispatcherSize;
@Value("${actors.system.rule_dispatcher_pool_size:4}")
private int ruleDispatcherSize;
@PostConstruct
public void initActorSystem() {
log.info("Initializing Actor system.");
log.info("Initializing actor system.");
actorContext.setActorService(this);
system = ActorSystem.create(ACTOR_SYSTEM_NAME, actorContext.getConfig());
TbActorSystemSettings settings = new TbActorSystemSettings(actorThroughput, schedulerPoolSize, maxActorInitAttempts);
system = new DefaultTbActorSystem(settings);
system.createDispatcher(APP_DISPATCHER_NAME, initDispatcherExecutor(APP_DISPATCHER_NAME, appDispatcherSize));
system.createDispatcher(TENANT_DISPATCHER_NAME, initDispatcherExecutor(TENANT_DISPATCHER_NAME, tenantDispatcherSize));
system.createDispatcher(DEVICE_DISPATCHER_NAME, initDispatcherExecutor(DEVICE_DISPATCHER_NAME, deviceDispatcherSize));
system.createDispatcher(RULE_DISPATCHER_NAME, initDispatcherExecutor(RULE_DISPATCHER_NAME, ruleDispatcherSize));
actorContext.setActorSystem(system);
appActor = system.actorOf(Props.create(new AppActor.ActorCreator(actorContext)).withDispatcher(APP_DISPATCHER_NAME), "appActor");
appActor = system.createRootActor(APP_DISPATCHER_NAME, new AppActor.ActorCreator(actorContext));
actorContext.setAppActor(appActor);
ActorRef statsActor = system.actorOf(Props.create(new StatsActor.ActorCreator(actorContext)).withDispatcher(CORE_DISPATCHER_NAME), "statsActor");
TbActorRef statsActor = system.createRootActor(TENANT_DISPATCHER_NAME, new StatsActor.ActorCreator(actorContext, "StatsActor"));
actorContext.setStatsActor(statsActor);
log.info("Actor system initialized.");
}
private ExecutorService initDispatcherExecutor(String dispatcherName, int poolSize) {
if (poolSize == 0) {
int cores = Runtime.getRuntime().availableProcessors();
poolSize = Math.max(1, cores / 2);
}
if (poolSize == 1) {
return Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(dispatcherName));
} else {
return Executors.newWorkStealingPool(poolSize);
}
}
@EventListener(ApplicationReadyEvent.class)
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
log.info("Received application ready event. Sending application init message to actor system");
appActor.tell(new AppInitMsg(), ActorRef.noSender());
appActor.tellWithHighPriority(new AppInitMsg());
}
@EventListener(PartitionChangeEvent.class)
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
log.info("Received partition change event.");
this.appActor.tell(new PartitionChangeMsg(partitionChangeEvent.getServiceQueueKey(), partitionChangeEvent.getPartitions()), ActorRef.noSender());
this.appActor.tellWithHighPriority(new PartitionChangeMsg(partitionChangeEvent.getServiceQueueKey(), partitionChangeEvent.getPartitions()));
}
@PreDestroy
public void stopActorSystem() {
Future<Terminated> status = system.terminate();
try {
Terminated terminated = Await.result(status, Duration.Inf());
log.info("Actor system terminated: {}", terminated);
} catch (Exception e) {
log.error("Failed to terminate actor system.", e);
if (system != null) {
log.info("Stopping actor system.");
system.stop();
log.info("Actor system stopped.");
}
}

36
application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java

@ -15,18 +15,13 @@
*/
package org.thingsboard.server.actors.shared;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Scheduler;
import akka.event.LoggingAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import scala.concurrent.ExecutionContextExecutor;
import scala.concurrent.duration.Duration;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
@ -40,31 +35,16 @@ public abstract class AbstractContextAwareMsgProcessor {
this.systemContext = systemContext;
}
private Scheduler getScheduler() {
private ScheduledExecutorService getScheduler() {
return systemContext.getScheduler();
}
private ExecutionContextExecutor getSystemDispatcher() {
return systemContext.getActorSystem().dispatcher();
protected void schedulePeriodicMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs, long periodInMs) {
systemContext.schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs);
}
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs) {
schedulePeriodicMsgWithDelay(msg, delayInMs, periodInMs, ctx.self());
protected void scheduleMsgWithDelay(TbActorCtx ctx, TbActorMsg msg, long delayInMs) {
systemContext.scheduleMsgWithDelay(ctx, msg, delayInMs);
}
private void schedulePeriodicMsgWithDelay(Object msg, long delayInMs, long periodInMs, ActorRef target) {
log.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs) {
scheduleMsgWithDelay(msg, delayInMs, ctx.self());
}
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
}

21
application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java

@ -15,16 +15,15 @@
*/
package org.thingsboard.server.actors.shared;
import akka.actor.ActorContext;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
@Slf4j
@ -42,38 +41,38 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
public abstract String getComponentName();
public abstract void start(ActorContext context) throws Exception;
public abstract void start(TbActorCtx context) throws Exception;
public abstract void stop(ActorContext context) throws Exception;
public abstract void stop(TbActorCtx context) throws Exception;
public abstract void onPartitionChangeMsg(PartitionChangeMsg msg) throws Exception;
public void onCreated(ActorContext context) throws Exception {
public void onCreated(TbActorCtx context) throws Exception {
start(context);
}
public void onUpdate(ActorContext context) throws Exception {
public void onUpdate(TbActorCtx context) throws Exception {
restart(context);
}
public void onActivate(ActorContext context) throws Exception {
public void onActivate(TbActorCtx context) throws Exception {
restart(context);
}
public void onSuspend(ActorContext context) throws Exception {
public void onSuspend(TbActorCtx context) throws Exception {
stop(context);
}
public void onStop(ActorContext context) throws Exception {
public void onStop(TbActorCtx context) throws Exception {
stop(context);
}
private void restart(ActorContext context) throws Exception {
private void restart(TbActorCtx context) throws Exception {
stop(context);
start(context);
}
public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) {
public void scheduleStatsPersistTick(TbActorCtx context, long statsPersistFrequency) {
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
}

40
application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java

@ -19,10 +19,15 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbStringActorId;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
@Slf4j
@ -35,24 +40,17 @@ public class StatsActor extends ContextAwareActor {
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg\
return false;
}
@Override
public void onReceive(Object msg) {
protected boolean doProcess(TbActorMsg msg) {
log.debug("Received message: {}", msg);
if (msg instanceof StatsPersistMsg) {
try {
onStatsPersistMsg((StatsPersistMsg) msg);
} catch (Exception e) {
log.warn("Failed to persist statistics: {}", msg, e);
}
if (msg.getMsgType().equals(MsgType.STATS_PERSIST_MSG)) {
onStatsPersistMsg((StatsPersistMsg) msg);
return true;
} else {
return false;
}
}
public void onStatsPersistMsg(StatsPersistMsg msg) throws Exception {
public void onStatsPersistMsg(StatsPersistMsg msg) {
Event event = new Event();
event.setEntityId(msg.getEntityId());
event.setTenantId(msg.getTenantId());
@ -65,15 +63,21 @@ public class StatsActor extends ContextAwareActor {
return mapper.createObjectNode().put("server", serviceId).put("messagesProcessed", messagesProcessed).put("errorsOccurred", errorsOccurred);
}
public static class ActorCreator extends ContextBasedCreator<StatsActor> {
private static final long serialVersionUID = 1L;
public static class ActorCreator extends ContextBasedCreator {
private final String actorId;
public ActorCreator(ActorSystemContext context) {
public ActorCreator(ActorSystemContext context, String actorId) {
super(context);
this.actorId = actorId;
}
@Override
public TbActorId createActorId() {
return new TbStringActorId(actorId);
}
@Override
public StatsActor create() {
public TbActor createActor() {
return new StatsActor(context);
}
}

10
application/src/main/java/org/thingsboard/server/actors/stats/StatsPersistMsg.java

@ -20,13 +20,21 @@ import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
@AllArgsConstructor
@Getter
@ToString
public final class StatsPersistMsg {
public final class StatsPersistMsg implements TbActorMsg {
private long messagesProcessed;
private long errorsOccurred;
private TenantId tenantId;
private EntityId entityId;
@Override
public MsgType getMsgType() {
return MsgType.STATS_PERSIST_MSG;
}
}

134
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -15,16 +15,16 @@
*/
package org.thingsboard.server.actors.tenant;
import akka.actor.ActorInitializationException;
import akka.actor.ActorRef;
import akka.actor.LocalActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.Terminated;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbActorNotRegisteredException;
import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
@ -32,6 +32,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
@ -46,32 +47,25 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType;
import scala.concurrent.duration.Duration;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
public class TenantActor extends RuleChainManagerActor {
private final BiMap<DeviceId, ActorRef> deviceActors;
private boolean isRuleEngineForCurrentTenant;
private boolean isCore;
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, tenantId);
this.deviceActors = HashBiMap.create();
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
boolean cantFindTenant = false;
@Override
public void preStart() {
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
log.info("[{}] Starting tenant actor.", tenantId);
try {
Tenant tenant = systemContext.getTenantService().findTenantById(tenantId);
@ -101,16 +95,18 @@ public class TenantActor extends RuleChainManagerActor {
}
} catch (Exception e) {
log.warn("[{}] Unknown failure", tenantId, e);
// TODO: throw this in 3.1?
// throw new TbActorException("Failed to init actor", e);
}
}
@Override
public void postStop() {
public void destroy() {
log.info("[{}] Stopping tenant actor.", tenantId);
}
@Override
protected boolean process(TbActorMsg msg) {
protected boolean doProcess(TbActorMsg msg) {
if (cantFindTenant) {
log.info("[{}] Processing missing Tenant msg: {}", tenantId, msg);
if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) {
@ -127,13 +123,13 @@ public class TenantActor extends RuleChainManagerActor {
//To Rule Chain Actors
broadcast(msg);
} else if (ServiceType.TB_CORE.equals(serviceType)) {
//To Device Actors
List<DeviceId> repartitionedDevices =
deviceActors.keySet().stream().filter(deviceId -> !isMyPartition(deviceId)).collect(Collectors.toList());
for (DeviceId deviceId : repartitionedDevices) {
ActorRef deviceActor = deviceActors.remove(deviceId);
context().stop(deviceActor);
}
List<TbActorId> deviceActorIds = ctx.filterChildren(new TbEntityTypeActorIdPredicate(EntityType.DEVICE) {
@Override
protected boolean testEntityId(EntityId entityId) {
return super.testEntityId(entityId) && !isMyPartition(entityId);
}
});
deviceActorIds.forEach(id -> ctx.stop(id));
}
break;
case COMPONENT_LIFE_CYCLE_MSG:
@ -143,12 +139,14 @@ public class TenantActor extends RuleChainManagerActor {
onQueueToRuleEngineMsg((QueueToRuleEngineMsg) msg);
break;
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg, false);
break;
case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg);
onToDeviceActorMsg((DeviceAwareMsg) msg, true);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg);
@ -159,8 +157,8 @@ public class TenantActor extends RuleChainManagerActor {
return true;
}
private boolean isMyPartition(DeviceId deviceId) {
return systemContext.resolve(ServiceType.TB_CORE, tenantId, deviceId).isMyPartition();
private boolean isMyPartition(EntityId entityId) {
return systemContext.resolve(ServiceType.TB_CORE, tenantId, entityId).isMyPartition();
}
private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
@ -171,16 +169,15 @@ public class TenantActor extends RuleChainManagerActor {
TbMsg tbMsg = msg.getTbMsg();
if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) {
getRootChainActor().tell(msg, self());
getRootChainActor().tell(msg);
} else {
tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!"));
log.info("[{}] No Root Chain: {}", tenantId, msg);
}
} else {
ActorRef ruleChainActor = get(tbMsg.getRuleChainId());
if (ruleChainActor != null) {
ruleChainActor.tell(msg, self());
} else {
try {
ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg);
} catch (TbActorNotRegisteredException ex) {
log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId());
//TODO: 3.1 Log it to dead letters queue;
tbMsg.getCallback().onSuccess();
@ -189,63 +186,46 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onRuleChainMsg(RuleChainAwareMsg msg) {
getOrCreateActor(context(), msg.getRuleChainId()).tell(msg, self());
getOrCreateActor(msg.getRuleChainId()).tell(msg);
}
private void onToDeviceActorMsg(DeviceAwareMsg msg) {
private void onToDeviceActorMsg(DeviceAwareMsg msg, boolean priority) {
if (!isCore) {
log.warn("RECEIVED INVALID MESSAGE: {}", msg);
}
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
TbActorRef deviceActor = getOrCreateDeviceActor(msg.getDeviceId());
if (priority) {
deviceActor.tellWithHighPriority(msg);
} else {
deviceActor.tell(msg);
}
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
if (isRuleEngineForCurrentTenant) {
ActorRef target = getEntityActorRef(msg.getEntityId());
TbActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
RuleChain ruleChain = systemContext.getRuleChainService().
findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
if (ruleChain != null && ruleChain.getType().equals(RuleChainType.SYSTEM)) {
if (ruleChain != null && ruleChain.getType().equals(RuleChainType.CORE)) {
visit(ruleChain, target);
}
}
target.tell(msg, ActorRef.noSender());
target.tellWithHighPriority(msg);
} else {
log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg);
}
}
}
private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
return deviceActors.computeIfAbsent(deviceId, k -> {
log.debug("[{}][{}] Creating device actor.", tenantId, deviceId);
ActorRef deviceActor = context().actorOf(Props.create(new DeviceActorCreator(systemContext, tenantId, deviceId))
.withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME)
, deviceId.toString());
context().watch(deviceActor);
log.debug("[{}][{}] Created device actor: {}.", tenantId, deviceId, deviceActor);
return deviceActor;
});
}
@Override
protected void processTermination(Terminated message) {
ActorRef terminated = message.actor();
if (terminated instanceof LocalActorRef) {
boolean removed = deviceActors.inverse().remove(terminated) != null;
if (removed) {
log.debug("[{}] Removed actor:", terminated);
} else {
log.debug("Removed actor was not found in the device map!");
}
} else {
throw new IllegalStateException("Remote actors are not supported!");
}
private TbActorRef getOrCreateDeviceActor(DeviceId deviceId) {
return ctx.getOrCreateChildActor(new TbEntityActorId(deviceId),
() -> DefaultActorService.DEVICE_DISPATCHER_NAME,
() -> new DeviceActorCreator(systemContext, tenantId, deviceId));
}
public static class ActorCreator extends ContextBasedCreator<TenantActor> {
private static final long serialVersionUID = 1L;
public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId;
@ -255,18 +235,14 @@ public class TenantActor extends RuleChainManagerActor {
}
@Override
public TenantActor create() {
return new TenantActor(context, tenantId);
public TbActorId createActorId() {
return new TbEntityActorId(tenantId);
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> {
log.warn("[{}] Unknown failure", tenantId, t);
if (t instanceof ActorInitializationException) {
return SupervisorStrategy.stop();
} else {
return SupervisorStrategy.resume();
@Override
public TbActor createActor() {
return new TenantActor(context, tenantId);
}
});
}
}

12
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -91,6 +91,9 @@ public class AlarmController extends BaseController {
logEntityAction(savedAlarm.getId(), savedAlarm,
getCurrentUser().getCustomerId(),
alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
sendNotificationMsgToEdgeService(getTenantId(), savedAlarm.getId(), alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED);
return savedAlarm;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ALARM), alarm,
@ -107,8 +110,11 @@ public class AlarmController extends BaseController {
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
checkAlarmId(alarmId, Operation.WRITE);
sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.DELETED);
return alarmService.deleteAlarm(getTenantId(), alarmId);
} catch (Exception e) {
} catch (Exception e) {
throw handleException(e);
}
}
@ -125,6 +131,8 @@ public class AlarmController extends BaseController {
alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get();
alarm.setAckTs(ackTs);
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.ALARM_ACK);
} catch (Exception e) {
throw handleException(e);
}
@ -142,6 +150,8 @@ public class AlarmController extends BaseController {
alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get();
alarm.setClearTs(clearTs);
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
sendNotificationMsgToEdgeService(getTenantId(), alarmId, ActionType.ALARM_CLEAR);
} catch (Exception e) {
throw handleException(e);
}

11
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -27,12 +27,14 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
@ -86,6 +88,9 @@ public class AssetController extends BaseController {
Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
sendNotificationMsgToEdgeService(savedAsset.getTenantId(), null,
savedAsset.getId(), EdgeEventType.ASSET, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED);
logEntityAction(savedAsset.getId(), savedAsset,
savedAsset.getCustomerId(),
asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
@ -112,6 +117,7 @@ public class AssetController extends BaseController {
asset.getCustomerId(),
ActionType.DELETED, null, strAssetId);
sendNotificationMsgToEdgeService(getTenantId(), null, assetId, EdgeEventType.ASSET, ActionType.DELETED);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET),
null,
@ -354,6 +360,8 @@ public class AssetController extends BaseController {
savedAsset.getCustomerId(),
ActionType.ASSIGNED_TO_EDGE, null, strAssetId, strEdgeId, edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedAsset.getId(), EdgeEventType.ASSET, ActionType.ASSIGNED_TO_EDGE);
return savedAsset;
} catch (Exception e) {
@ -385,6 +393,9 @@ public class AssetController extends BaseController {
asset.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strAssetId, edge.getId().toString(), edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedAsset.getId(),
EdgeEventType.ASSET, ActionType.UNASSIGNED_FROM_EDGE);
return savedAsset;
} catch (Exception e) {

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

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
@ -66,6 +67,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode;
@ -83,6 +85,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.DataValidationException;
@ -95,10 +98,12 @@ import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
@ -108,6 +113,7 @@ import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException;
import javax.management.relation.Relation;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
@ -200,6 +206,12 @@ public abstract class BaseController {
@Autowired
protected EdgeService edgeService;
@Autowired
protected EdgeNotificationService edgeNotificationService;
@Autowired
protected EdgeEventService edgeEventService;
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@ -518,6 +530,24 @@ public abstract class BaseController {
}
}
ComponentDescriptor checkComponentDescriptorByClazz(String clazz) throws ThingsboardException {
try {
log.debug("[{}] Lookup component descriptor", clazz);
return checkNotNull(componentDescriptorService.getComponent(clazz));
} catch (Exception e) {
throw handleException(e, false);
}
}
List<ComponentDescriptor> checkComponentDescriptorsByType(ComponentType type, RuleChainType ruleChainType) throws ThingsboardException {
try {
log.debug("[{}] Lookup component descriptors", type);
return componentDescriptorService.getComponents(type, ruleChainType);
} catch (Exception e) {
throw handleException(e, false);
}
}
List<ComponentDescriptor> checkComponentDescriptorsByTypes(Set<ComponentType> types, RuleChainType ruleChainType) throws ThingsboardException {
try {
log.debug("[{}] Lookup component descriptors", types);
@ -559,7 +589,6 @@ public abstract class BaseController {
}
if (e == null) {
pushEntityActionToRuleEngine(entityId, entity, user, customerId, actionType, additionalInfo);
// TODO: voba - refactor to push events to edge queue directly, instead of the rule engine flow
}
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}
@ -606,10 +635,6 @@ public abstract class BaseController {
case UNASSIGNED_FROM_EDGE:
msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;
break;
case CREDENTIALS_UPDATED:
//TODO: voba - this is not efficient way to do this. Refactor on later stages
msgType = DataConstants.ENTITY_UPDATED;
break;
}
if (!StringUtils.isEmpty(msgType)) {
try {
@ -698,5 +723,46 @@ public abstract class BaseController {
return result;
}
protected void sendNotificationMsgToEdgeService(TenantId tenantId, EntityRelation relation, ActionType edgeEventAction) {
try {
sendNotificationMsgToEdgeService(tenantId, null, null, json.writeValueAsString(relation), EdgeEventType.RELATION, edgeEventAction);
} catch (Exception e) {
log.warn("Failed to push relation to core: {}", relation, e);
}
}
protected void sendNotificationMsgToEdgeService(TenantId tenantId, EntityId entityId, ActionType edgeEventAction) {
EdgeEventType edgeEventType = edgeEventService.getEdgeEventTypeByEntityType(entityId.getEntityType());
if (edgeEventType != null) {
sendNotificationMsgToEdgeService(tenantId, null, entityId, null, edgeEventType, edgeEventAction);
}
}
protected void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventType edgeEventType, ActionType edgeEventAction) {
sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, edgeEventType, edgeEventAction);
}
private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String entityBody, EdgeEventType edgeEventType, ActionType edgeEventAction) {
TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setEdgeEventType(edgeEventType.name());
builder.setEdgeEventAction(edgeEventAction.name());
if (entityId != null) {
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setEntityType(entityId.getEntityType().name());
}
if (edgeId != null) {
builder.setEdgeIdMSB(edgeId.getId().getMostSignificantBits());
builder.setEdgeIdLSB(edgeId.getId().getLeastSignificantBits());
}
if (entityBody != null) {
builder.setEntityBody(entityBody);
}
TransportProtos.EdgeNotificationMsgProto msg = builder.build();
tbClusterService.pushMsgToCore(tenantId, entityId != null ? entityId : tenantId,
TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
}
}

47
application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.controller;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@ -25,8 +26,8 @@ import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.HashSet;
import java.util.List;
@ -37,24 +38,56 @@ import java.util.Set;
@RequestMapping("/api")
public class ComponentDescriptorController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET)
@ResponseBody
public ComponentDescriptor getComponentDescriptorByClazz(@PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException {
checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz);
try {
return checkComponentDescriptorByClazz(strComponentDescriptorClazz);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET)
@ResponseBody
public List<ComponentDescriptor> getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType,
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
checkParameter("componentType", strComponentType);
try {
return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType));
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components/{ruleChainType}", params = {"componentTypes"}, method = RequestMethod.GET)
@RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
@ResponseBody
public List<ComponentDescriptor> getComponentDescriptorsByTypes(@PathVariable("ruleChainType") String strRuleChainType,
@RequestParam("componentTypes") String[] strComponentTypes) throws ThingsboardException {
public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes,
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
checkArrayParameter("componentTypes", strComponentTypes);
checkParameter("ruleChainType", strRuleChainType);
try {
RuleChainType ruleChainType = RuleChainType.valueOf(strRuleChainType);
Set<ComponentType> componentTypes = new HashSet<>();
for (String strComponentType : strComponentTypes) {
componentTypes.add(ComponentType.valueOf(strComponentType));
}
return checkComponentDescriptorsByTypes(componentTypes, ruleChainType);
return checkComponentDescriptorsByTypes(componentTypes, getRuleChainType(strRuleChainType));
} catch (Exception e) {
throw handleException(e);
}
}
private RuleChainType getRuleChainType(String strRuleChainType) {
RuleChainType ruleChainType;
if (StringUtils.isEmpty(strRuleChainType)) {
ruleChainType = RuleChainType.CORE;
} else {
ruleChainType = RuleChainType.valueOf(strRuleChainType);
}
return ruleChainType;
}
}

10
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.ShortCustomerInfo;
import org.thingsboard.server.common.data.ShortEdgeInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@ -116,6 +117,9 @@ public class DashboardController extends BaseController {
null,
dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
sendNotificationMsgToEdgeService(savedDashboard.getTenantId(), null, savedDashboard.getId(),
EdgeEventType.DASHBOARD, savedDashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED);
return savedDashboard;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD), dashboard,
@ -139,6 +143,7 @@ public class DashboardController extends BaseController {
null,
ActionType.DELETED, null, strDashboardId);
sendNotificationMsgToEdgeService(getTenantId(), null, dashboardId, EdgeEventType.DASHBOARD, ActionType.DELETED);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DASHBOARD),
@ -495,6 +500,8 @@ public class DashboardController extends BaseController {
null,
ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, strEdgeId, edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedDashboard.getId(),
EdgeEventType.DASHBOARD, ActionType.ASSIGNED_TO_EDGE);
return savedDashboard;
} catch (Exception e) {
@ -526,6 +533,9 @@ public class DashboardController extends BaseController {
null,
ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edge.getId().toString(), edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedDashboard.getId(),
EdgeEventType.DASHBOARD, ActionType.UNASSIGNED_FROM_EDGE);
return savedDashboard;
} catch (Exception e) {

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

@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
@ -57,6 +58,7 @@ import org.thingsboard.server.dao.device.claim.ClaimResponse;
import org.thingsboard.server.dao.device.claim.ClaimResult;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
@ -106,6 +108,9 @@ public class DeviceController extends BaseController {
tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null);
sendNotificationMsgToEdgeService(savedDevice.getTenantId(), null, savedDevice.getId(),
EdgeEventType.DEVICE, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED);
logEntityAction(savedDevice.getId(), savedDevice,
savedDevice.getCustomerId(),
device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
@ -137,6 +142,8 @@ public class DeviceController extends BaseController {
device.getCustomerId(),
ActionType.DELETED, null, strDeviceId);
sendNotificationMsgToEdgeService(getTenantId(), null, deviceId, EdgeEventType.DEVICE, ActionType.DELETED);
deviceStateService.onDeviceDeleted(device);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE),
@ -260,6 +267,8 @@ public class DeviceController extends BaseController {
tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId()), null);
sendNotificationMsgToEdgeService(getTenantId(), null, device.getId(), EdgeEventType.DEVICE, ActionType.CREDENTIALS_UPDATED);
logEntityAction(device.getId(), device,
device.getCustomerId(),
ActionType.CREDENTIALS_UPDATED, null, deviceCredentials);
@ -509,6 +518,8 @@ public class DeviceController extends BaseController {
savedDevice.getCustomerId(),
ActionType.ASSIGNED_TO_EDGE, null, strDeviceId, strEdgeId, edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedDevice.getId(), EdgeEventType.DEVICE, ActionType.ASSIGNED_TO_EDGE);
return savedDevice;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), null,
@ -538,6 +549,8 @@ public class DeviceController extends BaseController {
device.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strDeviceId, edge.getId().toString(), edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedDevice.getId(), EdgeEventType.DEVICE, ActionType.UNASSIGNED_FROM_EDGE);
return savedDevice;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.DEVICE), null,

7
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -97,7 +97,7 @@ public class EdgeController extends BaseController {
if (created) {
ruleChainService.assignRuleChainToEdge(tenantId, defaultRootEdgeRuleChain.getId(), savedEdge.getId());
edgeService.setEdgeRootRuleChain(tenantId, savedEdge, defaultRootEdgeRuleChain.getId());
edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, defaultRootEdgeRuleChain.getId());
edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId());
}
@ -257,8 +257,7 @@ public class EdgeController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET)
@ResponseBody
public Edge getTenantEdge(
@RequestParam String edgeName) throws ThingsboardException {
public Edge getTenantEdge(@RequestParam String edgeName) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName));
@ -283,7 +282,7 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE,
edge.getId(), edge);
Edge updatedEdge = edgeService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId);
Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId);
logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null);

9
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java

@ -24,7 +24,9 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
@ -63,10 +65,13 @@ public class EntityRelationController extends BaseController {
relation.setTypeGroup(RelationTypeGroup.COMMON);
}
relationService.saveRelation(getTenantId(), relation);
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, null, relation);
logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, null, relation);
sendNotificationMsgToEdgeService(getTenantId(), relation, ActionType.RELATION_ADD_OR_UPDATE);
} catch (Exception e) {
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, e, relation);
@ -104,6 +109,8 @@ public class EntityRelationController extends BaseController {
ActionType.RELATION_DELETED, null, relation);
logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_DELETED, null, relation);
sendNotificationMsgToEdgeService(getTenantId(), relation, ActionType.RELATION_DELETED);
} catch (Exception e) {
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_DELETED, e, relation);
@ -125,6 +132,8 @@ public class EntityRelationController extends BaseController {
try {
relationService.deleteEntityRelations(getTenantId(), entityId);
logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, null);
sendNotificationMsgToEdgeService(getTenantId(), entityId, ActionType.RELATIONS_DELETED);
} catch (Exception e) {
logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, e);
throw handleException(e);

13
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
@ -116,6 +117,9 @@ public class EntityViewController extends BaseController {
logEntityAction(savedEntityView.getId(), savedEntityView, null,
entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
sendNotificationMsgToEdgeService(getTenantId(), null, savedEntityView.getId(),
EdgeEventType.ENTITY_VIEW, entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED);
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null,
@ -185,6 +189,8 @@ public class EntityViewController extends BaseController {
entityViewService.deleteEntityView(getTenantId(), entityViewId);
logEntityAction(entityViewId, entityView, entityView.getCustomerId(),
ActionType.DELETED, null, strEntityViewId);
sendNotificationMsgToEdgeService(getTenantId(), null, entityViewId, EdgeEventType.ENTITY_VIEW, ActionType.DELETED);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW),
null,
@ -389,6 +395,10 @@ public class EntityViewController extends BaseController {
logEntityAction(entityViewId, savedEntityView,
savedEntityView.getCustomerId(),
ActionType.ASSIGNED_TO_EDGE, null, strEntityViewId, strEdgeId, edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedEntityView.getId(),
EdgeEventType.ENTITY_VIEW, ActionType.ASSIGNED_TO_EDGE);
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
@ -417,6 +427,9 @@ public class EntityViewController extends BaseController {
entityView.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strEntityViewId, edge.getId().toString(), edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedEntityView.getId(),
EdgeEventType.ENTITY_VIEW, ActionType.UNASSIGNED_FROM_EDGE);
return savedEntityView;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,

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

@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
@ -134,7 +135,7 @@ public class RuleChainController extends BaseController {
RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
if (RuleChainType.SYSTEM.equals(savedRuleChain.getType())) {
if (RuleChainType.CORE.equals(savedRuleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
}
@ -143,6 +144,12 @@ public class RuleChainController extends BaseController {
null,
created ? ActionType.ADDED : ActionType.UPDATED, null);
if (RuleChainType.EDGE.equals(savedRuleChain.getType())) {
sendNotificationMsgToEdgeService(savedRuleChain.getTenantId(), null,
savedRuleChain.getId(), EdgeEventType.RULE_CHAIN,
savedRuleChain.getId() == null ? ActionType.ADDED : ActionType.UPDATED);
}
return savedRuleChain;
} catch (Exception e) {
@ -209,7 +216,7 @@ public class RuleChainController extends BaseController {
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData));
if (RuleChainType.SYSTEM.equals(ruleChain.getType())) {
if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
}
@ -217,6 +224,13 @@ public class RuleChainController extends BaseController {
null,
ActionType.UPDATED, null, ruleChainMetaData);
if (RuleChainType.EDGE.equals(ruleChain.getType())) {
sendNotificationMsgToEdgeService(ruleChain.getTenantId(),
null,
ruleChain.getId(), EdgeEventType.RULE_CHAIN,
ActionType.UPDATED);
}
return savedRuleChainMetaData;
} catch (Exception e) {
@ -243,7 +257,7 @@ public class RuleChainController extends BaseController {
RuleChainType type = RuleChainType.valueOf(typeStr);
return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink));
} else {
return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.SYSTEM, pageLink));
return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, pageLink));
}
} catch (Exception e) {
throw handleException(e);
@ -267,7 +281,7 @@ public class RuleChainController extends BaseController {
referencingRuleChainIds.remove(ruleChain.getId());
if (RuleChainType.SYSTEM.equals(ruleChain.getType())) {
if (RuleChainType.CORE.equals(ruleChain.getType())) {
referencingRuleChainIds.forEach(referencingRuleChainId ->
tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED));
@ -278,6 +292,11 @@ public class RuleChainController extends BaseController {
null,
ActionType.DELETED, null, strRuleChainId);
if (RuleChainType.EDGE.equals(ruleChain.getType())) {
sendNotificationMsgToEdgeService(ruleChain.getTenantId(), null,
ruleChain.getId(), EdgeEventType.RULE_CHAIN, ActionType.DELETED);
}
} catch (Exception e) {
logEntityAction(emptyId(EntityType.RULE_CHAIN),
null,
@ -407,6 +426,8 @@ public class RuleChainController extends BaseController {
null,
ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, strEdgeId, edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedRuleChain.getId(),
EdgeEventType.RULE_CHAIN, ActionType.ASSIGNED_TO_EDGE);
return savedRuleChain;
} catch (Exception e) {
@ -438,6 +459,9 @@ public class RuleChainController extends BaseController {
null,
ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edge.getId().toString(), edge.getName());
sendNotificationMsgToEdgeService(getTenantId(), edgeId, savedRuleChain.getId(),
EdgeEventType.RULE_CHAIN, ActionType.UNASSIGNED_FROM_EDGE);
return savedRuleChain;
} catch (Exception e) {

84
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -306,88 +306,4 @@ public class UserController extends BaseController {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/user/{userId}", method = RequestMethod.POST)
@ResponseBody
public User assignUserToEdge(@PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(USER_ID) String strUserId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(USER_ID, strUserId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
UserId userId = new UserId(toUUID(strUserId));
checkUserId(userId, Operation.ASSIGN_TO_EDGE);
User savedUser = checkNotNull(userService.assignUserToEdge(getTenantId(), userId, edgeId));
logEntityAction(userId, savedUser,
savedUser.getCustomerId(),
ActionType.ASSIGNED_TO_EDGE, null, strUserId, strEdgeId, edge.getName());
return savedUser;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.USER), null,
null,
ActionType.ASSIGNED_TO_EDGE, e, strUserId, strEdgeId);
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/user/{userId}", method = RequestMethod.DELETE)
@ResponseBody
public User unassignUserFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(USER_ID) String strUserId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(USER_ID, strUserId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
UserId userId = new UserId(toUUID(strUserId));
User user = checkUserId(userId, Operation.UNASSIGN_FROM_EDGE);
User savedUser = checkNotNull(userService.unassignUserFromEdge(getTenantId(), userId, edgeId));
logEntityAction(userId, savedUser,
savedUser.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strUserId, edge.getId().toString(), edge.getName());
return savedUser;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.USER), null,
null,
ActionType.UNASSIGNED_FROM_EDGE, e, strUserId);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/users", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
public TimePageData<User> getEdgeUsers(
@PathVariable(EDGE_ID) String strEdgeId,
@RequestParam int limit,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) String offset) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
checkEdgeId(edgeId, Operation.READ);
TimePageLink pageLink = createPageLink(limit, startTime, endTime, ascOrder, offset);
return checkNotNull(userService.findUsersByTenantIdAndEdgeId(tenantId, edgeId, pageLink).get());
} catch (Exception e) {
throw handleException(e);
}
}
}

36
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java

@ -40,7 +40,6 @@ import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@ -65,7 +64,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
private Map<String, ComponentDescriptor> components = new HashMap<>();
private Map<ComponentType, List<ComponentDescriptor>> systemComponentsMap = new HashMap<>();
private Map<ComponentType, List<ComponentDescriptor>> coreComponentsMap = new HashMap<>();
private Map<ComponentType, List<ComponentDescriptor>> edgeComponentsMap = new HashMap<>();
@ -116,8 +115,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
}
private void putComponentIntoMaps(ComponentType type, RuleNode ruleNodeAnnotation, ComponentDescriptor component) {
if (ruleChainTypeContainsArray(RuleChainType.SYSTEM, ruleNodeAnnotation.ruleChainTypes())) {
systemComponentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
if (ruleChainTypeContainsArray(RuleChainType.CORE, ruleNodeAnnotation.ruleChainTypes())) {
coreComponentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
}
if (ruleChainTypeContainsArray(RuleChainType.EDGE, ruleNodeAnnotation.ruleChainTypes())) {
edgeComponentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
@ -223,10 +222,30 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
log.info("Found following definitions: {}", components.values());
}
@Override
public List<ComponentDescriptor> getComponents(ComponentType type, RuleChainType ruleChainType) {
if (RuleChainType.CORE.equals(ruleChainType)) {
if (coreComponentsMap.containsKey(type)) {
return Collections.unmodifiableList(coreComponentsMap.get(type));
} else {
return Collections.emptyList();
}
} else if (RuleChainType.EDGE.equals(ruleChainType)) {
if (edgeComponentsMap.containsKey(type)) {
return Collections.unmodifiableList(edgeComponentsMap.get(type));
} else {
return Collections.emptyList();
}
} else {
log.error("Unsupported rule chain type {}", ruleChainType);
throw new RuntimeException("Unsupported rule chain type " + ruleChainType);
}
}
@Override
public List<ComponentDescriptor> getComponents(Set<ComponentType> types, RuleChainType ruleChainType) {
if (RuleChainType.SYSTEM.equals(ruleChainType)) {
return getComponents(types, systemComponentsMap);
if (RuleChainType.CORE.equals(ruleChainType)) {
return getComponents(types, coreComponentsMap);
} else if (RuleChainType.EDGE.equals(ruleChainType)) {
return getComponents(types, edgeComponentsMap);
} else {
@ -235,6 +254,11 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
}
}
@Override
public Optional<ComponentDescriptor> getComponent(String clazz) {
return Optional.ofNullable(components.get(clazz));
}
private List<ComponentDescriptor> getComponents(Set<ComponentType> types, Map<ComponentType, List<ComponentDescriptor>> componentsMap) {
List<ComponentDescriptor> result = new ArrayList<>();
types.stream().filter(componentsMap::containsKey).forEach(type -> {

4
application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
@ -29,6 +30,9 @@ public interface ComponentDiscoveryService {
void discoverComponents();
List<ComponentDescriptor> getComponents(ComponentType type, RuleChainType ruleChainType);
List<ComponentDescriptor> getComponents(Set<ComponentType> types, RuleChainType ruleChainType);
Optional<ComponentDescriptor> getComponent(String clazz);
}

323
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -0,0 +1,323 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@Service
@TbCoreComponent
@Slf4j
public class DefaultEdgeNotificationService implements EdgeNotificationService {
private static final ObjectMapper mapper = new ObjectMapper();
@Autowired
private EdgeService edgeService;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private AlarmService alarmService;
@Autowired
private RelationService relationService;
@Autowired
private EdgeEventService edgeEventService;
@Autowired
private DbCallbackExecutorService dbCallbackExecutorService;
private ExecutorService tsCallBackExecutor;
@PostConstruct
public void initExecutor() {
tsCallBackExecutor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (tsCallBackExecutor != null) {
tsCallBackExecutor.shutdownNow();
}
}
@Override
public TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink) {
return edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink);
}
@Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, ActionType.UPDATED, ruleChainId, null);
return savedEdge;
}
private void saveEdgeEvent(TenantId tenantId,
EdgeId edgeId,
EdgeEventType edgeEventType,
ActionType edgeEventAction,
EntityId entityId,
JsonNode entityBody) {
log.debug("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], edgeEventType [{}], edgeEventAction[{}], entityId [{}], entityBody [{}]",
tenantId, edgeId, edgeEventType, edgeEventAction, entityId, entityBody);
EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setEdgeId(edgeId);
edgeEvent.setTenantId(tenantId);
edgeEvent.setEdgeEventType(edgeEventType);
edgeEvent.setEdgeEventAction(edgeEventAction.name());
if (entityId != null) {
edgeEvent.setEntityId(entityId.getId());
}
edgeEvent.setEntityBody(entityBody);
edgeEventService.saveAsync(edgeEvent);
}
@Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
try {
TenantId tenantId = new TenantId(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType edgeEventType = EdgeEventType.valueOf(edgeNotificationMsg.getEdgeEventType());
switch (edgeEventType) {
// TODO: voba - handle edge updates
// case EDGE:
case ASSET:
case DEVICE:
case ENTITY_VIEW:
case DASHBOARD:
case RULE_CHAIN:
processEntities(tenantId, edgeNotificationMsg);
break;
case ALARM:
processAlarm(tenantId, edgeNotificationMsg);
break;
case RELATION:
processRelation(tenantId, edgeNotificationMsg);
break;
default:
log.debug("Edge event type [{}] is not designed to be pushed to edge", edgeEventType);
}
} catch (Exception e) {
callback.onFailure(e);
log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, e);
} finally {
callback.onSuccess();
}
}
private void processEntities(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
ActionType edgeEventActionType = ActionType.valueOf(edgeNotificationMsg.getEdgeEventAction());
EdgeEventType edgeEventType = EdgeEventType.valueOf(edgeNotificationMsg.getEdgeEventType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(edgeEventType, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
switch (edgeEventActionType) {
// TODO: voba - ADDED is not required for CE version ?
// case ADDED:
case UPDATED:
ListenableFuture<List<EdgeId>> edgeIdsFuture = findRelatedEdgeIdsByEntityId(tenantId, entityId);
Futures.transform(edgeIdsFuture, edgeIds -> {
if (edgeIds != null && !edgeIds.isEmpty()) {
for (EdgeId edgeId : edgeIds) {
try {
saveEdgeEvent(tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, null);
if (edgeEventType.equals(EdgeEventType.RULE_CHAIN)) {
RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(tenantId, new RuleChainId(entityId.getId()));
saveEdgeEvent(tenantId, edgeId, EdgeEventType.RULE_CHAIN_METADATA, edgeEventActionType, ruleChainMetaData.getRuleChainId(), null);
}
} catch (Exception e) {
log.error("[{}] Failed to push event to edge, edgeId [{}], edgeEventType [{}], edgeEventActionType [{}], entityId [{}]",
tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, e);
}
}
}
return null;
}, dbCallbackExecutorService);
break;
case DELETED:
TextPageData<Edge> edgesByTenantId = edgeService.findEdgesByTenantId(tenantId, new TextPageLink(Integer.MAX_VALUE));
if (edgesByTenantId != null && edgesByTenantId.getData() != null && !edgesByTenantId.getData().isEmpty()) {
for (Edge edge : edgesByTenantId.getData()) {
saveEdgeEvent(tenantId, edge.getId(), edgeEventType, edgeEventActionType, entityId, null);
}
}
break;
case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
saveEdgeEvent(tenantId, edgeId, edgeEventType, edgeEventActionType, entityId, null);
break;
case RELATIONS_DELETED:
// TODO: voba - add support for relations deleted
break;
}
}
private void processAlarm(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
Futures.transform(alarmFuture, alarm -> {
if (alarm != null) {
EdgeEventType edgeEventType = getEdgeQueueTypeByEntityType(alarm.getOriginator().getEntityType());
if (edgeEventType != null) {
ListenableFuture<List<EdgeId>> relatedEdgeIdsByEntityIdFuture = findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator());
Futures.transform(relatedEdgeIdsByEntityIdFuture, relatedEdgeIdsByEntityId -> {
if (relatedEdgeIdsByEntityId != null) {
for (EdgeId edgeId : relatedEdgeIdsByEntityId) {
saveEdgeEvent(tenantId,
edgeId,
EdgeEventType.ALARM,
ActionType.valueOf(edgeNotificationMsg.getEdgeEventAction()),
alarmId,
null);
}
}
return null;
}, dbCallbackExecutorService);
}
}
return null;
}, dbCallbackExecutorService);
}
private void processRelation(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
EntityRelation entityRelation = mapper.convertValue(edgeNotificationMsg.getEntityBody(), EntityRelation.class);
List<ListenableFuture<List<EdgeId>>> futures = new ArrayList<>();
futures.add(findRelatedEdgeIdsByEntityId(tenantId, entityRelation.getTo()));
futures.add(findRelatedEdgeIdsByEntityId(tenantId, entityRelation.getFrom()));
ListenableFuture<List<List<EdgeId>>> combinedFuture = Futures.allAsList(futures);
Futures.transform(combinedFuture, listOfListsEdgeIds -> {
Set<EdgeId> uniqueEdgeIds = new HashSet<>();
if (listOfListsEdgeIds != null && !listOfListsEdgeIds.isEmpty()) {
for (List<EdgeId> listOfListsEdgeId : listOfListsEdgeIds) {
if (listOfListsEdgeId != null) {
uniqueEdgeIds.addAll(listOfListsEdgeId);
}
}
}
if (!uniqueEdgeIds.isEmpty()) {
for (EdgeId edgeId : uniqueEdgeIds) {
saveEdgeEvent(tenantId,
edgeId,
EdgeEventType.RELATION,
ActionType.valueOf(edgeNotificationMsg.getEdgeEventAction()),
null,
mapper.valueToTree(entityRelation));
}
}
return null;
}, dbCallbackExecutorService);
}
private ListenableFuture<List<EdgeId>> findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) {
case DEVICE:
case ASSET:
case ENTITY_VIEW:
ListenableFuture<List<EntityRelation>> originatorEdgeRelationsFuture = relationService.findByToAndTypeAsync(tenantId, entityId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE);
return Futures.transform(originatorEdgeRelationsFuture, originatorEdgeRelations -> {
if (originatorEdgeRelations != null && originatorEdgeRelations.size() > 0) {
return Collections.singletonList(new EdgeId(originatorEdgeRelations.get(0).getFrom().getId()));
} else {
return Collections.emptyList();
}
}, dbCallbackExecutorService);
case DASHBOARD:
return convertToEdgeIds(edgeService.findEdgesByTenantIdAndDashboardId(tenantId, new DashboardId(entityId.getId())));
case RULE_CHAIN:
return convertToEdgeIds(edgeService.findEdgesByTenantIdAndRuleChainId(tenantId, new RuleChainId(entityId.getId())));
default:
return Futures.immediateFuture(Collections.emptyList());
}
}
private ListenableFuture<List<EdgeId>> convertToEdgeIds(ListenableFuture<List<Edge>> future) {
return Futures.transform(future, edges -> {
if (edges != null && !edges.isEmpty()) {
return edges.stream().map(IdBased::getId).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}, dbCallbackExecutorService);
}
private EdgeEventType getEdgeQueueTypeByEntityType(EntityType entityType) {
switch (entityType) {
case DEVICE:
return EdgeEventType.DEVICE;
case ASSET:
return EdgeEventType.ASSET;
case ENTITY_VIEW:
return EdgeEventType.ENTITY_VIEW;
default:
log.info("Unsupported entity type: [{}]", entityType);
return null;
}
}
}

19
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java

@ -31,11 +31,14 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
import org.thingsboard.server.service.edge.rpc.constructor.AlarmUpdateMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.AssetUpdateMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DashboardUpdateMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DeviceUpdateMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EntityViewUpdateMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RelationUpdateMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RuleChainUpdateMsgConstructor;
@ -53,6 +56,10 @@ public class EdgeContextComponent {
@Autowired
private EdgeService edgeService;
@Lazy
@Autowired
private EdgeNotificationService edgeNotificationService;
@Lazy
@Autowired
private AssetService assetService;
@ -89,6 +96,14 @@ public class EdgeContextComponent {
@Autowired
private DashboardService dashboardService;
@Lazy
@Autowired
private RuleChainService ruleChainService;
@Lazy
@Autowired
private UserService userService;
@Lazy
@Autowired
private ActorService actorService;
@ -137,6 +152,10 @@ public class EdgeContextComponent {
@Autowired
private RelationUpdateMsgConstructor relationUpdateMsgConstructor;
@Lazy
@Autowired
private EntityDataMsgConstructor entityDataMsgConstructor;
@Lazy
@Autowired
private EdgeEventStorageSettings edgeEventStorageSettings;

38
application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.io.IOException;
public interface EdgeNotificationService {
TimePageData<EdgeEvent> findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException;
void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback);
}

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

@ -19,34 +19,40 @@ import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.ByteString;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.thingsboard.server.common.data.Customer;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeQueueEntry;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.DataType;
@ -60,15 +66,13 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.ConnectRequestMsg;
import org.thingsboard.server.gen.edge.ConnectResponseCode;
import org.thingsboard.server.gen.edge.ConnectResponseMsg;
import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.DownlinkMsg;
import org.thingsboard.server.gen.edge.EdgeConfiguration;
@ -82,7 +86,7 @@ import org.thingsboard.server.gen.edge.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.UpdateMsgType;
import org.thingsboard.server.gen.edge.UplinkMsg;
import org.thingsboard.server.gen.edge.UplinkResponseMsg;
import org.thingsboard.server.gen.edge.UserUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import java.io.Closeable;
@ -104,6 +108,8 @@ public final class EdgeGrpcSession implements Closeable {
private static final ReentrantLock deviceCreationLock = new ReentrantLock();
private final Gson gson = new Gson();
private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs";
private final UUID sessionId;
@ -169,36 +175,28 @@ public final class EdgeGrpcSession implements Closeable {
void processHandleMessages() throws ExecutionException, InterruptedException {
Long queueStartTs = getQueueStartTs().get();
TimePageLink pageLink = new TimePageLink(ctx.getEdgeEventStorageSettings().getMaxReadRecordsCount(), queueStartTs, null, true);
TimePageData<Event> pageData;
TimePageData<EdgeEvent> pageData;
UUID ifOffset = null;
do {
pageData = ctx.getEdgeService().findQueueEvents(edge.getTenantId(), edge.getId(), pageLink);
pageData = ctx.getEdgeNotificationService().findEdgeEvents(edge.getTenantId(), edge.getId(), pageLink);
if (isConnected() && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size());
for (Event event : pageData.getData()) {
log.trace("[{}] Processing event [{}]", this.sessionId, event);
for (EdgeEvent edgeEvent : pageData.getData()) {
log.trace("[{}] Processing edge event [{}]", this.sessionId, edgeEvent);
try {
EdgeQueueEntry entry = objectMapper.treeToValue(event.getBody(), EdgeQueueEntry.class);
UpdateMsgType msgType = getResponseMsgType(entry.getType());
switch (msgType) {
case ENTITY_DELETED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case ENTITY_CREATED_RPC_MESSAGE:
case ALARM_ACK_RPC_MESSAGE:
case ALARM_CLEAR_RPC_MESSAGE:
processEntityCRUDMessage(entry, msgType);
break;
case RULE_CHAIN_CUSTOM_MESSAGE:
processCustomDownlinkMessage(entry);
break;
}
if (ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
pushEntityAttributesToEdge(entry);
UpdateMsgType msgType = getResponseMsgType(ActionType.valueOf(edgeEvent.getEdgeEventAction()));
if (msgType == null) {
processTelemetryMessage(edgeEvent);
} else {
processEntityCRUDMessage(edgeEvent, msgType);
if (ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
pushEntityAttributesToEdge(edgeEvent);
}
}
} catch (Exception e) {
log.error("Exception during processing records from queue", e);
}
ifOffset = event.getUuidId();
ifOffset = edgeEvent.getUuidId();
}
}
if (isConnected() && pageData.hasNext()) {
@ -222,63 +220,66 @@ public final class EdgeGrpcSession implements Closeable {
}
}
private void pushEntityAttributesToEdge(EdgeQueueEntry entry) throws IOException {
private ListenableFuture<Long> getQueueStartTs() {
ListenableFuture<Optional<AttributeKvEntry>> future =
ctx.getAttributesService().find(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, QUEUE_START_TS_ATTR_KEY);
return Futures.transform(future, attributeKvEntryOpt -> {
if (attributeKvEntryOpt != null && attributeKvEntryOpt.isPresent()) {
AttributeKvEntry attributeKvEntry = attributeKvEntryOpt.get();
return attributeKvEntry.getLongValue().isPresent() ? attributeKvEntry.getLongValue().get() : 0L;
} else {
return 0L;
}
}, MoreExecutors.directExecutor());
}
private void updateQueueStartTs(Long newStartTs) {
newStartTs = ++newStartTs; // increments ts by 1 - next edge event search starts from current offset + 1
List<AttributeKvEntry> attributes = Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, newStartTs), System.currentTimeMillis()));
ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes);
}
private void pushEntityAttributesToEdge(EdgeEvent edgeEvent) throws IOException {
EntityId entityId = null;
String entityName = null;
switch (entry.getEntityType()) {
switch (edgeEvent.getEdgeEventType()) {
case EDGE:
Edge edge = objectMapper.readValue(entry.getData(), Edge.class);
entityId = edge.getId();
entityName = edge.getName();
break;
case DEVICE:
Device device = objectMapper.readValue(entry.getData(), Device.class);
entityId = device.getId();
entityName = device.getName();
entityId = new DeviceId(edgeEvent.getEntityId());
break;
case ASSET:
Asset asset = objectMapper.readValue(entry.getData(), Asset.class);
entityId = asset.getId();
entityName = asset.getName();
entityId = new AssetId(edgeEvent.getEntityId());
break;
case ENTITY_VIEW:
EntityView entityView = objectMapper.readValue(entry.getData(), EntityView.class);
entityId = entityView.getId();
entityName = entityView.getName();
entityId = new EntityViewId(edgeEvent.getEntityId());
break;
case DASHBOARD:
Dashboard dashboard = objectMapper.readValue(entry.getData(), Dashboard.class);
entityId = dashboard.getId();
entityName = dashboard.getName();
entityId = new DashboardId(edgeEvent.getEntityId());
break;
}
if (entityId != null) {
final EntityId finalEntityId = entityId;
final String finalEntityName = entityName;
ListenableFuture<List<AttributeKvEntry>> ssAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SERVER_SCOPE);
Futures.transform(ssAttrFuture, ssAttributes -> {
if (ssAttributes != null && !ssAttributes.isEmpty()) {
try {
TbMsgMetaData metaData = new TbMsgMetaData();
ObjectNode entityNode = objectMapper.createObjectNode();
metaData.putValue("scope", DataConstants.SERVER_SCOPE);
for (AttributeKvEntry attr : ssAttributes) {
if (attr.getDataType() == DataType.BOOLEAN) {
if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) {
entityNode.put(attr.getKey(), attr.getBooleanValue().get());
} else if (attr.getDataType() == DataType.DOUBLE) {
} else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) {
entityNode.put(attr.getKey(), attr.getDoubleValue().get());
} else if (attr.getDataType() == DataType.LONG) {
} else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) {
entityNode.put(attr.getKey(), attr.getLongValue().get());
} else {
entityNode.put(attr.getKey(), attr.getValueAsString());
}
}
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ATTRIBUTES_UPDATED, finalEntityId, metaData, TbMsgDataType.JSON
, objectMapper.writeValueAsString(entityNode));
log.debug("Sending donwlink entity data msg, entityName [{}], tbMsg [{}]", finalEntityName, tbMsg);
log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", finalEntityId, entityNode);
DownlinkMsg value = constructEntityDataProtoMsg(finalEntityId, ActionType.ATTRIBUTES_UPDATED, JsonUtils.parse(objectMapper.writeValueAsString(entityNode)));
outputStream.onNext(ResponseMsg.newBuilder()
.setDownlinkMsg(constructDownlinkEntityDataMsg(finalEntityName, finalEntityId, tbMsg))
.build());
.setDownlinkMsg(value).build());
} catch (Exception e) {
log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e);
}
@ -290,184 +291,333 @@ public final class EdgeGrpcSession implements Closeable {
}
}
private void processCustomDownlinkMessage(EdgeQueueEntry entry) throws IOException {
log.trace("Executing processCustomDownlinkMessage, entry [{}]", entry);
TbMsg tbMsg = TbMsg.fromBytes(Base64.decodeBase64(entry.getData()), TbMsgCallback.EMPTY);
String entityName = null;
private void processTelemetryMessage(EdgeEvent edgeEvent) throws IOException {
log.trace("Executing processTelemetryMessage, edgeEvent [{}]", edgeEvent);
EntityId entityId = null;
switch (entry.getEntityType()) {
switch (edgeEvent.getEdgeEventType()) {
case DEVICE:
Device device = ctx.getDeviceService().findDeviceById(edge.getTenantId(), new DeviceId(tbMsg.getOriginator().getId()));
entityName = device.getName();
entityId = device.getId();
entityId = new DeviceId(edgeEvent.getEntityId());
break;
case ASSET:
Asset asset = ctx.getAssetService().findAssetById(edge.getTenantId(), new AssetId(tbMsg.getOriginator().getId()));
entityName = asset.getName();
entityId = asset.getId();
entityId = new AssetId(edgeEvent.getEntityId());
break;
case ENTITY_VIEW:
EntityView entityView = ctx.getEntityViewService().findEntityViewById(edge.getTenantId(), new EntityViewId(tbMsg.getOriginator().getId()));
entityName = entityView.getName();
entityId = entityView.getId();
entityId = new EntityViewId(edgeEvent.getEntityId());
break;
}
if (entityName != null && entityId != null) {
log.debug("Sending downlink entity data msg, entityName [{}], tbMsg [{}]", entityName, tbMsg);
outputStream.onNext(ResponseMsg.newBuilder()
.setDownlinkMsg(constructDownlinkEntityDataMsg(entityName, entityId, tbMsg))
.build());
if (entityId != null) {
log.debug("Sending telemetry data msg, entityId [{}], body [{}]", edgeEvent.getEntityId(), edgeEvent.getEntityBody());
DownlinkMsg downlinkMsg;
try {
ActionType actionType = ActionType.valueOf(edgeEvent.getEdgeEventAction());
downlinkMsg = constructEntityDataProtoMsg(entityId, actionType, JsonUtils.parse(objectMapper.writeValueAsString(edgeEvent.getEntityBody())));
outputStream.onNext(ResponseMsg.newBuilder()
.setDownlinkMsg(downlinkMsg)
.build());
} catch (Exception e) {
log.warn("Can't send telemetry data msg, entityId [{}], body [{}]", edgeEvent.getEntityId(), edgeEvent.getEntityBody(), e);
}
}
}
private void processEntityCRUDMessage(EdgeQueueEntry entry, UpdateMsgType msgType) throws java.io.IOException {
log.trace("Executing processEntityCRUDMessage, entry [{}], msgType [{}]", entry, msgType);
switch (entry.getEntityType()) {
private void processEntityCRUDMessage(EdgeEvent edgeEvent, UpdateMsgType msgType) {
log.trace("Executing processEntityCRUDMessage, edgeEvent [{}], msgType [{}]", edgeEvent, msgType);
switch (edgeEvent.getEdgeEventType()) {
case EDGE:
Edge edge = objectMapper.readValue(entry.getData(), Edge.class);
onEdgeUpdated(msgType, edge);
// TODO: voba - add edge update logic
break;
case DEVICE:
Device device = objectMapper.readValue(entry.getData(), Device.class);
onDeviceUpdated(msgType, device);
processDeviceCRUD(edgeEvent, msgType);
break;
case ASSET:
Asset asset = objectMapper.readValue(entry.getData(), Asset.class);
onAssetUpdated(msgType, asset);
processAssetCRUD(edgeEvent, msgType);
break;
case ENTITY_VIEW:
EntityView entityView = objectMapper.readValue(entry.getData(), EntityView.class);
onEntityViewUpdated(msgType, entityView);
processEntityViewCRUD(edgeEvent, msgType);
break;
case DASHBOARD:
Dashboard dashboard = objectMapper.readValue(entry.getData(), Dashboard.class);
onDashboardUpdated(msgType, dashboard);
processDashboardCRUD(edgeEvent, msgType);
break;
case RULE_CHAIN:
RuleChain ruleChain = objectMapper.readValue(entry.getData(), RuleChain.class);
onRuleChainUpdated(msgType, ruleChain);
processRuleChainCRUD(edgeEvent, msgType);
break;
case RULE_CHAIN_METADATA:
RuleChainMetaData ruleChainMetaData = objectMapper.readValue(entry.getData(), RuleChainMetaData.class);
onRuleChainMetadataUpdated(msgType, ruleChainMetaData);
processRuleChainMetadataCRUD(edgeEvent, msgType);
break;
case ALARM:
Alarm alarm = objectMapper.readValue(entry.getData(), Alarm.class);
onAlarmUpdated(msgType, alarm);
processAlarmCRUD(edgeEvent, msgType);
break;
case USER:
User user = objectMapper.readValue(entry.getData(), User.class);
onUserUpdated(msgType, user);
processUserCRUD(edgeEvent, msgType);
break;
case RELATION:
EntityRelation entityRelation = objectMapper.readValue(entry.getData(), EntityRelation.class);
onEntityRelationUpdated(msgType, entityRelation);
processRelationCRUD(edgeEvent, msgType);
break;
}
}
private void updateQueueStartTs(Long newStartTs) {
newStartTs = ++newStartTs; // increments ts by 1 - next edge event search starts from current offset + 1
List<AttributeKvEntry> attributes = Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, newStartTs), System.currentTimeMillis()));
ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes);
}
private void processDeviceCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
DeviceId deviceId = new DeviceId(edgeEvent.getEntityId());
switch (msgType) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case DEVICE_CONFLICT_RPC_MESSAGE:
ListenableFuture<Device> deviceFuture = ctx.getDeviceService().findDeviceByIdAsync(edgeEvent.getTenantId(), deviceId);
Futures.addCallback(deviceFuture,
new FutureCallback<Device>() {
@Override
public void onSuccess(@Nullable Device device) {
if (device != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDeviceUpdateMsg(ctx.getDeviceUpdateMsgConstructor().constructDeviceUpdatedMsg(msgType, device))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
private ListenableFuture<Long> getQueueStartTs() {
ListenableFuture<Optional<AttributeKvEntry>> future =
ctx.getAttributesService().find(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, QUEUE_START_TS_ATTR_KEY);
return Futures.transform(future, attributeKvEntryOpt -> {
if (attributeKvEntryOpt != null && attributeKvEntryOpt.isPresent()) {
AttributeKvEntry attributeKvEntry = attributeKvEntryOpt.get();
return attributeKvEntry.getLongValue().isPresent() ? attributeKvEntry.getLongValue().get() : 0L;
} else {
return 0L;
}
}, MoreExecutors.directExecutor());
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processDeviceCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
break;
case ENTITY_DELETED_RPC_MESSAGE:
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDeviceUpdateMsg(ctx.getDeviceUpdateMsgConstructor().constructDeviceDeleteMsg(deviceId))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
private void onEdgeUpdated(UpdateMsgType msgType, Edge edge) {
// TODO: voba add configuration update to edge
this.edge = edge;
}
private void onDeviceUpdated(UpdateMsgType msgType, Device device) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDeviceUpdateMsg(ctx.getDeviceUpdateMsgConstructor().constructDeviceUpdatedMsg(msgType, device))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
private void onAssetUpdated(UpdateMsgType msgType, Asset asset) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAssetUpdateMsg(ctx.getAssetUpdateMsgConstructor().constructAssetUpdatedMsg(msgType, asset))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
private void processAssetCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
AssetId assetId = new AssetId(edgeEvent.getEntityId());
switch (msgType) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case DEVICE_CONFLICT_RPC_MESSAGE:
ListenableFuture<Asset> assetFuture = ctx.getAssetService().findAssetByIdAsync(edgeEvent.getTenantId(), assetId);
Futures.addCallback(assetFuture,
new FutureCallback<Asset>() {
@Override
public void onSuccess(@Nullable Asset asset) {
if (asset != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAssetUpdateMsg(ctx.getAssetUpdateMsgConstructor().constructAssetUpdatedMsg(msgType, asset))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
private void onEntityViewUpdated(UpdateMsgType msgType, EntityView entityView) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(ctx.getEntityViewUpdateMsgConstructor().constructEntityViewUpdatedMsg(msgType, entityView))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
@Override
public void onFailure(Throwable t) {
log.warn("Can't processAssetCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
break;
case ENTITY_DELETED_RPC_MESSAGE:
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAssetUpdateMsg(ctx.getAssetUpdateMsgConstructor().constructAssetDeleteMsg(assetId))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
break;
}
}
private void onRuleChainUpdated(UpdateMsgType msgType, RuleChain ruleChain) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ctx.getRuleChainUpdateMsgConstructor().constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
private void processEntityViewCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId());
switch (msgType) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case DEVICE_CONFLICT_RPC_MESSAGE:
ListenableFuture<EntityView> entityViewFuture = ctx.getEntityViewService().findEntityViewByIdAsync(edgeEvent.getTenantId(), entityViewId);
Futures.addCallback(entityViewFuture,
new FutureCallback<EntityView>() {
@Override
public void onSuccess(@Nullable EntityView entityView) {
if (entityView != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(ctx.getEntityViewUpdateMsgConstructor().constructEntityViewUpdatedMsg(msgType, entityView))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processEntityViewCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
break;
case ENTITY_DELETED_RPC_MESSAGE:
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(ctx.getEntityViewUpdateMsgConstructor().constructEntityViewDeleteMsg(entityViewId))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
break;
}
}
private void onRuleChainMetadataUpdated(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) {
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg =
ctx.getRuleChainUpdateMsgConstructor().constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData);
if (ruleChainMetadataUpdateMsg != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
private void processDashboardCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId());
switch (msgType) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case DEVICE_CONFLICT_RPC_MESSAGE:
ListenableFuture<Dashboard> dashboardFuture = ctx.getDashboardService().findDashboardByIdAsync(edgeEvent.getTenantId(), dashboardId);
Futures.addCallback(dashboardFuture,
new FutureCallback<Dashboard>() {
@Override
public void onSuccess(@Nullable Dashboard dashboard) {
if (dashboard != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(ctx.getDashboardUpdateMsgConstructor().constructDashboardUpdatedMsg(msgType, dashboard))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processDashboardCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
break;
case ENTITY_DELETED_RPC_MESSAGE:
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(ctx.getDashboardUpdateMsgConstructor().constructDashboardDeleteMsg(dashboardId))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
break;
}
}
private void onDashboardUpdated(UpdateMsgType msgType, Dashboard dashboard) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(ctx.getDashboardUpdateMsgConstructor().constructDashboardUpdatedMsg(msgType, dashboard))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
private void processRuleChainCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
switch (msgType) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case DEVICE_CONFLICT_RPC_MESSAGE:
ListenableFuture<RuleChain> ruleChainFuture = ctx.getRuleChainService().findRuleChainByIdAsync(edgeEvent.getTenantId(), ruleChainId);
Futures.addCallback(ruleChainFuture,
new FutureCallback<RuleChain>() {
@Override
public void onSuccess(@Nullable RuleChain ruleChain) {
if (ruleChain != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ctx.getRuleChainUpdateMsgConstructor().constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processRuleChainCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
break;
case ENTITY_DELETED_RPC_MESSAGE:
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ctx.getRuleChainUpdateMsgConstructor().constructRuleChainDeleteMsg(ruleChainId))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
break;
}
}
private void onAlarmUpdated(UpdateMsgType msgType, Alarm alarm) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAlarmUpdateMsg(ctx.getAlarmUpdateMsgConstructor().constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
private void processRuleChainMetadataCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
ListenableFuture<RuleChain> ruleChainFuture = ctx.getRuleChainService().findRuleChainByIdAsync(edgeEvent.getTenantId(), ruleChainId);
Futures.addCallback(ruleChainFuture,
new FutureCallback<RuleChain>() {
@Override
public void onSuccess(@Nullable RuleChain ruleChain) {
if (ruleChain != null) {
RuleChainMetaData ruleChainMetaData = ctx.getRuleChainService().loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg =
ctx.getRuleChainUpdateMsgConstructor().constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData);
if (ruleChainMetadataUpdateMsg != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processRuleChainMetadataCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
}
private void onUserUpdated(UpdateMsgType msgType, User user) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setUserUpdateMsg(ctx.getUserUpdateMsgConstructor().constructUserUpdatedMsg(msgType, user))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
private void processUserCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
UserId userId = new UserId(edgeEvent.getEntityId());
switch (msgType) {
case ENTITY_CREATED_RPC_MESSAGE:
case ENTITY_UPDATED_RPC_MESSAGE:
case DEVICE_CONFLICT_RPC_MESSAGE:
ListenableFuture<User> userFuture = ctx.getUserService().findUserByIdAsync(edgeEvent.getTenantId(), userId);
Futures.addCallback(userFuture,
new FutureCallback<User>() {
@Override
public void onSuccess(@Nullable User user) {
if (user != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setUserUpdateMsg(ctx.getUserUpdateMsgConstructor().constructUserUpdatedMsg(msgType, user))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processUserCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
break;
case ENTITY_DELETED_RPC_MESSAGE:
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setUserUpdateMsg(ctx.getUserUpdateMsgConstructor().constructUserDeleteMsg(userId))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
break;
}
}
private void onEntityRelationUpdated(UpdateMsgType msgType, EntityRelation entityRelation) {
private void processRelationCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
EntityRelation entityRelation = objectMapper.convertValue(edgeEvent.getEntityBody(), EntityRelation.class);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRelationUpdateMsg(ctx.getRelationUpdateMsgConstructor().constructRelationUpdatedMsg(msgType, entityRelation))
.build();
@ -476,55 +626,57 @@ public final class EdgeGrpcSession implements Closeable {
.build());
}
private UpdateMsgType getResponseMsgType(String msgType) {
if (msgType.equals(SessionMsgType.POST_TELEMETRY_REQUEST.name()) ||
msgType.equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ||
msgType.equals(DataConstants.ATTRIBUTES_UPDATED) ||
msgType.equals(DataConstants.ATTRIBUTES_DELETED)) {
return UpdateMsgType.RULE_CHAIN_CUSTOM_MESSAGE;
} else {
switch (msgType) {
case DataConstants.ENTITY_UPDATED:
return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
case DataConstants.ENTITY_CREATED:
case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
return ENTITY_CREATED_RPC_MESSAGE;
case DataConstants.ENTITY_DELETED:
case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE:
return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
case DataConstants.ALARM_ACK:
return UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
case DataConstants.ALARM_CLEAR:
return UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE;
default:
throw new RuntimeException("Unsupported msgType [" + msgType + "]");
}
private void processAlarmCRUD(EdgeEvent edgeEvent, UpdateMsgType msgType) {
AlarmId alarmId = new AlarmId(edgeEvent.getEntityId());
ListenableFuture<Alarm> alarmFuture = ctx.getAlarmService().findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId);
Futures.addCallback(alarmFuture,
new FutureCallback<Alarm>() {
@Override
public void onSuccess(@Nullable Alarm alarm) {
if (alarm != null) {
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setAlarmUpdateMsg(ctx.getAlarmUpdateMsgConstructor().constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm))
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
}
}
@Override
public void onFailure(Throwable t) {
log.warn("Can't processAlarmCRUD, edgeEvent [{}]", edgeEvent, t);
}
}, ctx.getDbCallbackExecutor());
}
private UpdateMsgType getResponseMsgType(ActionType actionType) {
switch (actionType) {
case UPDATED:
return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
case ADDED:
case ASSIGNED_TO_EDGE:
return ENTITY_CREATED_RPC_MESSAGE;
case DELETED:
case UNASSIGNED_FROM_EDGE:
return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
case ALARM_ACK:
return UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
case ALARM_CLEAR:
return UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE;
case ATTRIBUTES_UPDATED:
case ATTRIBUTES_DELETED:
case TIMESERIES_DELETED:
return null;
default:
throw new RuntimeException("Unsupported actionType [" + actionType + "]");
}
}
private DownlinkMsg constructDownlinkEntityDataMsg(String entityName, EntityId entityId, TbMsg tbMsg) {
EntityDataProto entityData = EntityDataProto.newBuilder()
.setEntityName(entityName)
.setTbMsg(ByteString.copyFrom(TbMsg.toByteArray(tbMsg)))
.setEntityIdMSB(entityId.getId().getMostSignificantBits())
.setEntityIdLSB(entityId.getId().getLeastSignificantBits())
.build();
private DownlinkMsg constructEntityDataProtoMsg(EntityId entityId, ActionType actionType, JsonElement entityData) {
EntityDataProto entityDataProto = ctx.getEntityDataMsgConstructor().constructEntityDataMsg(entityId, actionType, entityData);
DownlinkMsg.Builder builder = DownlinkMsg.newBuilder()
.addAllEntityData(Collections.singletonList(entityData));
return builder.build();
}
private CustomerUpdateMsg constructCustomerUpdatedMsg(UpdateMsgType msgType, Customer customer) {
CustomerUpdateMsg.Builder builder = CustomerUpdateMsg.newBuilder()
.setMsgType(msgType);
return builder.build();
}
private UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user) {
UserUpdateMsg.Builder builder = UserUpdateMsg.newBuilder()
.setMsgType(msgType);
.addAllEntityData(Collections.singletonList(entityDataProto));
return builder.build();
}
@ -532,20 +684,21 @@ public final class EdgeGrpcSession implements Closeable {
try {
if (uplinkMsg.getEntityDataList() != null && !uplinkMsg.getEntityDataList().isEmpty()) {
for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) {
TbMsg tbMsg = null;
TbMsg originalTbMsg = TbMsg.fromBytes(entityData.getTbMsg().toByteArray(), TbMsgCallback.EMPTY);
if (originalTbMsg.getOriginator().getEntityType() == EntityType.DEVICE) {
String deviceName = entityData.getEntityName();
Device device = ctx.getDeviceService().findDeviceByTenantIdAndName(edge.getTenantId(), deviceName);
if (device != null) {
tbMsg = TbMsg.newMsg(originalTbMsg.getType(), device.getId(), originalTbMsg.getMetaData().copy(),
originalTbMsg.getDataType(), originalTbMsg.getData());
}
} else {
tbMsg = originalTbMsg;
}
if (tbMsg != null) {
ctx.getTbClusterService().pushMsgToRuleEngine(edge.getTenantId(), tbMsg.getOriginator(), tbMsg, null);
EntityId entityId = constructEntityId(entityData);
if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg()) && entityId != null) {
ListenableFuture<TbMsgMetaData> metaDataFuture = constructBaseMsgMetadata(entityId);
Futures.transform(metaDataFuture, metaData -> {
if (metaData != null) {
metaData.putValue(DataConstants.MSG_SOURCE_KEY, DataConstants.EDGE_MSG_SOURCE);
if (entityData.hasPostAttributesMsg()) {
processPostAttributes(entityId, entityData.getPostAttributesMsg(), metaData);
}
if (entityData.hasPostTelemetryMsg()) {
processPostTelemetry(entityId, entityData.getPostTelemetryMsg(), metaData);
}
}
return null;
}, ctx.getDbCallbackExecutor());
}
}
}
@ -571,6 +724,78 @@ public final class EdgeGrpcSession implements Closeable {
return UplinkResponseMsg.newBuilder().setSuccess(true).build();
}
private ListenableFuture<TbMsgMetaData> constructBaseMsgMetadata(EntityId entityId) {
switch (entityId.getEntityType()) {
case DEVICE:
ListenableFuture<Device> deviceFuture = ctx.getDeviceService().findDeviceByIdAsync(edge.getTenantId(), new DeviceId(entityId.getId()));
return Futures.transform(deviceFuture, device -> {
TbMsgMetaData metaData = new TbMsgMetaData();
if (device != null) {
metaData.putValue("deviceName", device.getName());
metaData.putValue("deviceType", device.getType());
}
return metaData;
}, ctx.getDbCallbackExecutor());
case ASSET:
ListenableFuture<Asset> assetFuture = ctx.getAssetService().findAssetByIdAsync(edge.getTenantId(), new AssetId(entityId.getId()));
return Futures.transform(assetFuture, asset -> {
TbMsgMetaData metaData = new TbMsgMetaData();
if (asset != null) {
metaData.putValue("assetName", asset.getName());
metaData.putValue("assetType", asset.getType());
}
return metaData;
}, ctx.getDbCallbackExecutor());
case ENTITY_VIEW:
ListenableFuture<EntityView> entityViewFuture = ctx.getEntityViewService().findEntityViewByIdAsync(edge.getTenantId(), new EntityViewId(entityId.getId()));
return Futures.transform(entityViewFuture, entityView -> {
TbMsgMetaData metaData = new TbMsgMetaData();
if (entityView != null) {
metaData.putValue("entityViewName", entityView.getName());
metaData.putValue("entityViewType", entityView.getType());
}
return metaData;
}, ctx.getDbCallbackExecutor());
default:
log.debug("Constructing empty metadata for entityId [{}]", entityId);
return Futures.immediateFuture(new TbMsgMetaData());
}
}
private EntityId constructEntityId(EntityDataProto entityData) {
EntityType entityType = EntityType.valueOf(entityData.getEntityType());
switch (entityType) {
case DEVICE:
return new DeviceId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
case ASSET:
return new AssetId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
case ENTITY_VIEW:
return new EntityViewId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
case DASHBOARD:
return new DashboardId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
default:
log.warn("Unsupported entity type [{}] during construct of entity id. EntityDataProto [{}]", entityData.getEntityType(), entityData);
return null;
}
}
private void processPostTelemetry(EntityId entityId, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData metaData) {
for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList());
metaData.putValue("ts", tsKv.getTs() + "");
TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, metaData, gson.toJson(json));
// TODO: voba - verify that null callback is OK
ctx.getTbClusterService().pushMsgToRuleEngine(edge.getTenantId(), tbMsg.getOriginator(), tbMsg, null);
}
}
private void processPostAttributes(EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) {
JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, gson.toJson(json));
// TODO: voba - verify that null callback is OK
ctx.getTbClusterService().pushMsgToRuleEngine(edge.getTenantId(), tbMsg.getOriginator(), tbMsg, null);
}
private void onDeviceUpdate(DeviceUpdateMsg deviceUpdateMsg) {
log.info("onDeviceUpdate {}", deviceUpdateMsg);
DeviceId edgeDeviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()));
@ -771,7 +996,7 @@ public final class EdgeGrpcSession implements Closeable {
.setTenantIdLSB(edge.getTenantId().getId().getLeastSignificantBits())
.setName(edge.getName())
.setRoutingKey(edge.getRoutingKey())
.setType(edge.getType().toString())
.setType(edge.getType())
.build();
}

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetUpdateMsgConstructor.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.constructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.gen.edge.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.UpdateMsgType;
@ -38,4 +39,10 @@ public class AssetUpdateMsgConstructor {
return builder.build();
}
public AssetUpdateMsg constructAssetDeleteMsg(AssetId assetId) {
return AssetUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(assetId.getId().getMostSignificantBits())
.setIdLSB(assetId.getId().getLeastSignificantBits()).build();
}
}

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardUpdateMsgConstructor.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
@ -42,4 +43,11 @@ public class DashboardUpdateMsgConstructor {
return builder.build();
}
public DashboardUpdateMsg constructDashboardDeleteMsg(DashboardId dashboardId) {
return DashboardUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(dashboardId.getId().getMostSignificantBits())
.setIdLSB(dashboardId.getId().getLeastSignificantBits()).build();
}
}

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceUpdateMsgConstructor.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
@ -58,4 +59,11 @@ public class DeviceUpdateMsgConstructor {
}
return builder.build();
}
public DeviceUpdateMsg constructDeviceDeleteMsg(DeviceId deviceId) {
return DeviceUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(deviceId.getId().getMostSignificantBits())
.setIdLSB(deviceId.getId().getLeastSignificantBits()).build();
}
}

56
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.constructor;
import com.google.gson.JsonElement;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.edge.EntityDataProto;
@Component
@Slf4j
public class EntityDataMsgConstructor {
public EntityDataProto constructEntityDataMsg(EntityId entityId, ActionType actionType, JsonElement entityData) {
EntityDataProto.Builder builder = EntityDataProto.newBuilder()
.setEntityIdMSB(entityId.getId().getMostSignificantBits())
.setEntityIdLSB(entityId.getId().getLeastSignificantBits())
.setEntityType(entityId.getEntityType().name());
switch (actionType) {
case TIMESERIES_UPDATED:
try {
builder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(entityData));
} catch (Exception e) {
log.warn("Can't convert to telemetry proto, entityData [{}]", entityData, e);
}
break;
case ATTRIBUTES_UPDATED:
try {
builder.setPostAttributesMsg(JsonConverter.convertToAttributesProto(entityData));
} catch (Exception e) {
log.warn("Can't convert to attributes proto, entityData [{}]", entityData, e);
}
break;
// TODO: voba - add support for attribute delete
// case ATTRIBUTES_DELETED:
}
return builder.build();
}
}

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewUpdateMsgConstructor.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.constructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.gen.edge.EdgeEntityType;
import org.thingsboard.server.gen.edge.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.UpdateMsgType;
@ -50,4 +51,10 @@ public class EntityViewUpdateMsgConstructor {
return builder.build();
}
public EntityViewUpdateMsg constructEntityViewDeleteMsg(EntityViewId entityViewId) {
return EntityViewUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(entityViewId.getId().getMostSignificantBits())
.setIdLSB(entityViewId.getId().getLeastSignificantBits()).build();
}
}

9
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainUpdateMsgConstructor.java

@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain;
@ -126,7 +125,6 @@ public class RuleChainUpdateMsgConstructor {
.build();
}
private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException {
return RuleNodeProto.newBuilder()
.setIdMSB(node.getId().getId().getMostSignificantBits())
@ -139,4 +137,11 @@ public class RuleChainUpdateMsgConstructor {
.build();
}
public RuleChainUpdateMsg constructRuleChainDeleteMsg(RuleChainId ruleChainId) {
return RuleChainUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(ruleChainId.getId().getMostSignificantBits())
.setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build();
}
}

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserUpdateMsgConstructor.java

@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.util.mapping.JacksonUtil;
@ -61,4 +62,11 @@ public class UserUpdateMsgConstructor {
}
return builder.build();
}
public UserUpdateMsg constructUserDeleteMsg(UserId userId) {
return UserUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(userId.getId().getMostSignificantBits())
.setIdLSB(userId.getId().getLeastSignificantBits()).build();
}
}

255
application/src/main/java/org/thingsboard/server/service/edge/rpc/init/DefaultSyncEdgeService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.init;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import io.grpc.stub.StreamObserver;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -30,6 +31,8 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
@ -121,54 +124,58 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
@Override
public void sync(EdgeContextComponent ctx, Edge edge, StreamObserver<ResponseMsg> outputStream) {
Set<EntityId> pushedEntityIds = new HashSet<>();
syncRuleChains(edge, pushedEntityIds, outputStream);
syncDevices(edge, pushedEntityIds, outputStream);
syncAssets(edge, pushedEntityIds, outputStream);
syncEntityViews(edge, pushedEntityIds, outputStream);
syncDashboards(edge, pushedEntityIds, outputStream);
syncUsers(ctx, edge, pushedEntityIds, outputStream);
syncRelations(ctx, edge, pushedEntityIds, outputStream);
List<ListenableFuture<Void>> futures = new ArrayList<>();
futures.add(syncRuleChains(ctx, edge, pushedEntityIds, outputStream));
futures.add(syncDevices(ctx, edge, pushedEntityIds, outputStream));
futures.add(syncAssets(ctx, edge, pushedEntityIds, outputStream));
futures.add(syncEntityViews(ctx, edge, pushedEntityIds, outputStream));
futures.add(syncDashboards(ctx, edge, pushedEntityIds, outputStream));
ListenableFuture<List<Void>> joinFuture = Futures.allAsList(futures);
Futures.transform(joinFuture, result -> {
syncRelations(ctx, edge, pushedEntityIds, outputStream);
return null;
}, MoreExecutors.directExecutor());
}
private void syncRuleChains(Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
private ListenableFuture<Void> syncRuleChains(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
TimePageLink pageLink = new TimePageLink(100);
TimePageData<RuleChain> pageData;
do {
pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] rule chains(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (RuleChain ruleChain : pageData.getData()) {
RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainUpdateMsgConstructor.constructRuleChainUpdatedMsg(
edge.getRootRuleChainId(),
UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE,
ruleChain);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ruleChainUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(ruleChain.getId());
ListenableFuture<TimePageData<RuleChain>> future = ruleChainService.findRuleChainsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
try {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] rule chains(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (RuleChain ruleChain : pageData.getData()) {
RuleChainUpdateMsg ruleChainUpdateMsg =
ruleChainUpdateMsgConstructor.constructRuleChainUpdatedMsg(
edge.getRootRuleChainId(),
UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE,
ruleChain);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setRuleChainUpdateMsg(ruleChainUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(ruleChain.getId());
}
}
} catch (Exception e) {
log.error("Exception during loading edge rule chain(s) on sync!", e);
}
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return null;
}, ctx.getDbCallbackExecutor());
} catch (Exception e) {
log.error("Exception during loading edge rule chain(s) on sync!", e);
return Futures.immediateFuture(null);
}
}
private void syncDevices(Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
private ListenableFuture<Void> syncDevices(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
TimePageLink pageLink = new TimePageLink(100);
TimePageData<Device> pageData;
do {
pageData = deviceService.findDevicesByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) {
ListenableFuture<TimePageData<Device>> future = deviceService.findDevicesByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Device device : pageData.getData()) {
DeviceUpdateMsg deviceUpdateMsg =
@ -184,22 +191,19 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
pushedEntityIds.add(device.getId());
}
}
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return null;
}, ctx.getDbCallbackExecutor());
} catch (Exception e) {
log.error("Exception during loading edge device(s) on sync!", e);
return Futures.immediateFuture(null);
}
}
private void syncAssets(Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
private ListenableFuture<Void> syncAssets(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
TimePageLink pageLink = new TimePageLink(100);
TimePageData<Asset> pageData;
do {
pageData = assetService.findAssetsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) {
ListenableFuture<TimePageData<Asset>> future = assetService.findAssetsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (Asset asset : pageData.getData()) {
AssetUpdateMsg assetUpdateMsg =
@ -215,110 +219,112 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
pushedEntityIds.add(asset.getId());
}
}
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return null;
}, ctx.getDbCallbackExecutor());
} catch (Exception e) {
log.error("Exception during loading edge asset(s) on sync!", e);
return Futures.immediateFuture(null);
}
}
private void syncEntityViews(Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
private ListenableFuture<Void> syncEntityViews(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
TimePageLink pageLink = new TimePageLink(100);
TimePageData<EntityView> pageData;
do {
pageData = entityViewService.findEntityViewsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] entity view(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (EntityView entityView : pageData.getData()) {
EntityViewUpdateMsg entityViewUpdateMsg =
entityViewUpdateMsgConstructor.constructEntityViewUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
entityView);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(entityViewUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(entityView.getId());
ListenableFuture<TimePageData<EntityView>> future = entityViewService.findEntityViewsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
try {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] entity view(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (EntityView entityView : pageData.getData()) {
EntityViewUpdateMsg entityViewUpdateMsg =
entityViewUpdateMsgConstructor.constructEntityViewUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
entityView);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setEntityViewUpdateMsg(entityViewUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(entityView.getId());
}
}
} catch (Exception e) {
log.error("Exception during loading edge entity view(s) on sync!", e);
}
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return null;
}, ctx.getDbCallbackExecutor());
} catch (Exception e) {
log.error("Exception during loading edge entity view(s) on sync!", e);
return Futures.immediateFuture(null);
}
}
private void syncDashboards(Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
private ListenableFuture<Void> syncDashboards(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
TimePageLink pageLink = new TimePageLink(100);
TimePageData<DashboardInfo> pageData;
do {
pageData = dashboardService.findDashboardsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] dashboard(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (DashboardInfo dashboardInfo : pageData.getData()) {
Dashboard dashboard = dashboardService.findDashboardById(edge.getTenantId(), dashboardInfo.getId());
DashboardUpdateMsg dashboardUpdateMsg =
dashboardUpdateMsgConstructor.constructDashboardUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
dashboard);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(dashboardUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(dashboard.getId());
ListenableFuture<TimePageData<DashboardInfo>> future = dashboardService.findDashboardsByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), new TimePageLink(Integer.MAX_VALUE));
return Futures.transform(future, pageData -> {
try {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] dashboard(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (DashboardInfo dashboardInfo : pageData.getData()) {
Dashboard dashboard = dashboardService.findDashboardById(edge.getTenantId(), dashboardInfo.getId());
DashboardUpdateMsg dashboardUpdateMsg =
dashboardUpdateMsgConstructor.constructDashboardUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
dashboard);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setDashboardUpdateMsg(dashboardUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(dashboard.getId());
}
}
} catch (Exception e) {
log.error("Exception during loading edge dashboard(s) on sync!", e);
}
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
return null;
}, ctx.getDbCallbackExecutor());
} catch (Exception e) {
log.error("Exception during loading edge dashboard(s) on sync!", e);
return Futures.immediateFuture(null);
}
}
private void syncUsers(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
try {
TimePageLink pageLink = new TimePageLink(100);
TimePageData<User> pageData;
do {
pageData = userService.findUsersByTenantIdAndEdgeId(edge.getTenantId(), edge.getId(), pageLink).get();
if (!pageData.getData().isEmpty()) {
log.trace("[{}] [{}] user(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (User user : pageData.getData()) {
UserUpdateMsg userUpdateMsg =
userUpdateMsgConstructor.constructUserUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
user);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setUserUpdateMsg(userUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(user.getId());
}
}
if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink();
}
} while (pageData.hasNext());
TextPageData<User> pageData = userService.findTenantAdmins(edge.getTenantId(), new TextPageLink(Integer.MAX_VALUE));
pushUsersToEdge(pageData, edge, pushedEntityIds, outputStream);
if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
pageData = userService.findCustomerUsers(edge.getTenantId(), edge.getCustomerId(), new TextPageLink(Integer.MAX_VALUE));
pushUsersToEdge(pageData, edge, pushedEntityIds, outputStream);
}
} catch (Exception e) {
log.error("Exception during loading edge user(s) on sync!", e);
}
}
private void syncRelations(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
private void pushUsersToEdge(TextPageData<User> pageData, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
log.trace("[{}] [{}] user(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size());
for (User user : pageData.getData()) {
UserUpdateMsg userUpdateMsg =
userUpdateMsgConstructor.constructUserUpdatedMsg(
UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE,
user);
EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
.setUserUpdateMsg(userUpdateMsg)
.build();
outputStream.onNext(ResponseMsg.newBuilder()
.setEntityUpdateMsg(entityUpdateMsg)
.build());
pushedEntityIds.add(user.getId());
}
}
}
private ListenableFuture<Void> syncRelations(EdgeContextComponent ctx, Edge edge, Set<EntityId> pushedEntityIds, StreamObserver<ResponseMsg> outputStream) {
if (!pushedEntityIds.isEmpty()) {
List<ListenableFuture<List<EntityRelation>>> futures = new ArrayList<>();
for (EntityId entityId : pushedEntityIds) {
@ -326,7 +332,7 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
futures.add(syncRelations(edge, entityId, EntitySearchDirection.TO));
}
ListenableFuture<List<List<EntityRelation>>> relationsListFuture = Futures.allAsList(futures);
Futures.transform(relationsListFuture, relationsList -> {
return Futures.transform(relationsListFuture, relationsList -> {
try {
Set<EntityRelation> uniqueEntityRelations = new HashSet<>();
if (!relationsList.isEmpty()) {
@ -360,6 +366,8 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
}
return null;
}, ctx.getDbCallbackExecutor());
} else {
return Futures.immediateFuture(null);
}
}
@ -369,7 +377,6 @@ public class DefaultSyncEdgeService implements SyncEdgeService {
return relationService.findByQuery(edge.getTenantId(), query);
}
@Override
public void syncRuleChainMetadata(Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg, StreamObserver<ResponseMsg> outputStream) {
if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() != 0 && ruleChainMetadataRequestMsg.getRuleChainIdLSB() != 0) {

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

@ -239,7 +239,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.6.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'SYSTEM'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'CORE'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception e) {}
log.info("Schema updated.");
}

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

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.queue;
import akka.actor.ActorRef;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
@ -28,6 +27,7 @@ import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
@ -42,6 +42,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
@ -82,18 +83,20 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final TbLocalSubscriptionService localSubscriptionService;
private final SubscriptionManagerService subscriptionManagerService;
private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
private final EdgeNotificationService edgeNotificationService;
private final TbCoreConsumerStats stats = new TbCoreConsumerStats();
public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService,
SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService,
TbCoreDeviceRpcService tbCoreDeviceRpcService) {
TbCoreDeviceRpcService tbCoreDeviceRpcService, EdgeNotificationService edgeNotificationService) {
super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.stateService = stateService;
this.localSubscriptionService = localSubscriptionService;
this.subscriptionManagerService = subscriptionManagerService;
this.tbCoreDeviceRpcService = tbCoreDeviceRpcService;
this.edgeNotificationService = edgeNotificationService;
}
@PostConstruct
@ -142,6 +145,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreMsg.hasDeviceStateServiceMsg()) {
log.trace("[{}] Forwarding message to state service {}", id, toCoreMsg.getDeviceStateServiceMsg());
forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback);
} else if (toCoreMsg.hasEdgeNotificationMsg()) {
log.trace("[{}] Forwarding message to edge service {}", id, toCoreMsg.getEdgeNotificationMsg());
forwardToEdgeNotificationService(toCoreMsg.getEdgeNotificationMsg(), callback);
} else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray());
if (actorMsg.isPresent()) {
@ -150,7 +156,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg);
} else {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tell(actorMsg.get(), ActorRef.noSender());
actorContext.tell(actorMsg.get());
}
}
callback.onSuccess();
@ -161,7 +167,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
}
});
if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) {
ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process message: {}", id, msg.getValue()));
ctx.getAckMap().forEach((id, msg) -> log.debug("[{}] Timeout to process message: {}", id, msg.getValue()));
ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
}
mainConsumer.commit();
@ -208,7 +214,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreNotification.getComponentLifecycleMsg().toByteArray());
if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tell(actorMsg.get(), ActorRef.noSender());
actorContext.tellWithHighPriority(actorMsg.get());
}
callback.onSuccess();
}
@ -275,11 +281,18 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
stateService.onQueueMsg(deviceStateServiceMsg, callback);
}
private void forwardToEdgeNotificationService(EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
if (statsEnabled) {
stats.log(edgeNotificationMsg);
}
edgeNotificationService.pushNotificationToEdge(edgeNotificationMsg, callback);
}
private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TbCallback callback) {
if (statsEnabled) {
stats.log(toDeviceActorMsg);
}
actorContext.tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback), ActorRef.noSender());
actorContext.tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback));
}
private void throwNotHandled(Object msg, TbCallback callback) {

14
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.queue;
import akka.actor.ActorRef;
import com.google.protobuf.ProtocolStringList;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -51,7 +50,6 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStr
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.stats.RuleEngineStatisticsService;
@ -168,7 +166,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbMsgCallback callback = new TbMsgPackCallback(id, tenantId, ctx);
try {
if (toRuleEngineMsg.getTbMsg() != null && !toRuleEngineMsg.getTbMsg().isEmpty()) {
forwardToRuleEngineActor(tenantId, toRuleEngineMsg, callback);
forwardToRuleEngineActor(configuration.getName(), tenantId, toRuleEngineMsg, callback);
} else {
callback.onSuccess();
}
@ -182,7 +180,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
timeout = true;
}
TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(timeout, ctx);
TbRuleEngineProcessingResult result = new TbRuleEngineProcessingResult(configuration.getName(), timeout, ctx);
TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
if (statsEnabled) {
stats.log(result, decision.isCommit());
@ -232,7 +230,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
Optional<TbActorMsg> actorMsg = encodingService.decode(nfMsg.getComponentLifecycleMsg().toByteArray());
if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tell(actorMsg.get(), ActorRef.noSender());
actorContext.tellWithHighPriority(actorMsg.get());
}
callback.onSuccess();
} else if (nfMsg.hasFromDeviceRpcResponse()) {
@ -248,8 +246,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
}
}
private void forwardToRuleEngineActor(TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) {
TbMsg tbMsg = TbMsg.fromBytes(toRuleEngineMsg.getTbMsg().toByteArray(), callback);
private void forwardToRuleEngineActor(String queueName, TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) {
TbMsg tbMsg = TbMsg.fromBytes(queueName, toRuleEngineMsg.getTbMsg().toByteArray(), callback);
QueueToRuleEngineMsg msg;
ProtocolStringList relationTypesList = toRuleEngineMsg.getRelationTypesList();
Set<String> relationTypes = null;
@ -261,7 +259,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
}
}
msg = new QueueToRuleEngineMsg(tenantId, tbMsg, relationTypes, toRuleEngineMsg.getFailureMessage());
actorContext.tell(msg, ActorRef.noSender());
actorContext.tell(msg);
}
@Scheduled(fixedDelayString = "${queue.rule-engine.stats.print-interval-ms}")

6
application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java

@ -33,6 +33,7 @@ public class TbCoreConsumerStats {
private final AtomicInteger claimDeviceCounter = new AtomicInteger(0);
private final AtomicInteger deviceStateCounter = new AtomicInteger(0);
private final AtomicInteger edgeNotificationCounter = new AtomicInteger(0);
private final AtomicInteger subscriptionMsgCounter = new AtomicInteger(0);
private final AtomicInteger toCoreNotificationsCounter = new AtomicInteger(0);
@ -66,6 +67,11 @@ public class TbCoreConsumerStats {
deviceStateCounter.incrementAndGet();
}
public void log(TransportProtos.EdgeNotificationMsgProto msg) {
totalCounter.incrementAndGet();
edgeNotificationCounter.incrementAndGet();
}
public void log(TransportProtos.SubscriptionMgrMsgProto msg) {
totalCounter.incrementAndGet();
subscriptionMsgCounter.incrementAndGet();

5
application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingResult.java

@ -27,6 +27,8 @@ import java.util.concurrent.ConcurrentMap;
public class TbRuleEngineProcessingResult {
@Getter
private final String queueName;
@Getter
private final boolean success;
@Getter
@ -34,7 +36,8 @@ public class TbRuleEngineProcessingResult {
@Getter
private final TbMsgPackProcessingContext ctx;
public TbRuleEngineProcessingResult(boolean timeout, TbMsgPackProcessingContext ctx) {
public TbRuleEngineProcessingResult(String queueName, boolean timeout, TbMsgPackProcessingContext ctx) {
this.queueName = queueName;
this.timeout = timeout;
this.ctx = ctx;
this.success = !timeout && ctx.getPendingMap().isEmpty() && ctx.getFailedMap().isEmpty();

6
application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java

@ -100,7 +100,7 @@ public class TbRuleEngineProcessingStrategyFactory {
}
log.debug("[{}] Going to reprocess {} messages", queueName, toReprocess.size());
if (log.isTraceEnabled()) {
toReprocess.forEach((id, msg) -> log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
toReprocess.forEach((id, msg) -> log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(result.getQueueName(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
}
if (pauseBetweenRetries > 0) {
try {
@ -129,10 +129,10 @@ public class TbRuleEngineProcessingStrategyFactory {
log.debug("[{}] Reprocessing skipped for {} failed and {} timeout messages", queueName, result.getFailedMap().size(), result.getPendingMap().size());
}
if (log.isTraceEnabled()) {
result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(result.getQueueName(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
}
if (log.isTraceEnabled()) {
result.getPendingMap().forEach((id, msg) -> log.trace("Timeout messages [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
result.getPendingMap().forEach((id, msg) -> log.trace("Timeout messages [{}]: {}", id, TbMsg.fromBytes(result.getQueueName(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
}
return new TbRuleEngineProcessingDecision(true, null);
}

7
application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.service.rpc;
import akka.actor.ActorRef;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
@ -122,7 +121,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
log.trace("[{}][{}] Processing local rpc call to device actor [{}]", request.getTenantId(), request.getId(), request.getDeviceId());
UUID requestId = request.getId();
localToDeviceRpcRequests.put(requestId, rpcMsg);
actorContext.tell(rpcMsg, ActorRef.noSender());
actorContext.tellWithHighPriority(rpcMsg);
scheduleToDeviceTimeout(request, requestId);
}
@ -176,7 +175,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
}
private void scheduleToRuleEngineTimeout(ToDeviceRpcRequest request, UUID requestId) {
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis());
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()) + TimeUnit.SECONDS.toMillis(1);
log.trace("[{}] processing to rule engine request.", requestId);
scheduler.schedule(() -> {
log.trace("[{}] timeout for processing to rule engine request.", requestId);
@ -188,7 +187,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService {
}
private void scheduleToDeviceTimeout(ToDeviceRpcRequest request, UUID requestId) {
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis());
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()) + TimeUnit.SECONDS.toMillis(1);
log.trace("[{}] processing to device request.", requestId);
scheduler.schedule(() -> {
log.trace("[{}] timeout for to device request.", requestId);

2
application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java

@ -164,7 +164,7 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi
}
private void scheduleTimeout(ToDeviceRpcRequest request, UUID requestId) {
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis());
long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()) + TimeUnit.SECONDS.toMillis(1);
log.trace("[{}] processing the request: [{}]", this.hashCode(), requestId);
scheduler.schedule(() -> {
log.trace("[{}] timeout the request: [{}]", this.hashCode(), requestId);

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

@ -126,7 +126,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer
scriptIdToNameMap.put(scriptId, functionName);
return scriptId;
} catch (Exception e) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
log.debug("Failed to compile JS script: {}", e.getMessage(), e);
throw new ExecutionException(e);
}
});

139
application/src/main/resources/actor-system.conf

@ -1,139 +0,0 @@
#
# Copyright © 2016-2020 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
akka {
# JVM shutdown, System.exit(-1), in case of a fatal error,
# such as OutOfMemoryError
jvm-exit-on-fatal-error = off
loglevel = "INFO"
loggers = ["akka.event.slf4j.Slf4jLogger"]
}
# This dispatcher is used for app
app-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 1
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 1
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 1.0
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for rpc actors
rpc-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 8
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 0.5
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for auth
core-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 0.25
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for system rule chains and rule node actors
system-rule-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 0.25
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
# This dispatcher is used for tenant rule chains and rule node actors
rule-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min = 2
# Max number of threads to cap factor-based parallelism number to
parallelism-max = 12
# The parallelism factor is used to determine thread pool size using the
# following formula: ceil(available processors * factor). Resulting size
# is then bounded by the parallelism-min and parallelism-max values.
parallelism-factor = 0.25
}
# How long time the dispatcher will wait for new actors until it shuts down
shutdown-timeout = 1s
# Throughput defines the number of messages that are processed in a batch
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}

1
application/src/main/resources/logback.xml

@ -27,7 +27,6 @@
<logger name="org.thingsboard.server" level="INFO" />
<logger name="org.thingsboard.server.service.edge" level="TRACE" />
<logger name="akka" level="INFO" />
<!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
<!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->

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

@ -281,6 +281,14 @@ sql:
# Actor system parameters
actors:
system:
throughput: "${ACTORS_SYSTEM_THROUGHPUT:5}"
scheduler_pool_size: "${ACTORS_SYSTEM_SCHEDULER_POOL_SIZE:1}"
max_actor_init_attempts: "${ACTORS_SYSTEM_MAX_ACTOR_INIT_ATTEMPTS:10}"
app_dispatcher_pool_size: "${ACTORS_SYSTEM_APP_DISPATCHER_POOL_SIZE:1}"
tenant_dispatcher_pool_size: "${ACTORS_SYSTEM_TENANT_DISPATCHER_POOL_SIZE:2}"
device_dispatcher_pool_size: "${ACTORS_SYSTEM_DEVICE_DISPATCHER_POOL_SIZE:4}"
rule_dispatcher_pool_size: "${ACTORS_SYSTEM_RULE_DISPATCHER_POOL_SIZE:4}"
tenant:
create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}"
session:
@ -318,11 +326,6 @@ actors:
enabled: "${ACTORS_STATISTICS_ENABLED:true}"
js_print_interval_ms: "${ACTORS_JS_STATISTICS_PRINT_INTERVAL_MS:10000}"
persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}"
queue:
# Enable/disable persistence of un-processed messages to the queue
enabled: "${ACTORS_QUEUE_ENABLED:true}"
# Maximum allowed timeout for persistence into the queue
timeout: "${ACTORS_QUEUE_PERSISTENCE_TIMEOUT:30000}"
cache:
# caffeine or redis

5
application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentScope;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.security.Authority;
import java.util.List;
@ -81,14 +82,14 @@ public abstract class BaseComponentDescriptorControllerTest extends AbstractCont
@Test
public void testGetByType() throws Exception {
List<ComponentDescriptor> descriptors = readResponse(
doGet("/api/components/" + ComponentType.FILTER).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
doGet("/api/components?componentTypes={componentTypes}&ruleChainType={ruleChainType}", ComponentType.FILTER, RuleChainType.CORE).andExpect(status().isOk()), new TypeReference<List<ComponentDescriptor>>() {
});
Assert.assertNotNull(descriptors);
Assert.assertTrue(descriptors.size() >= AMOUNT_OF_DEFAULT_FILTER_NODES);
for (ComponentType type : ComponentType.values()) {
doGet("/api/components/" + type).andExpect(status().isOk());
doGet("/api/components?componentTypes={componentTypes}&ruleChainType={ruleChainType}", type, RuleChainType.CORE).andExpect(status().isOk());
}
}

99
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
@ -46,6 +47,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
private IdComparator<Edge> idComparator = new IdComparator<>();
private Tenant savedTenant;
private TenantId tenantId;
private User tenantAdmin;
@Before
@ -55,6 +57,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
tenantId = savedTenant.getId();
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
@ -77,9 +80,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testSaveEdge() throws Exception {
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
Edge edge = constructEdge("My edge", "default");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
Assert.assertNotNull(savedEdge);
@ -99,9 +100,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testFindEdgeById() throws Exception {
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
Edge edge = constructEdge("My edge", "default");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
Assert.assertNotNull(foundEdge);
@ -112,21 +111,15 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public void testFindEdgeTypesByTenantId() throws Exception {
List<Edge> edges = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Edge edge = new Edge();
edge.setName("My edge B" + i);
edge.setType("typeB");
Edge edge = constructEdge("My edge B" + i, "typeB");
edges.add(doPost("/api/edge", edge, Edge.class));
}
for (int i = 0; i < 7; i++) {
Edge edge = new Edge();
edge.setName("My edge C" + i);
edge.setType("typeC");
Edge edge = constructEdge("My edge C" + i, "typeC");
edges.add(doPost("/api/edge", edge, Edge.class));
}
for (int i = 0; i < 9; i++) {
Edge edge = new Edge();
edge.setName("My edge A" + i);
edge.setType("typeA");
Edge edge = constructEdge("My edge A" + i, "typeA");
edges.add(doPost("/api/edge", edge, Edge.class));
}
List<EntitySubtype> edgeTypes = doGetTyped("/api/edge/types",
@ -142,9 +135,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testDeleteEdge() throws Exception {
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
Edge edge = constructEdge("My edge", "default");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
doDelete("/api/edge/" + savedEdge.getId().getId().toString())
@ -156,8 +147,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testSaveEdgeWithEmptyType() throws Exception {
Edge edge = new Edge();
edge.setName("My edge");
Edge edge = constructEdge("My edge", null);
doPost("/api/edge", edge)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Edge type should be specified")));
@ -165,8 +155,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testSaveEdgeWithEmptyName() throws Exception {
Edge edge = new Edge();
edge.setType("default");
Edge edge = constructEdge(null, "default");
doPost("/api/edge", edge)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Edge name should be specified")));
@ -174,9 +163,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testAssignUnassignEdgeToCustomer() throws Exception {
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
Edge edge = constructEdge("My edge", "default");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
Customer customer = new Customer();
@ -200,9 +187,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
@Test
public void testAssignEdgeToNonExistentCustomer() throws Exception {
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
Edge edge = constructEdge("My edge", "default");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
doPost("/api/customer/" + UUIDs.timeBased().toString()
@ -234,9 +219,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
login(tenantAdmin.getEmail(), "testPassword1");
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
Edge edge = constructEdge("My edge", "default");
Edge savedEdge = doPost("/api/edge", edge, Edge.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
@ -253,9 +236,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
public void testFindTenantEdges() throws Exception {
List<Edge> edges = new ArrayList<>();
for (int i = 0; i < 178; i++) {
Edge edge = new Edge();
edge.setName("Edge" + i);
edge.setType("default");
Edge edge = constructEdge("Edge" + i, "default");
edges.add(doPost("/api/edge", edge, Edge.class));
}
List<Edge> loadedEdges = new ArrayList<>();
@ -282,23 +263,19 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
String title1 = "Edge title 1";
List<Edge> edgesTitle1 = new ArrayList<>();
for (int i = 0; i < 143; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType("default");
Edge edge = constructEdge(name, "default");
edgesTitle1.add(doPost("/api/edge", edge, Edge.class));
}
String title2 = "Edge title 2";
List<Edge> edgesTitle2 = new ArrayList<>();
for (int i = 0; i < 75; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType("default");
Edge edge = constructEdge(name, "default");
edgesTitle2.add(doPost("/api/edge", edge, Edge.class));
}
@ -368,24 +345,20 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
String type1 = "typeA";
List<Edge> edgesType1 = new ArrayList<>();
for (int i = 0; i < 143; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType(type1);
Edge edge = constructEdge(name, type1);
edgesType1.add(doPost("/api/edge", edge, Edge.class));
}
String title2 = "Edge title 2";
String type2 = "typeB";
List<Edge> edgesType2 = new ArrayList<>();
for (int i = 0; i < 75; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType(type2);
Edge edge = constructEdge(name, type2);
edgesType2.add(doPost("/api/edge", edge, Edge.class));
}
@ -458,9 +431,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
List<Edge> edges = new ArrayList<>();
for (int i = 0; i < 128; i++) {
Edge edge = new Edge();
edge.setName("Edge" + i);
edge.setType("default");
Edge edge = constructEdge("Edge" + i, "default");
edge = doPost("/api/edge", edge, Edge.class);
edges.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class));
@ -495,12 +466,10 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
String title1 = "Edge title 1";
List<Edge> edgesTitle1 = new ArrayList<>();
for (int i = 0; i < 125; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType("default");
Edge edge = constructEdge(name, "default");
edge = doPost("/api/edge", edge, Edge.class);
edgesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class));
@ -508,12 +477,10 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
String title2 = "Edge title 2";
List<Edge> edgesTitle2 = new ArrayList<>();
for (int i = 0; i < 143; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType("default");
Edge edge = constructEdge(name, "default");
edge = doPost("/api/edge", edge, Edge.class);
edgesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class));
@ -590,12 +557,10 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
String type1 = "typeC";
List<Edge> edgesType1 = new ArrayList<>();
for (int i = 0; i < 125; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType(type1);
Edge edge = constructEdge(name, type1);
edge = doPost("/api/edge", edge, Edge.class);
edgesType1.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class));
@ -604,12 +569,10 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
String type2 = "typeD";
List<Edge> edgesType2 = new ArrayList<>();
for (int i = 0; i < 143; i++) {
Edge edge = new Edge();
String suffix = RandomStringUtils.randomAlphanumeric(15);
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
edge.setName(name);
edge.setType(type2);
Edge edge = constructEdge(name, type2);
edge = doPost("/api/edge", edge, Edge.class);
edgesType2.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class));
@ -674,4 +637,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
private Edge constructEdge(String name, String type) {
return constructEdge(tenantId, name, type);
}
private Edge constructEdge(TenantId tenantId, String name, String type) {
Edge edge = new Edge();
edge.setTenantId(tenantId);
edge.setName(name);
edge.setType(type);
edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
return edge;
}
}

3
application/src/test/java/org/thingsboard/server/controller/ControllerNoSqlTestSuite.java

@ -27,7 +27,8 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.controller.nosql.*Test"})
// TODO: voba - fix before final test on cassandra
"org.thingsboard.server.controller.nosql.*VOBA_FIX_BEFORE_FINAL_TESTTest"})
public class ControllerNoSqlTestSuite {
@ClassRule

3
application/src/test/java/org/thingsboard/server/mqtt/MqttNoSqlTestSuite.java

@ -27,7 +27,8 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.mqtt.*.nosql.*Test"})
// TODO: voba - fix before final test on cassandra
"org.thingsboard.server.mqtt.*.nosql.*VOBA_FIX_BEFORE_FINAL_TESTTest"})
public class MqttNoSqlTestSuite {
@ClassRule

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

@ -26,7 +26,9 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.mqtt.rpc.sql.*Test", "org.thingsboard.server.mqtt.telemetry.sql.*Test"})
"org.thingsboard.server.mqtt.rpc.sql.*Test",
"org.thingsboard.server.mqtt.telemetry.sql.*Test"
})
public class MqttSqlTestSuite {
@ClassRule

4
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -136,7 +136,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(),
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}
@ -193,7 +193,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(),
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409),
asyncContextTimeoutToUseRpcPlugin);
}

8
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.rules.flow;
import akka.actor.ActorRef;
import com.datastax.driver.core.utils.UUIDs;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
@ -26,7 +24,6 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
@ -36,7 +33,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
@ -151,7 +147,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback);
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system
actorSystem.tell(qMsg, ActorRef.noSender());
actorSystem.tell(qMsg);
Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess();
TimePageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
@ -263,7 +259,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback);
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system
actorSystem.tell(qMsg, ActorRef.noSender());
actorSystem.tell(qMsg);
Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess();

6
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.server.rules.lifecycle;
import akka.actor.ActorRef;
import com.datastax.driver.core.utils.UUIDs;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
@ -26,7 +24,6 @@ import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Event;
@ -40,7 +37,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
@ -142,7 +138,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
TbMsg tbMsg = TbMsg.newMsg("CUSTOM", device.getId(), new TbMsgMetaData(), "{}", tbMsgCallback);
QueueToRuleEngineMsg qMsg = new QueueToRuleEngineMsg(savedTenant.getId(), tbMsg, null, null);
// Pushing Message to the system
actorSystem.tell(qMsg, ActorRef.noSender());
actorSystem.tell(qMsg);
Mockito.verify(tbMsgCallback, Mockito.timeout(3000)).onSuccess();

4
application/src/test/java/org/thingsboard/server/system/SystemNoSqlTestSuite.java

@ -29,7 +29,9 @@ import java.util.Arrays;
* @author Andrew Shvayka
*/
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.system.*NoSqlTest"})
@ClasspathSuite.ClassnameFilters({
// TODO: voba - fix before final test on cassandra
"org.thingsboard.server.system.*VOBA_FIX_BEFORE_FINAL_TESTNoSqlTest"})
public class SystemNoSqlTestSuite {
@ClassRule

2
application/src/test/resources/logback.xml

@ -13,8 +13,6 @@
<logger name="org.apache.cassandra" level="WARN"/>
<logger name="org.cassandraunit" level="INFO"/>
<logger name="akka" level="INFO" />
<root level="WARN">
<appender-ref ref="console"/>
</root>

75
common/actor/pom.xml

@ -0,0 +1,75 @@
<!--
Copyright © 2016-2020 The Thingsboard Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.5.3-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
<artifactId>actor</artifactId>
<packaging>jar</packaging>
<name>Thingsboard Actor system</name>
<url>https://thingsboard.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>util</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-core</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

34
common/actor/src/main/java/org/thingsboard/server/actors/AbstractTbActor.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
public abstract class AbstractTbActor implements TbActor {
@Getter
protected TbActorCtx ctx;
@Override
public void init(TbActorCtx ctx) throws TbActorException {
this.ctx = ctx;
}
@Override
public TbActorRef getActorRef() {
return ctx;
}
}

215
common/actor/src/main/java/org/thingsboard/server/actors/DefaultTbActorSystem.java

@ -0,0 +1,215 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@Slf4j
@Data
public class DefaultTbActorSystem implements TbActorSystem {
private final ConcurrentMap<String, Dispatcher> dispatchers = new ConcurrentHashMap<>();
private final ConcurrentMap<TbActorId, TbActorMailbox> actors = new ConcurrentHashMap<>();
private final ConcurrentMap<TbActorId, ReentrantLock> actorCreationLocks = new ConcurrentHashMap<>();
private final ConcurrentMap<TbActorId, Set<TbActorId>> parentChildMap = new ConcurrentHashMap<>();
@Getter
private final TbActorSystemSettings settings;
@Getter
private final ScheduledExecutorService scheduler;
public DefaultTbActorSystem(TbActorSystemSettings settings) {
this.settings = settings;
this.scheduler = Executors.newScheduledThreadPool(settings.getSchedulerPoolSize(), ThingsBoardThreadFactory.forName("actor-system-scheduler"));
}
@Override
public void createDispatcher(String dispatcherId, ExecutorService executor) {
Dispatcher current = dispatchers.putIfAbsent(dispatcherId, new Dispatcher(dispatcherId, executor));
if (current != null) {
throw new RuntimeException("Dispatcher with id [" + dispatcherId + "] is already registered!");
}
}
@Override
public void destroyDispatcher(String dispatcherId) {
Dispatcher dispatcher = dispatchers.remove(dispatcherId);
if (dispatcher != null) {
dispatcher.getExecutor().shutdownNow();
} else {
throw new RuntimeException("Dispatcher with id [" + dispatcherId + "] is not registered!");
}
}
@Override
public TbActorRef getActor(TbActorId actorId) {
return actors.get(actorId);
}
@Override
public TbActorRef createRootActor(String dispatcherId, TbActorCreator creator) {
return createActor(dispatcherId, creator, null);
}
@Override
public TbActorRef createChildActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
return createActor(dispatcherId, creator, parent);
}
private TbActorRef createActor(String dispatcherId, TbActorCreator creator, TbActorId parent) {
Dispatcher dispatcher = dispatchers.get(dispatcherId);
if (dispatcher == null) {
log.warn("Dispatcher with id [{}] is not registered!", dispatcherId);
throw new RuntimeException("Dispatcher with id [" + dispatcherId + "] is not registered!");
}
TbActorId actorId = creator.createActorId();
TbActorMailbox actorMailbox = actors.get(actorId);
if (actorMailbox != null) {
log.debug("Actor with id [{}] is already registered!", actorId);
} else {
Lock actorCreationLock = actorCreationLocks.computeIfAbsent(actorId, id -> new ReentrantLock());
actorCreationLock.lock();
try {
actorMailbox = actors.get(actorId);
if (actorMailbox == null) {
log.debug("Creating actor with id [{}]!", actorId);
TbActor actor = creator.createActor();
TbActorRef parentRef = null;
if (parent != null) {
parentRef = getActor(parent);
if (parentRef == null) {
throw new TbActorNotRegisteredException(parent, "Parent Actor with id [" + parent + "] is not registered!");
}
}
TbActorMailbox mailbox = new TbActorMailbox(this, settings, actorId, parentRef, actor, dispatcher);
actors.put(actorId, mailbox);
mailbox.initActor();
actorMailbox = mailbox;
if (parent != null) {
parentChildMap.computeIfAbsent(parent, id -> ConcurrentHashMap.newKeySet()).add(actorId);
}
} else {
log.debug("Actor with id [{}] is already registered!", actorId);
}
} finally {
actorCreationLock.unlock();
actorCreationLocks.remove(actorId);
}
}
return actorMailbox;
}
@Override
public void tellWithHighPriority(TbActorId target, TbActorMsg actorMsg) {
tell(target, actorMsg, true);
}
@Override
public void tell(TbActorId target, TbActorMsg actorMsg) {
tell(target, actorMsg, false);
}
private void tell(TbActorId target, TbActorMsg actorMsg, boolean highPriority) {
TbActorMailbox mailbox = actors.get(target);
if (mailbox == null) {
throw new TbActorNotRegisteredException(target, "Actor with id [" + target + "] is not registered!");
}
if (highPriority) {
mailbox.tellWithHighPriority(actorMsg);
} else {
mailbox.tell(actorMsg);
}
}
@Override
public void broadcastToChildren(TbActorId parent, TbActorMsg msg) {
broadcastToChildren(parent, id -> true, msg);
}
@Override
public void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg) {
Set<TbActorId> children = parentChildMap.get(parent);
if (children != null) {
children.stream().filter(childFilter).forEach(id -> tell(id, msg));
}
}
@Override
public List<TbActorId> filterChildren(TbActorId parent, Predicate<TbActorId> childFilter) {
Set<TbActorId> children = parentChildMap.get(parent);
if (children != null) {
return children.stream().filter(childFilter).collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
@Override
public void stop(TbActorRef actorRef) {
stop(actorRef.getActorId());
}
@Override
public void stop(TbActorId actorId) {
Set<TbActorId> children = parentChildMap.remove(actorId);
if (children != null) {
for (TbActorId child : children) {
stop(child);
}
}
TbActorMailbox mailbox = actors.remove(actorId);
if (mailbox != null) {
mailbox.destroy();
}
}
@Override
public void stop() {
dispatchers.values().forEach(dispatcher -> {
dispatcher.getExecutor().shutdown();
try {
dispatcher.getExecutor().awaitTermination(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.warn("[{}] Failed to stop dispatcher", dispatcher.getDispatcherId(), e);
}
});
if (scheduler != null) {
scheduler.shutdownNow();
}
actors.clear();
}
}

13
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeQueueEntry.java → common/actor/src/main/java/org/thingsboard/server/actors/Dispatcher.java

@ -13,13 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.edge;
package org.thingsboard.server.actors;
import lombok.Data;
import java.util.concurrent.ExecutorService;
@Data
public class EdgeQueueEntry {
private String type;
private EdgeQueueEntityType entityType;
private String data;
class Dispatcher {
private final String dispatcherId;
private final ExecutorService executor;
}

45
common/actor/src/main/java/org/thingsboard/server/actors/InitFailureStrategy.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import lombok.ToString;
@ToString
public class InitFailureStrategy {
@Getter
private boolean stop;
@Getter
private long retryDelay;
private InitFailureStrategy(boolean stop, long retryDelay) {
this.stop = stop;
this.retryDelay = retryDelay;
}
public static InitFailureStrategy retryImmediately() {
return new InitFailureStrategy(false, 0);
}
public static InitFailureStrategy retryWithDelay(long ms) {
return new InitFailureStrategy(false, ms);
}
public static InitFailureStrategy stop() {
return new InitFailureStrategy(true, 0);
}
}

38
common/actor/src/main/java/org/thingsboard/server/actors/ProcessFailureStrategy.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import lombok.ToString;
@ToString
public class ProcessFailureStrategy {
@Getter
private boolean stop;
private ProcessFailureStrategy(boolean stop) {
this.stop = stop;
}
public static ProcessFailureStrategy stop() {
return new ProcessFailureStrategy(true);
}
public static ProcessFailureStrategy resume() {
return new ProcessFailureStrategy(false);
}
}

43
common/actor/src/main/java/org/thingsboard/server/actors/TbActor.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
public interface TbActor {
boolean process(TbActorMsg msg);
TbActorRef getActorRef();
default void init(TbActorCtx ctx) throws TbActorException {
}
default void destroy() throws TbActorException {
}
default InitFailureStrategy onInitFailure(int attempt, Throwable t) {
return InitFailureStrategy.retryWithDelay(5000 * attempt);
}
default ProcessFailureStrategy onProcessFailure(Throwable t) {
if (t instanceof Error) {
return ProcessFailureStrategy.stop();
} else {
return ProcessFailureStrategy.resume();
}
}
}

24
common/actor/src/main/java/org/thingsboard/server/actors/TbActorCreator.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public interface TbActorCreator {
TbActorId createActorId();
TbActor createActor();
}

41
common/actor/src/main/java/org/thingsboard/server/actors/TbActorCtx.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.List;
import java.util.function.Predicate;
import java.util.function.Supplier;
public interface TbActorCtx extends TbActorRef {
TbActorId getSelf();
TbActorRef getParentRef();
void tell(TbActorId target, TbActorMsg msg);
void stop(TbActorId target);
TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator);
void broadcastToChildren(TbActorMsg msg);
void broadcastToChildren(TbActorMsg msg, Predicate<TbActorId> childFilter);
List<TbActorId> filterChildren(Predicate<TbActorId> childFilter);
}

23
common/actor/src/main/java/org/thingsboard/server/actors/TbActorException.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public class TbActorException extends Exception {
public TbActorException(String message, Throwable cause) {
super(message, cause);
}
}

20
common/actor/src/main/java/org/thingsboard/server/actors/TbActorId.java

@ -0,0 +1,20 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
public interface TbActorId {
}

209
common/actor/src/main/java/org/thingsboard/server/actors/TbActorMailbox.java

@ -0,0 +1,209 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Predicate;
import java.util.function.Supplier;
@Slf4j
@Data
public final class TbActorMailbox implements TbActorCtx {
private static final boolean HIGH_PRIORITY = true;
private static final boolean NORMAL_PRIORITY = false;
private static final boolean FREE = false;
private static final boolean BUSY = true;
private static final boolean NOT_READY = false;
private static final boolean READY = true;
private final TbActorSystem system;
private final TbActorSystemSettings settings;
private final TbActorId selfId;
private final TbActorRef parentRef;
private final TbActor actor;
private final Dispatcher dispatcher;
private final ConcurrentLinkedQueue<TbActorMsg> highPriorityMsgs = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<TbActorMsg> normalPriorityMsgs = new ConcurrentLinkedQueue<>();
private final AtomicBoolean busy = new AtomicBoolean(FREE);
private final AtomicBoolean ready = new AtomicBoolean(NOT_READY);
private final AtomicBoolean destroyInProgress = new AtomicBoolean();
public void initActor() {
dispatcher.getExecutor().execute(() -> tryInit(1));
}
private void tryInit(int attempt) {
try {
log.debug("[{}] Trying to init actor, attempt: {}", selfId, attempt);
if (!destroyInProgress.get()) {
actor.init(this);
if (!destroyInProgress.get()) {
ready.set(READY);
tryProcessQueue(false);
}
}
} catch (Throwable t) {
log.debug("[{}] Failed to init actor, attempt: {}", selfId, attempt, t);
int attemptIdx = attempt + 1;
InitFailureStrategy strategy = actor.onInitFailure(attempt, t);
if (strategy.isStop() || (settings.getMaxActorInitAttempts() > 0 && attemptIdx > settings.getMaxActorInitAttempts())) {
log.info("[{}] Failed to init actor, attempt {}, going to stop attempts.", selfId, attempt, t);
system.stop(selfId);
} else if (strategy.getRetryDelay() > 0) {
log.info("[{}] Failed to init actor, attempt {}, going to retry in attempts in {}ms", selfId, attempt, strategy.getRetryDelay());
log.debug("[{}] Error", selfId, t);
system.getScheduler().schedule(() -> dispatcher.getExecutor().execute(() -> tryInit(attemptIdx)), strategy.getRetryDelay(), TimeUnit.MILLISECONDS);
} else {
log.info("[{}] Failed to init actor, attempt {}, going to retry immediately", selfId, attempt);
log.debug("[{}] Error", selfId, t);
dispatcher.getExecutor().execute(() -> tryInit(attemptIdx));
}
}
}
private void enqueue(TbActorMsg msg, boolean highPriority) {
if (highPriority) {
highPriorityMsgs.add(msg);
} else {
normalPriorityMsgs.add(msg);
}
tryProcessQueue(true);
}
private void tryProcessQueue(boolean newMsg) {
if (ready.get() == READY) {
if (newMsg || !highPriorityMsgs.isEmpty() || !normalPriorityMsgs.isEmpty()) {
if (busy.compareAndSet(FREE, BUSY)) {
dispatcher.getExecutor().execute(this::processMailbox);
} else {
log.trace("[{}] MessageBox is busy, new msg: {}", selfId, newMsg);
}
} else {
log.trace("[{}] MessageBox is empty, new msg: {}", selfId, newMsg);
}
} else {
log.trace("[{}] MessageBox is not ready, new msg: {}", selfId, newMsg);
}
}
private void processMailbox() {
boolean noMoreElements = false;
for (int i = 0; i < settings.getActorThroughput(); i++) {
TbActorMsg msg = highPriorityMsgs.poll();
if (msg == null) {
msg = normalPriorityMsgs.poll();
}
if (msg != null) {
try {
log.debug("[{}] Going to process message: {}", selfId, msg);
actor.process(msg);
} catch (Throwable t) {
log.debug("[{}] Failed to process message: {}", selfId, msg, t);
ProcessFailureStrategy strategy = actor.onProcessFailure(t);
if (strategy.isStop()) {
system.stop(selfId);
}
}
} else {
noMoreElements = true;
break;
}
}
if (noMoreElements) {
busy.set(FREE);
dispatcher.getExecutor().execute(() -> tryProcessQueue(false));
} else {
dispatcher.getExecutor().execute(this::processMailbox);
}
}
@Override
public TbActorId getSelf() {
return selfId;
}
@Override
public void tell(TbActorId target, TbActorMsg actorMsg) {
system.tell(target, actorMsg);
}
@Override
public void broadcastToChildren(TbActorMsg msg) {
system.broadcastToChildren(selfId, msg);
}
@Override
public void broadcastToChildren(TbActorMsg msg, Predicate<TbActorId> childFilter) {
system.broadcastToChildren(selfId, childFilter, msg);
}
@Override
public List<TbActorId> filterChildren(Predicate<TbActorId> childFilter) {
return system.filterChildren(selfId, childFilter);
}
@Override
public void stop(TbActorId target) {
system.stop(target);
}
@Override
public TbActorRef getOrCreateChildActor(TbActorId actorId, Supplier<String> dispatcher, Supplier<TbActorCreator> creator) {
TbActorRef actorRef = system.getActor(actorId);
if (actorRef == null) {
return system.createChildActor(dispatcher.get(), creator.get(), selfId);
} else {
return actorRef;
}
}
public void destroy() {
destroyInProgress.set(true);
dispatcher.getExecutor().execute(() -> {
try {
ready.set(NOT_READY);
actor.destroy();
} catch (Throwable t) {
log.warn("[{}] Failed to destroy actor: {}", selfId, t);
}
});
}
@Override
public TbActorId getActorId() {
return selfId;
}
@Override
public void tell(TbActorMsg actorMsg) {
enqueue(actorMsg, NORMAL_PRIORITY);
}
@Override
public void tellWithHighPriority(TbActorMsg actorMsg) {
enqueue(actorMsg, HIGH_PRIORITY);
}
}

29
common/actor/src/main/java/org/thingsboard/server/actors/TbActorNotRegisteredException.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
public class TbActorNotRegisteredException extends RuntimeException {
@Getter
private TbActorId target;
public TbActorNotRegisteredException(TbActorId target, String message) {
super(message);
this.target = target;
}
}

28
common/actor/src/main/java/org/thingsboard/server/actors/TbActorRef.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
public interface TbActorRef {
TbActorId getActorId();
void tell(TbActorMsg actorMsg);
void tellWithHighPriority(TbActorMsg actorMsg);
}

54
common/actor/src/main/java/org/thingsboard/server/actors/TbActorSystem.java

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Predicate;
public interface TbActorSystem {
ScheduledExecutorService getScheduler();
void createDispatcher(String dispatcherId, ExecutorService executor);
void destroyDispatcher(String dispatcherId);
TbActorRef getActor(TbActorId actorId);
TbActorRef createRootActor(String dispatcherId, TbActorCreator creator);
TbActorRef createChildActor(String dispatcherId, TbActorCreator creator, TbActorId parent);
void tell(TbActorId target, TbActorMsg actorMsg);
void tellWithHighPriority(TbActorId target, TbActorMsg actorMsg);
void stop(TbActorRef actorRef);
void stop(TbActorId actorId);
void stop();
void broadcastToChildren(TbActorId parent, TbActorMsg msg);
void broadcastToChildren(TbActorId parent, Predicate<TbActorId> childFilter, TbActorMsg msg);
List<TbActorId> filterChildren(TbActorId parent, Predicate<TbActorId> childFilter);
}

27
common/actor/src/main/java/org/thingsboard/server/actors/TbActorSystemSettings.java

@ -0,0 +1,27 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Data;
@Data
public class TbActorSystemSettings {
private final int actorThroughput;
private final int schedulerPoolSize;
private final int maxActorInitAttempts;
}

49
common/actor/src/main/java/org/thingsboard/server/actors/TbEntityActorId.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import org.thingsboard.server.common.data.id.EntityId;
import java.util.Objects;
public class TbEntityActorId implements TbActorId {
@Getter
private final EntityId entityId;
public TbEntityActorId(EntityId entityId) {
this.entityId = entityId;
}
@Override
public String toString() {
return entityId.getEntityType() + "|" + entityId.getId();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TbEntityActorId that = (TbEntityActorId) o;
return entityId.equals(that.entityId);
}
@Override
public int hashCode() {
return Objects.hash(entityId);
}
}

34
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/PushToEdgeNodeCallback.java → common/actor/src/main/java/org/thingsboard/server/actors/TbStringActorId.java

@ -13,29 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.edge;
package org.thingsboard.server.actors;
import com.google.common.util.concurrent.FutureCallback;
import lombok.Data;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.Objects;
import javax.annotation.Nullable;
public class TbStringActorId implements TbActorId {
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
private final String id;
@Data
class PushToEdgeNodeCallback implements FutureCallback<Void> {
private final TbContext ctx;
private final TbMsg msg;
public TbStringActorId(String id) {
this.id = id;
}
@Override
public String toString() {
return id;
}
@Override
public void onSuccess(@Nullable Void result) {
ctx.tellNext(msg, SUCCESS);
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TbStringActorId that = (TbStringActorId) o;
return id.equals(that.id);
}
@Override
public void onFailure(Throwable t) {
ctx.tellFailure(msg, t);
public int hashCode() {
return Objects.hash(id);
}
}

224
common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java

@ -0,0 +1,224 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import org.thingsboard.server.common.data.id.DeviceId;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
public class ActorSystemTest {
public static final String ROOT_DISPATCHER = "root-dispatcher";
private static final int _100K = 100 * 1024;
private volatile TbActorSystem actorSystem;
private volatile ExecutorService submitPool;
private int parallelism;
@Before
public void initActorSystem() {
int cores = Runtime.getRuntime().availableProcessors();
parallelism = Math.max(2, cores / 2);
TbActorSystemSettings settings = new TbActorSystemSettings(5, parallelism, 42);
actorSystem = new DefaultTbActorSystem(settings);
submitPool = Executors.newWorkStealingPool(parallelism);
}
@After
public void shutdownActorSystem() {
actorSystem.stop();
submitPool.shutdownNow();
}
@Test
public void test1actorsAnd100KMessages() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
testActorsAndMessages(1, _100K, 1);
}
@Test
public void test10actorsAnd100KMessages() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
testActorsAndMessages(10, _100K, 1);
}
@Test
public void test100KActorsAnd1Messages5timesSingleThread() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newSingleThreadExecutor());
testActorsAndMessages(_100K, 1, 5);
}
@Test
public void test100KActorsAnd1Messages5times() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
testActorsAndMessages(_100K, 1, 5);
}
@Test
public void test100KActorsAnd10Messages() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
testActorsAndMessages(_100K, 10, 1);
}
@Test
public void test1KActorsAnd1KMessages() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
testActorsAndMessages(1000, 1000, 10);
}
@Test
public void testNoMessagesAfterDestroy() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorRef actorId1 = actorSystem.createRootActor(ROOT_DISPATCHER, new SlowInitActor.SlowInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx1));
TbActorRef actorId2 = actorSystem.createRootActor(ROOT_DISPATCHER, new SlowInitActor.SlowInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx2));
actorId1.tell(new IntTbActorMsg(42));
actorId2.tell(new IntTbActorMsg(42));
actorSystem.stop(actorId1);
Assert.assertTrue(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertFalse(testCtx1.getLatch().await(1, TimeUnit.SECONDS));
}
@Test
public void testOneActorCreated() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID()));
submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1)));
submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2)));
Thread.sleep(1000);
actorSystem.tell(actorId, new IntTbActorMsg(42));
Assert.assertTrue(testCtx1.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertFalse(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
}
@Test
public void testActorCreatorCalledOnce() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
ActorTestCtx testCtx = getActorTestCtx(1);
TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID()));
for (int i = 0; i < 1000; i++) {
submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx)));
}
Thread.sleep(1000);
actorSystem.tell(actorId, new IntTbActorMsg(42));
Assert.assertTrue(testCtx.getLatch().await(1, TimeUnit.SECONDS));
//One for creation and one for message
Assert.assertEquals(2, testCtx.getInvocationCount().get());
}
@Test
public void testFailedInit() throws InterruptedException {
actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism));
ActorTestCtx testCtx1 = getActorTestCtx(1);
ActorTestCtx testCtx2 = getActorTestCtx(1);
TbActorRef actorId1 = actorSystem.createRootActor(ROOT_DISPATCHER, new FailedToInitActor.FailedToInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx1, 1, 3000));
TbActorRef actorId2 = actorSystem.createRootActor(ROOT_DISPATCHER, new FailedToInitActor.FailedToInitActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx2, 2, 1));
actorId1.tell(new IntTbActorMsg(42));
actorId2.tell(new IntTbActorMsg(42));
Assert.assertFalse(testCtx1.getLatch().await(2, TimeUnit.SECONDS));
Assert.assertTrue(testCtx2.getLatch().await(1, TimeUnit.SECONDS));
Assert.assertTrue(testCtx1.getLatch().await(3, TimeUnit.SECONDS));
}
public void testActorsAndMessages(int actorsCount, int msgNumber, int times) throws InterruptedException {
Random random = new Random();
int[] randomIntegers = new int[msgNumber];
long sumTmp = 0;
for (int i = 0; i < msgNumber; i++) {
int tmp = random.nextInt();
randomIntegers[i] = tmp;
sumTmp += tmp;
}
long expected = sumTmp;
List<ActorTestCtx> testCtxes = new ArrayList<>();
List<TbActorRef> actorRefs = new ArrayList<>();
for (int actorIdx = 0; actorIdx < actorsCount; actorIdx++) {
ActorTestCtx testCtx = getActorTestCtx(msgNumber);
actorRefs.add(actorSystem.createRootActor(ROOT_DISPATCHER, new TestRootActor.TestRootActorCreator(
new TbEntityActorId(new DeviceId(UUID.randomUUID())), testCtx)));
testCtxes.add(testCtx);
}
for (int t = 0; t < times; t++) {
long start = System.nanoTime();
for (int i = 0; i < msgNumber; i++) {
int tmp = randomIntegers[i];
submitPool.execute(() -> actorRefs.forEach(actorId -> actorId.tell(new IntTbActorMsg(tmp))));
}
log.info("Submitted all messages");
testCtxes.forEach(ctx -> {
try {
boolean success = ctx.getLatch().await(1, TimeUnit.MINUTES);
if (!success) {
log.warn("Failed: {}, {}", ctx.getActual().get(), ctx.getInvocationCount().get());
}
Assert.assertTrue(success);
Assert.assertEquals(expected, ctx.getActual().get());
Assert.assertEquals(msgNumber, ctx.getInvocationCount().get());
ctx.clear();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long duration = System.nanoTime() - start;
log.info("Time spend: {}ns ({} ms)", duration, TimeUnit.NANOSECONDS.toMillis(duration));
}
}
private ActorTestCtx getActorTestCtx(int i) {
CountDownLatch countDownLatch = new CountDownLatch(1);
AtomicLong actual = new AtomicLong();
AtomicInteger invocations = new AtomicInteger();
return new ActorTestCtx(countDownLatch, invocations, i, actual);
}
}

39
common/actor/src/test/java/org/thingsboard/server/actors/ActorTestCtx.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
@Data
@AllArgsConstructor
public class ActorTestCtx {
private volatile CountDownLatch latch;
private final AtomicInteger invocationCount;
private final int expectedInvocationCount;
private final AtomicLong actual;
public void clear() {
latch = new CountDownLatch(1);
invocationCount.set(0);
actual.set(0L);
}
}

72
common/actor/src/test/java/org/thingsboard/server/actors/FailedToInitActor.java

@ -0,0 +1,72 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class FailedToInitActor extends TestRootActor {
int retryAttempts;
int retryDelay;
int attempts = 0;
public FailedToInitActor(TbActorId actorId, ActorTestCtx testCtx, int retryAttempts, int retryDelay) {
super(actorId, testCtx);
this.retryAttempts = retryAttempts;
this.retryDelay = retryDelay;
}
@Override
public void init(TbActorCtx ctx) throws TbActorException {
if (attempts < retryAttempts) {
attempts++;
throw new TbActorException("Test attempt", new RuntimeException());
} else {
super.init(ctx);
}
}
@Override
public InitFailureStrategy onInitFailure(int attempt, Throwable t) {
return InitFailureStrategy.retryWithDelay(retryDelay);
}
public static class FailedToInitActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
private final int retryAttempts;
private final int retryDelay;
public FailedToInitActorCreator(TbActorId actorId, ActorTestCtx testCtx, int retryAttempts, int retryDelay) {
this.actorId = actorId;
this.testCtx = testCtx;
this.retryAttempts = retryAttempts;
this.retryDelay = retryDelay;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new FailedToInitActor(actorId, testCtx, retryAttempts, retryDelay);
}
}
}

35
common/actor/src/test/java/org/thingsboard/server/actors/IntTbActorMsg.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
public class IntTbActorMsg implements TbActorMsg {
@Getter
private final int value;
public IntTbActorMsg(int value) {
this.value = value;
}
@Override
public MsgType getMsgType() {
return MsgType.QUEUE_TO_RULE_ENGINE_MSG;
}
}

53
common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SlowCreateActor extends TestRootActor {
public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx) {
super(actorId, testCtx);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
testCtx.getInvocationCount().incrementAndGet();
}
public static class SlowCreateActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new SlowCreateActor(actorId, testCtx);
}
}
}

57
common/actor/src/test/java/org/thingsboard/server/actors/SlowInitActor.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class SlowInitActor extends TestRootActor {
public SlowInitActor(TbActorId actorId, ActorTestCtx testCtx) {
super(actorId, testCtx);
}
@Override
public void init(TbActorCtx ctx) throws TbActorException {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.init(ctx);
}
public static class SlowInitActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
public SlowInitActorCreator(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new SlowInitActor(actorId, testCtx);
}
}
}

87
common/actor/src/test/java/org/thingsboard/server/actors/TestRootActor.java

@ -0,0 +1,87 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.TbActorMsg;
@Slf4j
public class TestRootActor extends AbstractTbActor {
@Getter
private final TbActorId actorId;
@Getter
private final ActorTestCtx testCtx;
private boolean initialized;
private long sum;
private int count;
public TestRootActor(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
initialized = true;
}
@Override
public boolean process(TbActorMsg msg) {
if (initialized) {
int value = ((IntTbActorMsg) msg).getValue();
sum += value;
count += 1;
if (count == testCtx.getExpectedInvocationCount()) {
testCtx.getActual().set(sum);
testCtx.getInvocationCount().addAndGet(count);
sum = 0;
count = 0;
testCtx.getLatch().countDown();
}
}
return true;
}
@Override
public void destroy() {
}
public static class TestRootActorCreator implements TbActorCreator {
private final TbActorId actorId;
private final ActorTestCtx testCtx;
public TestRootActorCreator(TbActorId actorId, ActorTestCtx testCtx) {
this.actorId = actorId;
this.testCtx = testCtx;
}
@Override
public TbActorId createActorId() {
return actorId;
}
@Override
public TbActor createActor() {
return new TestRootActor(actorId, testCtx);
}
}
}

14
common/actor/src/test/resources/logback.xml

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="console"/>
</root>
</configuration>

2
common/dao-api/pom.xml

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

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

Loading…
Cancel
Save