Browse Source

Merge branch 'develop/1.5' of github.com:thingsboard/thingsboard into develop/1.5

pull/773/head
Yura 9 years ago
parent
commit
72682dbfb8
  1. 8
      application/pom.xml
  2. 4
      application/src/main/data/upgrade/1.5.0/schema_update.cql
  3. 7
      application/src/main/data/upgrade/1.5.0/schema_update.sql
  4. 2
      application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java
  5. 212
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 116
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  7. 21
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
  8. 63
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  9. 7
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java
  10. 28
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java
  11. 26
      application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java
  12. 3
      application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java
  13. 7
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  14. 7
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
  15. 117
      application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java
  16. 41
      application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java
  17. 43
      application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java
  18. 90
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java
  19. 345
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java
  20. 107
      application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java
  21. 115
      application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java
  22. 209
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  23. 88
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  24. 195
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  25. 61
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainManagerActor.java
  26. 33
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java
  27. 109
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
  28. 116
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  29. 34
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeCtx.java
  30. 21
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java
  31. 39
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
  32. 37
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java
  33. 37
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java
  34. 11
      application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
  35. 5
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  36. 20
      application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java
  37. 23
      application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
  38. 7
      application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java
  39. 7
      application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java
  40. 3
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  41. 39
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  42. 86
      application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java
  43. 49
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java
  44. 4
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java
  45. 5
      application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java
  46. 135
      application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java
  47. 59
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java
  48. 25
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java
  49. 25
      application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java
  50. 7
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  51. 40
      application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java
  52. 129
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  53. 2
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  54. 12
      application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
  55. 4
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  56. 4
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  57. 4
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  58. 2
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  59. 6
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  60. 31
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  61. 20
      application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
  62. 2
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  63. 4
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  64. 36
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  65. 4
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  66. 4
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  67. 32
      application/src/main/java/org/thingsboard/server/controller/HttpValidationCallback.java
  68. 10
      application/src/main/java/org/thingsboard/server/controller/PluginController.java
  69. 139
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  70. 10
      application/src/main/java/org/thingsboard/server/controller/RuleController.java
  71. 586
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  72. 2
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  73. 6
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  74. 2
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  75. 2
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  76. 131
      application/src/main/java/org/thingsboard/server/controller/plugin/PluginApiController.java
  77. 133
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  78. 1
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
  79. 4
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  80. 105
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  81. 3
      application/src/main/java/org/thingsboard/server/service/component/ComponentDiscoveryService.java
  82. 59
      application/src/main/java/org/thingsboard/server/service/executors/AbstractListeningExecutor.java
  83. 32
      application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java
  84. 4
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  85. 100
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  86. 33
      application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java
  87. 33
      application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java
  88. 211
      application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
  89. 295
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  90. 71
      application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java
  91. 1
      application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java
  92. 335
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  93. 563
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  94. 33
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java
  95. 29
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java
  96. 31
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java
  97. 68
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java
  98. 34
      application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java
  99. 53
      application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java
  100. 18
      application/src/main/resources/thingsboard.yml

8
application/pom.xml

@ -53,6 +53,14 @@
<groupId>org.thingsboard</groupId>
<artifactId>extensions-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.rule-engine</groupId>
<artifactId>rule-engine-api</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.rule-engine</groupId>
<artifactId>rule-engine-components</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard</groupId>
<artifactId>extensions-core</artifactId>

4
application/src/main/data/upgrade/1.5.0/schema_update.cql

@ -69,6 +69,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_chain (
search_text text,
first_rule_node_id uuid,
root boolean,
debug_mode boolean,
configuration text,
additional_info text,
PRIMARY KEY (id, tenant_id)
@ -85,9 +86,12 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
id uuid,
type text,
name text,
debug_mode boolean,
search_text text,
configuration text,
additional_info text,
PRIMARY KEY (id)
);
ALTER TABLE thingsboard.device ADD last_connect bigint;
ALTER TABLE thingsboard.device ADD last_update bigint;

7
application/src/main/data/upgrade/1.5.0/schema_update.sql

@ -21,6 +21,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
name varchar(255),
first_rule_node_id varchar(31),
root boolean,
debug_mode boolean,
search_text varchar(255),
tenant_id varchar(31)
);
@ -31,5 +32,9 @@ CREATE TABLE IF NOT EXISTS rule_node (
configuration varchar(10000000),
type varchar(255),
name varchar(255),
debug_mode boolean,
search_text varchar(255)
);
);
ALTER TABLE device ADD COLUMN IF NOT EXISTS last_connect BIGINT;
ALTER TABLE device ADD COLUMN IF NOT EXISTS last_update BIGINT;

2
application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java

@ -19,6 +19,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.Arrays;
@ -26,6 +27,7 @@ import java.util.Arrays;
@SpringBootConfiguration
@EnableAsync
@EnableSwagger2
@EnableScheduling
@ComponentScan({"org.thingsboard.server"})
public class ThingsboardServerApplication {

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

@ -25,15 +25,19 @@ import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.rule.engine.api.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event;
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.TbMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
@ -46,116 +50,200 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.script.JsExecutorService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Optional;
@Slf4j
@Component
public class ActorSystemContext {
private static final String AKKA_CONF_FILE_NAME = "actor-system.conf";
protected final ObjectMapper mapper = new ObjectMapper();
@Getter @Setter private ActorService actorService;
@Getter
@Setter
private ActorService actorService;
@Autowired
@Getter private DiscoveryService discoveryService;
@Getter
private DiscoveryService discoveryService;
@Autowired
@Getter @Setter private ComponentDiscoveryService componentService;
@Getter
@Setter
private ComponentDiscoveryService componentService;
@Autowired
@Getter private ClusterRoutingService routingService;
@Getter
private ClusterRoutingService routingService;
@Autowired
@Getter private ClusterRpcService rpcService;
@Getter
private ClusterRpcService rpcService;
@Autowired
@Getter private DeviceAuthService deviceAuthService;
@Getter
private DeviceAuthService deviceAuthService;
@Autowired
@Getter private DeviceService deviceService;
@Getter
private DeviceService deviceService;
@Autowired
@Getter private AssetService assetService;
@Getter
private AssetService assetService;
@Autowired
@Getter private TenantService tenantService;
@Getter
private TenantService tenantService;
@Autowired
@Getter private CustomerService customerService;
@Getter
private CustomerService customerService;
@Autowired
@Getter private RuleService ruleService;
@Getter
private UserService userService;
@Autowired
@Getter private PluginService pluginService;
@Getter
private RuleService ruleService;
@Autowired
@Getter private TimeseriesService tsService;
@Getter
private RuleChainService ruleChainService;
@Autowired
@Getter private AttributesService attributesService;
@Getter
private PluginService pluginService;
@Autowired
@Getter private EventService eventService;
@Getter
private TimeseriesService tsService;
@Autowired
@Getter private AlarmService alarmService;
@Getter
private AttributesService attributesService;
@Autowired
@Getter private RelationService relationService;
@Getter
private EventService eventService;
@Autowired
@Getter private AuditLogService auditLogService;
@Getter
private AlarmService alarmService;
@Autowired
@Getter @Setter private PluginWebSocketMsgEndpoint wsMsgEndpoint;
@Getter
private RelationService relationService;
@Autowired
@Getter
private AuditLogService auditLogService;
@Autowired
@Getter
private TelemetrySubscriptionService tsSubService;
@Autowired
@Getter
@Setter
private PluginWebSocketMsgEndpoint wsMsgEndpoint;
@Autowired
@Getter
private JsExecutorService jsExecutor;
@Autowired
@Getter
private MailExecutorService mailExecutor;
@Autowired
@Getter
private DbCallbackExecutorService dbCallbackExecutor;
@Autowired
@Getter
private MailService mailService;
@Value("${actors.session.sync.timeout}")
@Getter private long syncSessionTimeout;
@Getter
private long syncSessionTimeout;
@Value("${actors.plugin.termination.delay}")
@Getter private long pluginActorTerminationDelay;
@Getter
private long pluginActorTerminationDelay;
@Value("${actors.plugin.processing.timeout}")
@Getter private long pluginProcessingTimeout;
@Getter
private long pluginProcessingTimeout;
@Value("${actors.plugin.error_persist_frequency}")
@Getter private long pluginErrorPersistFrequency;
@Getter
private long pluginErrorPersistFrequency;
@Value("${actors.rule.chain.error_persist_frequency}")
@Getter
private long ruleChainErrorPersistFrequency;
@Value("${actors.rule.node.error_persist_frequency}")
@Getter
private long ruleNodeErrorPersistFrequency;
@Value("${actors.rule.termination.delay}")
@Getter private long ruleActorTerminationDelay;
@Getter
private long ruleActorTerminationDelay;
@Value("${actors.rule.error_persist_frequency}")
@Getter private long ruleErrorPersistFrequency;
@Getter
private long ruleErrorPersistFrequency;
@Value("${actors.statistics.enabled}")
@Getter private boolean statisticsEnabled;
@Getter
private boolean statisticsEnabled;
@Value("${actors.statistics.persist_frequency}")
@Getter private long statisticsPersistFrequency;
@Getter
private long statisticsPersistFrequency;
@Value("${actors.tenant.create_components_on_init}")
@Getter private boolean tenantComponentsInitEnabled;
@Getter
private boolean tenantComponentsInitEnabled;
@Getter @Setter private ActorSystem actorSystem;
@Getter
@Setter
private ActorSystem actorSystem;
@Getter @Setter private ActorRef appActor;
@Getter
@Setter
private ActorRef appActor;
@Getter @Setter private ActorRef sessionManagerActor;
@Getter
@Setter
private ActorRef sessionManagerActor;
@Getter @Setter private ActorRef statsActor;
@Getter
@Setter
private ActorRef statsActor;
@Getter private final Config config;
@Getter
private final Config config;
public ActorSystemContext() {
config = ConfigFactory.parseResources(AKKA_CONF_FILE_NAME).withFallback(ConfigFactory.load());
@ -187,7 +275,7 @@ public class ActorSystemContext {
eventService.save(event);
}
private String toString(Exception e) {
private String toString(Throwable e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
return sw.toString();
@ -207,4 +295,60 @@ public class ActorSystemContext {
private JsonNode toBodyJson(ServerAddress server, String method, String body) {
return mapper.createObjectNode().put("server", server.toString()).put("method", method).put("error", body);
}
public String getServerAddress() {
return discoveryService.getCurrentServer().getServerAddress().toString();
}
public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
persistDebug(tenantId, entityId, "IN", tbMsg, null);
}
public void persistDebugInput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) {
persistDebug(tenantId, entityId, "IN", tbMsg, error);
}
public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg, Throwable error) {
persistDebug(tenantId, entityId, "OUT", tbMsg, error);
}
public void persistDebugOutput(TenantId tenantId, EntityId entityId, TbMsg tbMsg) {
persistDebug(tenantId, entityId, "OUT", tbMsg, null);
}
private void persistDebug(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, Throwable error) {
try {
Event event = new Event();
event.setTenantId(tenantId);
event.setEntityId(entityId);
event.setType(DataConstants.DEBUG_RULE_NODE);
String metadata = mapper.writeValueAsString(tbMsg.getMetaData().getData());
ObjectNode node = mapper.createObjectNode()
.put("type", type)
.put("server", getServerAddress())
.put("entityId", tbMsg.getOriginator().getId().toString())
.put("entityName", tbMsg.getOriginator().getEntityType().name())
.put("msgId", tbMsg.getId().toString())
.put("msgType", tbMsg.getType())
.put("dataType", tbMsg.getDataType().name())
.put("data", tbMsg.getData())
.put("metadata", metadata);
if (error != null) {
node = node.put("error", toString(error));
}
event.setBody(node);
eventService.save(event);
} catch (IOException ex) {
log.warn("Failed to persist rule node debug message", ex);
}
}
public static Exception toException(Throwable error) {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}
}

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

@ -22,48 +22,41 @@ import akka.event.LoggingAdapter;
import akka.japi.Function;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.plugin.PluginManager;
import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
import org.thingsboard.server.actors.shared.rule.RuleManager;
import org.thingsboard.server.actors.shared.rule.SystemRuleManager;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager;
import org.thingsboard.server.actors.tenant.TenantActor;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
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.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
import scala.concurrent.duration.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class AppActor extends ContextAwareActor {
public class AppActor extends RuleChainManagerActor {
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID);
private final RuleManager ruleManager;
private final PluginManager pluginManager;
private final TenantService tenantService;
private final Map<TenantId, ActorRef> tenantActors;
private AppActor(ActorSystemContext systemContext) {
super(systemContext);
this.ruleManager = new SystemRuleManager(systemContext);
this.pluginManager = new SystemPluginManager(systemContext);
super(systemContext, new SystemRuleChainManager(systemContext), new SystemPluginManager(systemContext));
this.tenantService = systemContext.getTenantService();
this.tenantActors = new HashMap<>();
}
@ -77,8 +70,7 @@ public class AppActor extends ContextAwareActor {
public void preStart() {
logger.info("Starting main system actor.");
try {
ruleManager.init(this.context());
pluginManager.init(this.context());
initRuleChains();
if (systemContext.isTenantComponentsInitEnabled()) {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT);
@ -96,29 +88,51 @@ public class AppActor extends ContextAwareActor {
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);
if (msg instanceof ToDeviceActorMsg) {
processDeviceMsg((ToDeviceActorMsg) msg);
} else if (msg instanceof ToPluginActorMsg) {
onToPluginMsg((ToPluginActorMsg) msg);
} else if (msg instanceof ToRuleActorMsg) {
onToRuleMsg((ToRuleActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
} else if (msg instanceof Terminated) {
processTermination((Terminated) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
onPluginTerminated((PluginTerminationMsg) msg);
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case SERVICE_TO_RULE_ENGINE_MSG:
onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
default:
return false;
}
return true;
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
//TODO: ashvayka handle this.
} else {
logger.warning("Unknown message: {}!", msg);
getOrCreateTenantActor(msg.getTenantId()).tell(msg, self());
}
}
// @Override
// public void onReceive(Object msg) throws Exception {
// logger.debug("Received message: {}", msg);
// if (msg instanceof ToDeviceActorMsg) {
// processDeviceMsg((ToDeviceActorMsg) msg);
// } else if (msg instanceof ToPluginActorMsg) {
// onToPluginMsg((ToPluginActorMsg) msg);
// } else if (msg instanceof ToDeviceActorNotificationMsg) {
// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
// } else if (msg instanceof Terminated) {
// processTermination((Terminated) msg);
// } else if (msg instanceof ClusterEventMsg) {
// broadcast(msg);
// } else if (msg instanceof ComponentLifecycleMsg) {
// onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
// } else if (msg instanceof PluginTerminationMsg) {
// onPluginTerminated((PluginTerminationMsg) msg);
// } else {
// logger.warning("Unknown message: {}!", msg);
// }
// }
private void onPluginTerminated(PluginTerminationMsg msg) {
pluginManager.remove(msg.getId());
}
@ -128,20 +142,10 @@ public class AppActor extends ContextAwareActor {
tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onToRuleMsg(ToRuleActorMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
target.tell(msg, ActorRef.noSender());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
target = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
} else {
target = getOrCreateTenantActor(msg.getPluginTenantId());
}
@ -149,26 +153,16 @@ public class AppActor extends ContextAwareActor {
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
ActorRef target = null;
ActorRef target;
if (SYSTEM_TENANT.equals(msg.getTenantId())) {
Optional<PluginId> pluginId = msg.getPluginId();
Optional<RuleId> ruleId = msg.getRuleId();
if (pluginId.isPresent()) {
target = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get());
} else if (ruleId.isPresent()) {
Optional<ActorRef> ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent());
if (ref.isPresent()) {
target = ref.get();
} else {
logger.debug("Failed to find actor for rule: [{}]", ruleId);
return;
}
}
target = getEntityActorRef(msg.getEntityId());
} else {
target = getOrCreateTenantActor(msg.getTenantId());
}
if (target != null) {
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("Invalid component lifecycle msg: {}", msg);
}
}
@ -180,7 +174,7 @@ public class AppActor extends ContextAwareActor {
TenantId tenantId = toDeviceActorMsg.getTenantId();
ActorRef tenantActor = getOrCreateTenantActor(tenantId);
if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
// tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
} else {
tenantActor.tell(toDeviceActorMsg, context().self());
}

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

@ -18,19 +18,19 @@ package org.thingsboard.server.actors.device;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.RulesProcessedMsg;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
public class DeviceActor extends ContextAwareActor {
@ -47,13 +47,18 @@ public class DeviceActor extends ContextAwareActor {
this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId);
}
@Override
protected boolean process(TbActorMsg msg) {
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RuleChainDeviceMsg) {
processor.process(context(), (RuleChainDeviceMsg) msg);
} else if (msg instanceof RulesProcessedMsg) {
processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
} else if (msg instanceof ToDeviceActorMsg) {
// if (msg instanceof RuleChainDeviceMsg) {
// processor.process(context(), (RuleChainDeviceMsg) msg);
// } else if (msg instanceof RulesProcessedMsg) {
// processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
if (msg instanceof ToDeviceActorMsg) {
processor.process(context(), (ToDeviceActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
if (msg instanceof DeviceAttributesEventNotificationMsg) {

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

@ -19,9 +19,7 @@ import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.*;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.actors.tenant.RuleChainDeviceMsg;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
@ -37,15 +35,10 @@ import org.thingsboard.server.common.msg.session.FromDeviceMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.common.msg.session.SessionType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.extensions.api.device.*;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutIntMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.extensions.api.plugins.msg.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
@ -230,18 +223,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
}
void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
ChainProcessingContext ctx = new ChainProcessingContext(md);
if (ctx.getChainLength() > 0) {
RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
ActorRef ruleActorRef = ctx.getCurrentActor();
ruleActorRef.tell(msg, ActorRef.noSender());
} else {
context.self().tell(new RulesProcessedMsg(ctx), context.self());
}
}
// void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
// ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
// srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
// ChainProcessingContext ctx = new ChainProcessingContext(md);
// if (ctx.getChainLength() > 0) {
// RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
// ActorRef ruleActorRef = ctx.getCurrentActor();
// ruleActorRef.tell(msg, ActorRef.noSender());
// } else {
// context.self().tell(new RulesProcessedMsg(ctx), context.self());
// }
// }
void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();
@ -302,18 +295,18 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
);
}
void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
ChainProcessingContext ctx = msg.getCtx();
ToDeviceActorMsg inMsg = ctx.getInMsg();
SessionId sid = inMsg.getSessionId();
ToDeviceSessionActorMsg response;
if (ctx.getResponse() != null) {
response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
} else {
response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
}
sendMsgToSessionActor(response, inMsg.getServerAddress());
}
// void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
// ChainProcessingContext ctx = msg.getCtx();
// ToDeviceActorMsg inMsg = ctx.getInMsg();
// SessionId sid = inMsg.getSessionId();
// ToDeviceSessionActorMsg response;
// if (ctx.getResponse() != null) {
// response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
// } else {
// response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
// }
// sendMsgToSessionActor(response, inMsg.getServerAddress());
// }
private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
SessionId sessionId = msg.getSessionId();

7
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
@ -40,6 +41,12 @@ public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProc
logger, context().parent(), context().self()));
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof PluginWebsocketMsg) {

28
application/src/main/java/org/thingsboard/server/actors/plugin/PluginActorMessageProcessor.java

@ -28,9 +28,14 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.extensions.api.plugins.Plugin;
import org.thingsboard.server.extensions.api.plugins.PluginInitializationException;
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
@ -57,7 +62,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
}
@Override
public void start() throws Exception {
public void start(ActorContext context) throws Exception {
logger.info("[{}] Going to start plugin actor.", entityId);
pluginMd = systemContext.getPluginService().findPluginById(entityId);
if (pluginMd == null) {
@ -76,7 +81,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
}
@Override
public void stop() throws Exception {
public void stop(ActorContext context) throws Exception {
onStop();
}
@ -98,7 +103,20 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException {
if (state == ComponentLifecycleState.ACTIVE) {
pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg());
try {
pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg());
} catch (Exception ex) {
logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex);
RuleToPluginMsg ruleMsg = msg.getMsg();
MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR;
Integer requestId = 0;
if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) {
requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId();
}
trustedCtx.reply(
new ResponsePluginToRuleMsg(ruleMsg.getUid(), tenantId, msg.getRuleId(),
BasicStatusCodeResponse.onError(responceMsgType, requestId, ex)));
}
} else {
//TODO: reply with plugin suspended message
}
@ -191,7 +209,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
if (pluginImpl != null) {
pluginImpl.stop(trustedCtx);
}
start();
start(context);
}
}
@ -217,7 +235,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
pluginImpl.resume(trustedCtx);
logger.info("[{}] Plugin resumed.", entityId);
} else {
start();
start(context);
}
}

26
application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
@ -330,6 +331,9 @@ public final class PluginProcessingContext implements PluginContext {
case RULE:
validateRule(ctx, entityId, callback);
return;
case RULE_CHAIN:
validateRuleChain(ctx, entityId, callback);
return;
case PLUGIN:
validatePlugin(ctx, entityId, callback);
return;
@ -411,6 +415,28 @@ public final class PluginProcessingContext implements PluginContext {
}
}
private void validateRuleChain(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<RuleChain> ruleChainFuture = pluginCtx.ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId()));
Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> {
if (ruleChain == null) {
return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!");
} else {
if (ctx.isTenantAdmin() && !ruleChain.getTenantId().equals(ctx.getTenantId())) {
return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
} else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
}
}
}));
}
}
private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) {
if (ctx.isCustomerUser()) {
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));

3
application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java

@ -32,6 +32,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -56,6 +57,7 @@ public final class SharedPluginProcessingContext {
final AssetService assetService;
final DeviceService deviceService;
final RuleService ruleService;
final RuleChainService ruleChainService;
final PluginService pluginService;
final CustomerService customerService;
final TenantService tenantService;
@ -84,6 +86,7 @@ public final class SharedPluginProcessingContext {
this.rpcService = sysContext.getRpcService();
this.routingService = sysContext.getRoutingService();
this.ruleService = sysContext.getRuleService();
this.ruleChainService = sysContext.getRuleChainService();
this.pluginService = sysContext.getPluginService();
this.customerService = sysContext.getCustomerService();
this.tenantService = sysContext.getTenantService();

7
application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.actors.ActorSystemContext;
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.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
@ -56,6 +57,12 @@ public class RpcManagerActor extends ContextAwareActor {
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RpcSessionTellMsg) {

7
application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java

@ -23,6 +23,7 @@ import io.grpc.stub.StreamObserver;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
@ -47,6 +48,12 @@ public class RpcSessionActor extends ContextAwareActor {
this.sessionId = sessionId;
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof RpcSessionTellMsg) {

117
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingContext.java

@ -1,117 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
import akka.actor.ActorRef;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.extensions.api.device.DeviceAttributes;
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
public class ChainProcessingContext {
private final ChainProcessingMetaData md;
private final int index;
private final RuleEngineError error;
private ToDeviceMsg response;
public ChainProcessingContext(ChainProcessingMetaData md) {
super();
this.md = md;
this.index = 0;
this.error = RuleEngineError.NO_RULES;
}
private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) {
super();
this.md = other.md;
this.index = other.index + indexOffset;
this.error = error;
this.response = other.response;
if (this.index < 0 || this.index >= this.md.chain.size()) {
throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!");
}
}
public ActorRef getDeviceActor() {
return md.originator;
}
public ActorRef getCurrentActor() {
return md.chain.getRuleActorMd(index).getActorRef();
}
public boolean hasNext() {
return (getChainLength() - 1) > index;
}
public boolean isFailure() {
return (error != null && error.isCritical()) || (response != null && !response.isSuccess());
}
public ChainProcessingContext getNext() {
return new ChainProcessingContext(this, 1, this.error);
}
public ChainProcessingContext withError(RuleEngineError error) {
if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) {
return new ChainProcessingContext(this, 0, error);
} else {
return this;
}
}
public int getChainLength() {
return md.chain.size();
}
public ToDeviceActorMsg getInMsg() {
return md.inMsg;
}
public DeviceMetaData getDeviceMetaData() {
return md.deviceMetaData;
}
public String getDeviceName() {
return md.deviceMetaData.getDeviceName();
}
public String getDeviceType() {
return md.deviceMetaData.getDeviceType();
}
public DeviceAttributes getAttributes() {
return md.deviceMetaData.getDeviceAttributes();
}
public ToDeviceMsg getResponse() {
return response;
}
public void mergeResponse(ToDeviceMsg response) {
// TODO add merge logic
this.response = response;
}
public RuleEngineErrorMsg getError() {
return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error);
}
}

41
application/src/main/java/org/thingsboard/server/actors/rule/ChainProcessingMetaData.java

@ -1,41 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
import akka.actor.ActorRef;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
/**
* Immutable part of chain processing data;
*
* @author ashvayka
*/
public final class ChainProcessingMetaData {
final RuleActorChain chain;
final ToDeviceActorMsg inMsg;
final ActorRef originator;
final DeviceMetaData deviceMetaData;
public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceMetaData deviceMetaData, ActorRef originator) {
super();
this.chain = chain;
this.inMsg = inMsg;
this.originator = originator;
this.deviceMetaData = deviceMetaData;
}
}

43
application/src/main/java/org/thingsboard/server/actors/rule/ComplexRuleActorChain.java

@ -1,43 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
public class ComplexRuleActorChain implements RuleActorChain {
private final RuleActorChain systemChain;
private final RuleActorChain tenantChain;
public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) {
super();
this.systemChain = systemChain;
this.tenantChain = tenantChain;
}
@Override
public int size() {
return systemChain.size() + tenantChain.size();
}
@Override
public RuleActorMetaData getRuleActorMd(int index) {
if (index < systemChain.size()) {
return systemChain.getRuleActorMd(index);
} else {
return tenantChain.getRuleActorMd(index - systemChain.size());
}
}
}

90
application/src/main/java/org/thingsboard/server/actors/rule/RuleActor.java

@ -1,90 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ComponentActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> {
private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) {
super(systemContext, tenantId, ruleId);
setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger));
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Received message: {}", id, msg);
if (msg instanceof RuleProcessingMsg) {
try {
processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onDeviceMsg", e);
}
} else if (msg instanceof PluginToRuleMsg<?>) {
try {
processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg);
} catch (Exception e) {
logAndPersist("onPluginMsg", e);
}
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
onClusterEventMsg((ClusterEventMsg) msg);
} else if (msg instanceof RuleToPluginTimeoutMsg) {
try {
processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg);
} catch (Exception e) {
logAndPersist("onTimeoutMsg", e);
}
} else if (msg instanceof StatsPersistTick) {
onStatsPersistTick(id);
} else {
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName());
}
}
public static class ActorCreator extends ContextBasedCreator<RuleActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleId ruleId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) {
super(context);
this.tenantId = tenantId;
this.ruleId = ruleId;
}
@Override
public RuleActor create() throws Exception {
return new RuleActor(context, tenantId, ruleId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getRuleErrorPersistFrequency();
}
}

345
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMessageProcessor.java

@ -1,345 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
import java.util.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.util.StringUtils;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.BasicRequest;
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
import org.thingsboard.server.common.msg.core.RuleEngineError;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.session.MsgType;
import org.thingsboard.server.common.msg.session.ToDeviceMsg;
import org.thingsboard.server.common.msg.session.ex.ProcessingTimeoutException;
import org.thingsboard.server.extensions.api.rules.*;
import org.thingsboard.server.extensions.api.plugins.PluginAction;
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg;
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
import com.fasterxml.jackson.databind.JsonNode;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> {
private final RuleProcessingContext ruleCtx;
private final Map<UUID, RuleProcessingMsg> pendingMsgMap;
private RuleMetaData ruleMd;
private ComponentLifecycleState state;
private List<RuleFilter> filters;
private RuleProcessor processor;
private PluginAction action;
private TenantId pluginTenantId;
private PluginId pluginId;
protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) {
super(systemContext, logger, tenantId, ruleId);
this.pendingMsgMap = new HashMap<>();
this.ruleCtx = new RuleProcessingContext(systemContext, ruleId);
}
@Override
public void start() throws Exception {
logger.info("[{}][{}] Starting rule actor.", entityId, tenantId);
ruleMd = systemContext.getRuleService().findRuleById(entityId);
if (ruleMd == null) {
throw new RuleInitializationException("Rule not found!");
}
state = ruleMd.getState();
if (state == ComponentLifecycleState.ACTIVE) {
logger.info("[{}] Rule is active. Going to initialize rule components.", entityId);
initComponent();
} else {
logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId);
}
logger.info("[{}][{}] Started rule actor.", entityId, tenantId);
}
@Override
public void stop() throws Exception {
onStop();
}
private void initComponent() throws RuleException {
try {
if (!ruleMd.getFilters().isArray()) {
throw new RuntimeException("Filters are not array!");
}
fetchPluginInfo();
initFilters();
initProcessor();
initAction();
} catch (RuntimeException e) {
throw new RuleInitializationException("Unknown runtime exception!", e);
} catch (InstantiationException e) {
throw new RuleInitializationException("No default constructor for rule implementation!", e);
} catch (IllegalAccessException e) {
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
} catch (ClassNotFoundException e) {
throw new RuleInitializationException("Rule Class not found!", e);
} catch (Exception e) {
throw new RuleException(e.getMessage(), e);
}
}
private void initAction() throws Exception {
if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) {
action = initComponent(ruleMd.getAction());
}
}
private void initProcessor() throws Exception {
if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) {
processor = initComponent(ruleMd.getProcessor());
}
}
private void initFilters() throws Exception {
filters = new ArrayList<>(ruleMd.getFilters().size());
for (int i = 0; i < ruleMd.getFilters().size(); i++) {
filters.add(initComponent(ruleMd.getFilters().get(i)));
}
}
private void fetchPluginInfo() {
if (!StringUtils.isEmpty(ruleMd.getPluginToken())) {
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken());
pluginTenantId = pluginMd.getTenantId();
pluginId = pluginMd.getId();
}
}
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException {
if (state != ComponentLifecycleState.ACTIVE) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES);
return;
}
ChainProcessingContext chainCtx = msg.getCtx();
ToDeviceActorMsg inMsg = chainCtx.getInMsg();
ruleCtx.update(inMsg, chainCtx.getDeviceMetaData());
logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg);
for (RuleFilter filter : filters) {
if (!filter.filter(ruleCtx, inMsg)) {
logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED);
return;
}
}
RuleProcessingMetaData inMsgMd;
if (processor != null) {
logger.debug("[{}] Going to process in msg: {}", entityId, inMsg);
inMsgMd = processor.process(ruleCtx, inMsg);
} else {
inMsgMd = new RuleProcessingMetaData();
}
logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg);
if (action != null) {
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd);
if (ruleToPluginMsgOptional.isPresent()) {
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get();
logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg);
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self());
if (action.isOneWayAction()) {
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
return;
} else {
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg);
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout());
return;
}
}
}
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId);
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS);
}
void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid());
if (pendingMsg != null) {
ChainProcessingContext ctx = pendingMsg.getCtx();
Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg);
if (ruleResponseOptional.isPresent()) {
ctx.mergeResponse(ruleResponseOptional.get());
pushToNextRule(context, ctx, null);
} else {
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS);
}
} else {
logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid());
}
}
void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) {
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId());
if (pendingMsg != null) {
logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg);
ChainProcessingContext ctx = pendingMsg.getCtx();
pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT);
}
}
private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) {
if (error != null) {
ctx = ctx.withError(error);
}
if (ctx.isFailure()) {
logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else if (!ctx.hasNext()) {
logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender());
} else {
logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId());
ChainProcessingContext nextTask = ctx.getNext();
nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self());
}
}
@Override
public void onCreated(ActorContext context) {
logger.info("[{}] Going to process onCreated rule.", entityId);
}
@Override
public void onUpdate(ActorContext context) throws RuleException {
RuleMetaData oldRuleMd = ruleMd;
ruleMd = systemContext.getRuleService().findRuleById(entityId);
logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd);
try {
fetchPluginInfo();
if (filters == null || !Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) {
logger.info("[{}] Rule filters require restart due to json change from {} to {}.",
entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters()));
stopFilters();
initFilters();
}
if (processor == null || !Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) {
logger.info("[{}] Rule processor require restart due to configuration change.", entityId);
stopProcessor();
initProcessor();
}
if (action == null || !Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) {
logger.info("[{}] Rule action require restart due to configuration change.", entityId);
stopAction();
initAction();
}
} catch (RuntimeException e) {
throw new RuleInitializationException("Unknown runtime exception!", e);
} catch (InstantiationException e) {
throw new RuleInitializationException("No default constructor for rule implementation!", e);
} catch (IllegalAccessException e) {
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e);
} catch (ClassNotFoundException e) {
throw new RuleInitializationException("Rule Class not found!", e);
} catch (JsonProcessingException e) {
throw new RuleInitializationException("Rule configuration is invalid!", e);
} catch (Exception e) {
throw new RuleInitializationException(e.getMessage(), e);
}
}
@Override
public void onActivate(ActorContext context) throws Exception {
logger.info("[{}] Going to process onActivate rule.", entityId);
this.state = ComponentLifecycleState.ACTIVE;
if (filters != null) {
filters.forEach(RuleLifecycleComponent::resume);
if (processor != null) {
processor.resume();
} else {
initProcessor();
}
if (action != null) {
action.resume();
}
logger.info("[{}] Rule resumed.", entityId);
} else {
start();
}
}
@Override
public void onSuspend(ActorContext context) {
logger.info("[{}] Going to process onSuspend rule.", entityId);
this.state = ComponentLifecycleState.SUSPENDED;
if (filters != null) {
filters.forEach(f -> f.suspend());
}
if (processor != null) {
processor.suspend();
}
if (action != null) {
action.suspend();
}
}
@Override
public void onStop(ActorContext context) {
logger.info("[{}] Going to process onStop rule.", entityId);
onStop();
scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay());
}
private void onStop() {
this.state = ComponentLifecycleState.SUSPENDED;
stopFilters();
stopProcessor();
stopAction();
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
//Do nothing
}
private void stopAction() {
if (action != null) {
action.stop();
}
}
private void stopProcessor() {
if (processor != null) {
processor.stop();
}
}
private void stopFilters() {
if (filters != null) {
filters.forEach(f -> f.stop());
}
}
}

107
application/src/main/java/org/thingsboard/server/actors/rule/RuleActorMetaData.java

@ -1,107 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
import java.util.Comparator;
import org.thingsboard.server.common.data.id.RuleId;
import akka.actor.ActorRef;
public class RuleActorMetaData {
private final RuleId ruleId;
private final boolean systemRule;
private final int weight;
private final ActorRef actorRef;
public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() {
@Override
public int compare(RuleActorMetaData r1, RuleActorMetaData r2) {
if (r1.isSystemRule() && !r2.isSystemRule()) {
return 1;
} else if (!r1.isSystemRule() && r2.isSystemRule()) {
return -1;
} else {
return Integer.compare(r2.getWeight(), r1.getWeight());
}
}
};
public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) {
return new RuleActorMetaData(ruleId, true, weight, actorRef);
}
public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) {
return new RuleActorMetaData(ruleId, false, weight, actorRef);
}
private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) {
super();
this.ruleId = ruleId;
this.systemRule = systemRule;
this.weight = weight;
this.actorRef = actorRef;
}
public RuleId getRuleId() {
return ruleId;
}
public boolean isSystemRule() {
return systemRule;
}
public int getWeight() {
return weight;
}
public ActorRef getActorRef() {
return actorRef;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RuleActorMetaData other = (RuleActorMetaData) obj;
if (ruleId == null) {
if (other.ruleId != null)
return false;
} else if (!ruleId.equals(other.ruleId))
return false;
return true;
}
@Override
public String toString() {
return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]";
}
}

115
application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingContext.java

@ -1,115 +0,0 @@
/**
* Copyright © 2016-2018 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.rule;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.extensions.api.device.DeviceMetaData;
import org.thingsboard.server.extensions.api.rules.RuleContext;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
public class RuleProcessingContext implements RuleContext {
private final TimeseriesService tsService;
private final EventService eventService;
private final AlarmService alarmService;
private final RuleId ruleId;
private TenantId tenantId;
private CustomerId customerId;
private DeviceId deviceId;
private DeviceMetaData deviceMetaData;
RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) {
this.tsService = systemContext.getTsService();
this.eventService = systemContext.getEventService();
this.alarmService = systemContext.getAlarmService();
this.ruleId = ruleId;
}
void update(ToDeviceActorMsg toDeviceActorMsg, DeviceMetaData deviceMetaData) {
this.tenantId = toDeviceActorMsg.getTenantId();
this.customerId = toDeviceActorMsg.getCustomerId();
this.deviceId = toDeviceActorMsg.getDeviceId();
this.deviceMetaData = deviceMetaData;
}
@Override
public RuleId getRuleId() {
return ruleId;
}
@Override
public DeviceMetaData getDeviceMetaData() {
return deviceMetaData;
}
@Override
public Event save(Event event) {
checkEvent(event);
return eventService.save(event);
}
@Override
public Optional<Event> saveIfNotExists(Event event) {
checkEvent(event);
return eventService.saveIfNotExists(event);
}
@Override
public Optional<Event> findEvent(String eventType, String eventUid) {
return eventService.findEvent(tenantId, deviceId, eventType, eventUid);
}
@Override
public Alarm createOrUpdateAlarm(Alarm alarm) {
alarm.setTenantId(tenantId);
return alarmService.createOrUpdateAlarm(alarm);
}
public Optional<Alarm> findLatestAlarm(EntityId originator, String alarmType) {
try {
return Optional.ofNullable(alarmService.findLatestByOriginatorAndType(tenantId, originator, alarmType).get());
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to lookup alarm!", e);
}
}
@Override
public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTs) {
return alarmService.clearAlarm(alarmId, clearTs);
}
private void checkEvent(Event event) {
if (event.getTenantId() == null) {
event.setTenantId(tenantId);
} else if (!tenantId.equals(event.getTenantId())) {
throw new IllegalArgumentException("Invalid Tenant id!");
}
if (event.getEntityId() == null) {
event.setEntityId(deviceId);
}
}
}

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

@ -0,0 +1,209 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import akka.actor.ActorRef;
import akka.actor.Cancellable;
import com.google.common.base.Function;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.actors.ActorSystemContext;
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.TbMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.service.script.NashornJsEngine;
import scala.concurrent.duration.Duration;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* Created by ashvayka on 19.03.18.
*/
class DefaultTbContext implements TbContext {
private static final Function<? super List<Void>, ? extends Void> LIST_VOID_FUNCTION = v -> null;
private final ActorSystemContext mainCtx;
private final RuleNodeCtx nodeCtx;
public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) {
this.mainCtx = mainCtx;
this.nodeCtx = nodeCtx;
}
@Override
public void tellNext(TbMsg msg) {
tellNext(msg, (String) null);
}
@Override
public void tellNext(TbMsg msg, String relationType) {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg);
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor());
}
@Override
public void tellSelf(TbMsg msg, long delayMs) {
//TODO: add persistence layer
scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor());
}
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
mainCtx.getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, mainCtx.getActorSystem().dispatcher(), nodeCtx.getSelfActor());
}
@Override
public void tellOthers(TbMsg msg) {
throw new RuntimeException("Not Implemented!");
}
@Override
public void tellSibling(TbMsg msg, ServerAddress address) {
throw new RuntimeException("Not Implemented!");
}
@Override
public void spawn(TbMsg msg) {
throw new RuntimeException("Not Implemented!");
}
@Override
public void ack(TbMsg msg) {
}
@Override
public void tellError(TbMsg msg, Throwable th) {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
}
nodeCtx.getSelfActor().tell(new RuleNodeToSelfErrorMsg(msg, th), nodeCtx.getSelfActor());
}
@Override
public void updateSelf(RuleNode self) {
nodeCtx.setSelf(self);
}
@Override
public RuleNodeId getSelfId() {
return nodeCtx.getSelf().getId();
}
@Override
public TenantId getTenantId() {
return nodeCtx.getTenantId();
}
@Override
public void tellNext(TbMsg msg, Set<String> relationTypes) {
relationTypes.forEach(type -> tellNext(msg, type));
}
@Override
public ListeningExecutor getJsExecutor() {
return mainCtx.getJsExecutor();
}
@Override
public ListeningExecutor getMailExecutor() {
return mainCtx.getMailExecutor();
}
@Override
public ListeningExecutor getDbCallbackExecutor() {
return mainCtx.getDbCallbackExecutor();
}
@Override
public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) {
return new NashornJsEngine(script, functionName, argNames);
}
@Override
public AttributesService getAttributesService() {
return mainCtx.getAttributesService();
}
@Override
public CustomerService getCustomerService() {
return mainCtx.getCustomerService();
}
@Override
public UserService getUserService() {
return mainCtx.getUserService();
}
@Override
public PluginService getPluginService() {
return mainCtx.getPluginService();
}
@Override
public AssetService getAssetService() {
return mainCtx.getAssetService();
}
@Override
public DeviceService getDeviceService() {
return mainCtx.getDeviceService();
}
@Override
public AlarmService getAlarmService() {
return mainCtx.getAlarmService();
}
@Override
public RuleChainService getRuleChainService() {
return mainCtx.getRuleChainService();
}
@Override
public TimeseriesService getTimeseriesService() {
return mainCtx.getTsService();
}
@Override
public RuleEngineTelemetryService getTelemetryService() {
return mainCtx.getTsSubService();
}
@Override
public RelationService getRelationService() {
return mainCtx.getRelationService();
}
@Override
public MailService getMailService() {
return mainCtx.getMailService();
}
}

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

@ -0,0 +1,88 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import akka.actor.OneForOneStrategy;
import akka.actor.SupervisorStrategy;
import org.thingsboard.server.actors.ActorSystemContext;
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.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import scala.concurrent.duration.Duration;
public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMessageProcessor> {
private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) {
super(systemContext, tenantId, ruleChainId);
setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext,
logger, context().parent(), context().self()));
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case SERVICE_TO_RULE_ENGINE_MSG:
processor.onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
break;
default:
return false;
}
return true;
}
public static class ActorCreator extends ContextBasedCreator<RuleChainActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleChainId ruleChainId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId pluginId) {
super(context);
this.tenantId = tenantId;
this.ruleChainId = pluginId;
}
@Override
public RuleChainActor create() throws Exception {
return new RuleChainActor(context, tenantId, ruleChainId);
}
}
@Override
protected long getErrorPersistFrequency() {
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();
});
}

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

@ -0,0 +1,195 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
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.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private final ActorRef parent;
private final ActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes;
private final RuleChainService service;
private RuleNodeId firstId;
private RuleNodeCtx firstNode;
RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, ruleChainId);
this.parent = parent;
this.self = self;
this.nodeActors = new HashMap<>();
this.nodeRoutes = new HashMap<>();
this.service = systemContext.getRuleChainService();
}
@Override
public void start(ActorContext context) throws Exception {
RuleChain ruleChain = service.findRuleChainById(entityId);
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
}
initRoutes(ruleChain, ruleNodeList);
}
@Override
public void onUpdate(ActorContext context) throws Exception {
RuleChain ruleChain = service.findRuleChainById(entityId);
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
for (RuleNode ruleNode : ruleNodeList) {
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
} else {
existing.setSelf(ruleNode);
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), 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 -> {
RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
});
initRoutes(ruleChain, ruleNodeList);
}
@Override
public void stop(ActorContext context) throws Exception {
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop);
nodeActors.clear();
nodeRoutes.clear();
context.stop(self);
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
}
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.getId()))
.withDispatcher(dispatcherName), ruleNode.getId().toString());
}
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) {
nodeRoutes.clear();
// Populating the routes map;
for (RuleNode ruleNode : ruleNodeList) {
List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
for (EntityRelation relation : relations) {
if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
if (ruleNodeCtx == null) {
throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
}
}
nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
.add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
}
}
firstId = ruleChain.getFirstRuleNodeId();
firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId());
state = ComponentLifecycleState.ACTIVE;
}
void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
checkActive();
TbMsg tbMsg = envelope.getTbMsg();
//TODO: push to queue and act on ack in async way
pushMsgToNode(firstNode, tbMsg);
}
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
checkActive();
RuleNodeId originator = envelope.getOriginator();
String targetRelationType = envelope.getRelationType();
List<RuleNodeRelation> relations = nodeRoutes.get(originator);
if (relations == null) {
return;
}
boolean copy = relations.size() > 1;
for (RuleNodeRelation relation : relations) {
TbMsg msg = envelope.getMsg();
if (copy) {
msg = msg.copy();
}
if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) {
switch (relation.getOut().getEntityType()) {
case RULE_NODE:
RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId());
RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId);
pushMsgToNode(targetRuleNode, msg);
break;
case RULE_CHAIN:
// TODO: implement
break;
}
}
}
}
private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg) {
if (nodeCtx != null) {
nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg), self);
}
}
}

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

@ -0,0 +1,61 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import akka.actor.ActorRef;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.shared.plugin.PluginManager;
import org.thingsboard.server.actors.shared.rulechain.RuleChainManager;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.dao.rule.RuleChainService;
/**
* Created by ashvayka on 15.03.18.
*/
public abstract class RuleChainManagerActor extends ContextAwareActor {
protected final RuleChainManager ruleChainManager;
protected final PluginManager pluginManager;
protected final RuleChainService ruleChainService;
public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) {
super(systemContext);
this.ruleChainManager = ruleChainManager;
this.pluginManager = pluginManager;
this.ruleChainService = systemContext.getRuleChainService();
}
protected void initRuleChains() {
pluginManager.init(this.context());
ruleChainManager.init(this.context());
}
protected ActorRef getEntityActorRef(EntityId entityId) {
ActorRef target = null;
switch (entityId.getEntityType()) {
case PLUGIN:
target = pluginManager.getOrCreateActor(this.context(), (PluginId) entityId);
break;
case RULE_CHAIN:
target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId);
break;
}
return target;
}
}

33
common/message/src/main/java/org/thingsboard/server/common/msg/RuleMsg.java → application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleNodeMsg.java

@ -13,26 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg;
package org.thingsboard.server.actors.ruleChain;
import org.thingsboard.server.common.data.rule.Scope;
import org.thingsboard.server.common.data.rule.RuleType;
import org.thingsboard.server.common.msg.aware.RuleAwareMsg;
import lombok.Data;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Message that is used to deliver some data to the rule instance.
* For example: aggregated statistics or command decoded from http request.
*
* @author ashvayka
*
* @param <V> - payload
* Created by ashvayka on 19.03.18.
*/
public interface RuleMsg<V> extends RuleAwareMsg {
@Data
final class RuleChainToRuleNodeMsg implements TbActorMsg {
private final TbContext ctx;
private final TbMsg msg;
Scope getRuleLevel();
RuleType getRuleType();
V getPayload();
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_MSG;
}
}

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

@ -0,0 +1,109 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import org.thingsboard.server.actors.ActorSystemContext;
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.msg.TbActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> {
private final RuleChainId ruleChainId;
private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
super(systemContext, tenantId, ruleNodeId);
this.ruleChainId = ruleChainId;
setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext,
logger, context().parent(), context().self()));
}
@Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case RULE_CHAIN_TO_RULE_MSG:
onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg);
break;
case RULE_TO_SELF_ERROR_MSG:
onRuleNodeToSelfErrorMsg((RuleNodeToSelfErrorMsg) msg);
break;
case RULE_TO_SELF_MSG:
onRuleNodeToSelfMsg((RuleNodeToSelfMsg) msg);
break;
default:
return false;
}
return true;
}
private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
processor.onRuleToSelfMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRuleMsg", e);
}
}
private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) {
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg());
try {
processor.onRuleChainToRuleNodeMsg(msg);
increaseMessagesProcessedCount();
} catch (Exception e) {
logAndPersist("onRuleMsg", e);
}
}
private void onRuleNodeToSelfErrorMsg(RuleNodeToSelfErrorMsg msg) {
logAndPersist("onRuleMsg", ActorSystemContext.toException(msg.getError()));
}
public static class ActorCreator extends ContextBasedCreator<RuleNodeActor> {
private static final long serialVersionUID = 1L;
private final TenantId tenantId;
private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
super(context);
this.tenantId = tenantId;
this.ruleChainId = ruleChainId;
this.ruleNodeId = ruleNodeId;
}
@Override
public RuleNodeActor create() throws Exception {
return new RuleNodeActor(context, tenantId, ruleChainId, ruleNodeId);
}
}
@Override
protected long getErrorPersistFrequency() {
return systemContext.getRuleNodeErrorPersistFrequency();
}
}

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

@ -0,0 +1,116 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
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.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
import java.util.concurrent.ExecutionException;
/**
* @author Andrew Shvayka
*/
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> {
private final ActorRef parent;
private final ActorRef self;
private final RuleChainService service;
private RuleNode ruleNode;
private TbNode tbNode;
private TbContext defaultCtx;
RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, LoggingAdapter logger, ActorRef parent, ActorRef self) {
super(systemContext, logger, tenantId, ruleNodeId);
this.parent = parent;
this.self = self;
this.service = systemContext.getRuleChainService();
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
}
@Override
public void start(ActorContext context) throws Exception {
tbNode = initComponent(ruleNode);
state = ComponentLifecycleState.ACTIVE;
}
@Override
public void onUpdate(ActorContext context) throws Exception {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(entityId);
boolean restartRequired = !(ruleNode.getType().equals(newRuleNode.getType())
&& ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode;
this.defaultCtx.updateSelf(newRuleNode);
if (restartRequired) {
if (tbNode != null) {
tbNode.destroy();
}
start(context);
}
}
@Override
public void stop(ActorContext context) throws Exception {
if (tbNode != null) {
tbNode.destroy();
}
context.stop(self);
}
@Override
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception {
}
public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
checkActive();
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg());
}
tbNode.onMsg(defaultCtx, msg.getMsg());
}
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
checkActive();
if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg());
}
tbNode.onMsg(msg.getCtx(), msg.getMsg());
}
private TbNode initComponent(RuleNode ruleNode) throws Exception {
Class<?> componentClazz = Class.forName(ruleNode.getType());
TbNode tbNode = (TbNode) (componentClazz.newInstance());
tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration()));
return tbNode;
}
}

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

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import akka.actor.ActorRef;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleNode;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
@AllArgsConstructor
final class RuleNodeCtx {
private final TenantId tenantId;
private final ActorRef chainActor;
private final ActorRef selfActor;
private RuleNode self;
}

21
application/src/main/java/org/thingsboard/server/actors/rule/RuleProcessingMsg.java → application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeRelation.java

@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.rule;
package org.thingsboard.server.actors.ruleChain;
public class RuleProcessingMsg {
import lombok.Data;
import org.thingsboard.server.common.data.id.EntityId;
private final ChainProcessingContext ctx;
/**
* Created by ashvayka on 19.03.18.
*/
public RuleProcessingMsg(ChainProcessingContext ctx) {
super();
this.ctx = ctx;
}
@Data
final class RuleNodeRelation {
public ChainProcessingContext getCtx() {
return ctx;
}
private final EntityId in;
private final EntityId out;
private final String type;
}

39
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
private final RuleNodeId originator;
private final String relationType;
private final TbMsg msg;
@Override
public MsgType getMsgType() {
return MsgType.RULE_TO_RULE_CHAIN_TELL_NEXT_MSG;
}
}

37
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfErrorMsg.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleNodeToSelfErrorMsg implements TbActorMsg {
private final TbMsg msg;
private final Throwable error;
@Override
public MsgType getMsgType() {
return MsgType.RULE_TO_SELF_ERROR_MSG;
}
}

37
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToSelfMsg.java

@ -0,0 +1,37 @@
/**
* Copyright © 2016-2018 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.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleNodeToSelfMsg implements TbActorMsg {
private final TbMsg msg;
@Override
public MsgType getMsgType() {
return MsgType.RULE_TO_SELF_MSG;
}
}

11
application/src/main/java/org/thingsboard/server/actors/service/ActorService.java

@ -15,20 +15,19 @@
*/
package org.thingsboard.server.actors.service;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.service.cluster.discovery.DiscoveryServiceListener;
import org.thingsboard.server.service.cluster.rpc.RpcMsgListener;
public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor, RestMsgProcessor, RpcMsgListener, DiscoveryServiceListener {
void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state);
void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state);
void onMsg(ServiceToRuleEngineMsg msg);
void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);

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

@ -54,7 +54,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
@Override
public void preStart() {
try {
processor.start();
processor.start(context());
logLifecycleEvent(ComponentLifecycleEvent.STARTED);
if (systemContext.isStatisticsEnabled()) {
scheduleStatsPersistTick();
@ -78,7 +78,7 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
@Override
public void postStop() {
try {
processor.stop();
processor.stop(context());
logLifecycleEvent(ComponentLifecycleEvent.STOPPED);
} catch (Exception e) {
logger.warning("[{}][{}] Failed to stop {} processor: {}", tenantId, id, id.getEntityType(), e.getMessage());
@ -141,7 +141,6 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
messagesProcessed++;
}
protected void logAndPersist(String method, Exception e) {
logAndPersist(method, e, false);
}

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

@ -16,9 +16,13 @@
package org.thingsboard.server.actors.service;
import akka.actor.UntypedActor;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.msg.TbActorMsg;
public abstract class ContextAwareActor extends UntypedActor {
protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
public static final int ENTITY_PACK_LIMIT = 1024;
@ -28,4 +32,20 @@ public abstract class ContextAwareActor extends UntypedActor {
super();
this.systemContext = systemContext;
}
@Override
public void onReceive(Object msg) throws Exception {
if (logger.isDebugEnabled()) {
logger.debug("Processing msg: {}", msg);
}
if (msg instanceof TbActorMsg) {
if(!process((TbActorMsg) msg)){
logger.warning("Unknown message: {}!", msg);
}
} else {
logger.warning("Unknown message: {}!", msg);
}
}
protected abstract boolean process(TbActorMsg msg);
}

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

@ -30,16 +30,14 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
import org.thingsboard.server.actors.session.SessionManagerActor;
import org.thingsboard.server.actors.stats.StatsActor;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -128,6 +126,11 @@ public class DefaultActorService implements ActorService {
}
}
@Override
public void onMsg(ServiceToRuleEngineMsg msg) {
appActor.tell(msg, ActorRef.noSender());
}
@Override
public void process(SessionAwareMsg msg) {
log.debug("Processing session aware msg: {}", msg);
@ -212,15 +215,9 @@ public class DefaultActorService implements ActorService {
}
@Override
public void onPluginStateChange(TenantId tenantId, PluginId pluginId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing onPluginStateChange event: {}", pluginId, state);
broadcast(ComponentLifecycleMsg.forPlugin(tenantId, pluginId, state));
}
@Override
public void onRuleStateChange(TenantId tenantId, RuleId ruleId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing onRuleStateChange event: {}", ruleId, state);
broadcast(ComponentLifecycleMsg.forRule(tenantId, ruleId, state));
public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) {
log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state);
broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
}
@Override

7
application/src/main/java/org/thingsboard/server/actors/session/SessionActor.java

@ -23,6 +23,7 @@ import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
@ -60,6 +61,12 @@ public class SessionActor extends ContextAwareActor {
});
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Processing: {}.", sessionId, msg);

7
application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java

@ -26,6 +26,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
import org.thingsboard.server.common.data.id.SessionId;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.SessionAwareMsg;
import akka.event.Logging;
@ -48,6 +49,12 @@ public class SessionManagerActor extends ContextAwareActor {
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE);
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
if (msg instanceof SessionCtrlMsg) {

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

@ -102,9 +102,6 @@ public abstract class AbstractContextAwareMsgProcessor {
case FILTER:
configurationClazz = ((Filter) componentClazz.getAnnotation(Filter.class)).configuration();
break;
case PROCESSOR:
configurationClazz = ((Processor) componentClazz.getAnnotation(Processor.class)).configuration();
break;
case ACTION:
configurationClazz = ((Action) componentClazz.getAnnotation(Action.class)).configuration();
break;

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

@ -20,12 +20,14 @@ import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor {
protected final TenantId tenantId;
protected final T entityId;
protected ComponentLifecycleState state;
protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
super(systemContext, logger);
@ -33,23 +35,44 @@ public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgPr
this.entityId = id;
}
public abstract void start() throws Exception;
public abstract void start(ActorContext context) throws Exception;
public abstract void stop() throws Exception;
public abstract void stop(ActorContext context) throws Exception;
public abstract void onCreated(ActorContext context) throws Exception;
public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
public abstract void onUpdate(ActorContext context) throws Exception;
public void onCreated(ActorContext context) throws Exception {
start(context);
}
public abstract void onActivate(ActorContext context) throws Exception;
public void onUpdate(ActorContext context) throws Exception {
restart(context);
}
public abstract void onSuspend(ActorContext context) throws Exception;
public void onActivate(ActorContext context) throws Exception {
restart(context);
}
public abstract void onStop(ActorContext context) throws Exception;
public void onSuspend(ActorContext context) throws Exception {
stop(context);
}
public abstract void onClusterEventMsg(ClusterEventMsg msg) throws Exception;
public void onStop(ActorContext context) throws Exception {
stop(context);
}
private void restart(ActorContext context) throws Exception {
stop(context);
start(context);
}
public void scheduleStatsPersistTick(ActorContext context, long statsPersistFrequency) {
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
}
protected void checkActive() {
if (state != ComponentLifecycleState.ACTIVE) {
throw new IllegalStateException("Rule chain is not active!");
}
}
}

86
application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2018 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.shared;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.actor.UntypedActor;
import akka.japi.Creator;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import java.util.HashMap;
import java.util.Map;
/**
* Created by ashvayka on 15.03.18.
*/
@Slf4j
public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> {
protected final ActorSystemContext systemContext;
protected final Map<T, ActorRef> actors;
public EntityActorsManager(ActorSystemContext systemContext) {
this.systemContext = systemContext;
this.actors = new HashMap<>();
}
protected abstract TenantId getTenantId();
protected abstract String getDispatcherName();
protected abstract Creator<A> creator(T entityId);
protected abstract PageDataIterable.FetchFunction<M> getFetchEntitiesFunction();
public void init(ActorContext context) {
for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) {
T entityId = (T) entity.getId();
log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId());
//TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
ActorRef actorRef = getOrCreateActor(context, entityId);
visit(entity, actorRef);
log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId());
}
}
protected void visit(M entity, ActorRef actorRef) {}
public ActorRef getOrCreateActor(ActorContext context, T entityId) {
return actors.computeIfAbsent(entityId, eId ->
context.actorOf(Props.create(creator(eId))
.withDispatcher(getDispatcherName()), eId.toString()));
}
public void broadcast(Object msg) {
actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
public void remove(T id) {
actors.remove(id);
}
}

49
application/src/main/java/org/thingsboard/server/actors/shared/plugin/PluginManager.java

@ -15,63 +15,28 @@
*/
package org.thingsboard.server.actors.shared.plugin;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.japi.Creator;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.plugin.PluginActor;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.shared.EntityActorsManager;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.dao.plugin.PluginService;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public abstract class PluginManager {
public abstract class PluginManager extends EntityActorsManager<PluginId, PluginActor, PluginMetaData> {
protected final ActorSystemContext systemContext;
protected final PluginService pluginService;
protected final Map<PluginId, ActorRef> pluginActors;
public PluginManager(ActorSystemContext systemContext) {
this.systemContext = systemContext;
super(systemContext);
this.pluginService = systemContext.getPluginService();
this.pluginActors = new HashMap<>();
}
public void init(ActorContext context) {
PageDataIterable<PluginMetaData> pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(),
ContextAwareActor.ENTITY_PACK_LIMIT);
for (PluginMetaData plugin : pluginIterator) {
log.debug("[{}] Creating plugin actor", plugin.getId());
getOrCreatePluginActor(context, plugin.getId());
log.debug("Plugin actor created.");
}
@Override
public Creator<PluginActor> creator(PluginId entityId){
return new PluginActor.ActorCreator(systemContext, getTenantId(), entityId);
}
abstract FetchFunction<PluginMetaData> getFetchPluginsFunction();
abstract TenantId getTenantId();
abstract String getDispatcherName();
public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) {
return pluginActors.computeIfAbsent(pluginId, pId ->
context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pId))
.withDispatcher(getDispatcherName()), pId.toString()));
}
public void broadcast(Object msg) {
pluginActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
public void remove(PluginId id) {
pluginActors.remove(id);
}
}

4
application/src/main/java/org/thingsboard/server/actors/shared/plugin/SystemPluginManager.java

@ -29,12 +29,12 @@ public class SystemPluginManager extends PluginManager {
}
@Override
FetchFunction<PluginMetaData> getFetchPluginsFunction() {
protected FetchFunction<PluginMetaData> getFetchEntitiesFunction() {
return pluginService::findSystemPlugins;
}
@Override
TenantId getTenantId() {
protected TenantId getTenantId() {
return BasePluginService.SYSTEM_TENANT;
}

5
application/src/main/java/org/thingsboard/server/actors/shared/plugin/TenantPluginManager.java

@ -19,6 +19,7 @@ import akka.actor.ActorContext;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
@ -39,12 +40,12 @@ public class TenantPluginManager extends PluginManager {
}
@Override
FetchFunction<PluginMetaData> getFetchPluginsFunction() {
protected FetchFunction<PluginMetaData> getFetchEntitiesFunction() {
return link -> pluginService.findTenantPlugins(tenantId, link);
}
@Override
TenantId getTenantId() {
protected TenantId getTenantId() {
return tenantId;
}

135
application/src/main/java/org/thingsboard/server/actors/shared/rule/RuleManager.java

@ -1,135 +0,0 @@
/**
* Copyright © 2016-2018 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.shared.rule;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.actor.Props;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.rule.RuleActor;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.actors.rule.RuleActorMetaData;
import org.thingsboard.server.actors.rule.SimpleRuleActorChain;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.rule.RuleService;
import java.util.*;
@Slf4j
public abstract class RuleManager {
protected final ActorSystemContext systemContext;
protected final RuleService ruleService;
protected final Map<RuleId, ActorRef> ruleActors;
protected final TenantId tenantId;
private Map<RuleMetaData, RuleActorMetaData> ruleMap;
private RuleActorChain ruleChain;
public RuleManager(ActorSystemContext systemContext, TenantId tenantId) {
this.systemContext = systemContext;
this.ruleService = systemContext.getRuleService();
this.ruleActors = new HashMap<>();
this.tenantId = tenantId;
}
public void init(ActorContext context) {
doInit(context);
}
private void doInit(ActorContext context) {
PageDataIterable<RuleMetaData> ruleIterator = new PageDataIterable<>(getFetchRulesFunction(),
ContextAwareActor.ENTITY_PACK_LIMIT);
ruleMap = new HashMap<>();
for (RuleMetaData rule : ruleIterator) {
log.debug("[{}] Creating rule actor {}", rule.getId(), rule);
ActorRef ref = getOrCreateRuleActor(context, rule.getId());
ruleMap.put(rule, RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref));
log.debug("[{}] Rule actor created.", rule.getId());
}
refreshRuleChain();
}
public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) {
if (ruleMap == null) {
doInit(context);
}
RuleMetaData rule;
if (event != ComponentLifecycleEvent.DELETED) {
rule = systemContext.getRuleService().findRuleById(ruleId);
} else {
rule = ruleMap.keySet().stream()
.filter(r -> r.getId().equals(ruleId))
.peek(r -> r.setState(ComponentLifecycleState.SUSPENDED))
.findFirst()
.orElse(null);
if (rule != null) {
ruleMap.remove(rule);
ruleActors.remove(ruleId);
}
}
if (rule != null) {
RuleActorMetaData actorMd = ruleMap.get(rule);
if (actorMd == null) {
ActorRef ref = getOrCreateRuleActor(context, rule.getId());
actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref);
ruleMap.put(rule, actorMd);
}
refreshRuleChain();
return Optional.of(actorMd.getActorRef());
} else {
log.warn("[{}] Can't process unknown rule!", ruleId);
return Optional.empty();
}
}
abstract FetchFunction<RuleMetaData> getFetchRulesFunction();
abstract String getDispatcherName();
public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) {
return ruleActors.computeIfAbsent(ruleId, rId ->
context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, rId))
.withDispatcher(getDispatcherName()), rId.toString()));
}
public RuleActorChain getRuleChain(ActorContext context) {
if (ruleChain == null) {
doInit(context);
}
return ruleChain;
}
private void refreshRuleChain() {
Set<RuleActorMetaData> activeRuleSet = new HashSet<>();
for (Map.Entry<RuleMetaData, RuleActorMetaData> rule : ruleMap.entrySet()) {
if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) {
activeRuleSet.add(rule.getValue());
}
}
ruleChain = new SimpleRuleActorChain(activeRuleSet);
}
}

59
application/src/main/java/org/thingsboard/server/actors/shared/rulechain/RuleChainManager.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2018 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.shared.rulechain;
import akka.actor.ActorRef;
import akka.japi.Creator;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.ruleChain.RuleChainActor;
import org.thingsboard.server.actors.shared.EntityActorsManager;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService;
/**
* Created by ashvayka on 15.03.18.
*/
@Slf4j
public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, RuleChainActor, RuleChain> {
protected final RuleChainService service;
@Getter
protected RuleChain rootChain;
@Getter
protected ActorRef rootChainActor;
public RuleChainManager(ActorSystemContext systemContext) {
super(systemContext);
this.service = systemContext.getRuleChainService();
}
@Override
public Creator<RuleChainActor> creator(RuleChainId entityId) {
return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId);
}
@Override
protected void visit(RuleChain entity, ActorRef actorRef) {
if (entity.isRoot()) {
rootChain = entity;
rootChainActor = actorRef;
}
}
}

25
application/src/main/java/org/thingsboard/server/actors/shared/rule/SystemRuleManager.java → application/src/main/java/org/thingsboard/server/actors/shared/rulechain/SystemRuleChainManager.java

@ -13,28 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.shared.rule;
package org.thingsboard.server.actors.shared.rulechain;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.plugin.PluginManager;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.plugin.BasePluginService;
public class SystemRuleManager extends RuleManager {
public class SystemRuleChainManager extends RuleChainManager {
public SystemRuleManager(ActorSystemContext systemContext) {
super(systemContext, new TenantId(ModelConstants.NULL_UUID));
public SystemRuleChainManager(ActorSystemContext systemContext) {
super(systemContext);
}
@Override
FetchFunction<RuleMetaData> getFetchRulesFunction() {
return ruleService::findSystemRules;
protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
return service::findSystemRuleChains;
}
@Override
String getDispatcherName() {
protected TenantId getTenantId() {
return BasePluginService.SYSTEM_TENANT;
}
@Override
protected String getDispatcherName() {
return DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME;
}
}

25
application/src/main/java/org/thingsboard/server/actors/shared/rule/TenantRuleManager.java → application/src/main/java/org/thingsboard/server/actors/shared/rulechain/TenantRuleChainManager.java

@ -13,19 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.shared.rule;
package org.thingsboard.server.actors.shared.rulechain;
import akka.actor.ActorContext;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
public class TenantRuleManager extends RuleManager {
public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext, tenantId);
public class TenantRuleChainManager extends RuleChainManager {
private final TenantId tenantId;
public TenantRuleChainManager(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
this.tenantId = tenantId;
}
@Override
@ -36,13 +39,17 @@ public class TenantRuleManager extends RuleManager {
}
@Override
FetchFunction<RuleMetaData> getFetchRulesFunction() {
return link -> ruleService.findTenantRules(tenantId, link);
protected TenantId getTenantId() {
return tenantId;
}
@Override
String getDispatcherName() {
protected String getDispatcherName() {
return DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
}
@Override
protected FetchFunction<RuleChain> getFetchEntitiesFunction() {
return link -> service.findTenantRuleChains(tenantId, link);
}
}

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

@ -24,6 +24,7 @@ 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.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
public class StatsActor extends ContextAwareActor {
@ -35,6 +36,12 @@ public class StatsActor extends ContextAwareActor {
super(context);
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg\
return false;
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("Received message: {}", msg);

40
application/src/main/java/org/thingsboard/server/actors/tenant/RuleChainDeviceMsg.java

@ -1,40 +0,0 @@
/**
* Copyright © 2016-2018 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.tenant;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
public class RuleChainDeviceMsg {
private final ToDeviceActorMsg toDeviceActorMsg;
private final RuleActorChain ruleChain;
public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) {
super();
this.toDeviceActorMsg = toDeviceActorMsg;
this.ruleChain = ruleChain;
}
public ToDeviceActorMsg getToDeviceActorMsg() {
return toDeviceActorMsg;
}
public RuleActorChain getRuleChain() {
return ruleChain;
}
}

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

@ -15,52 +15,38 @@
*/
package org.thingsboard.server.actors.tenant;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActor;
import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
import org.thingsboard.server.actors.rule.ComplexRuleActorChain;
import org.thingsboard.server.actors.rule.RuleActorChain;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.actors.shared.plugin.PluginManager;
import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
import org.thingsboard.server.actors.shared.rule.RuleManager;
import org.thingsboard.server.actors.shared.rule.TenantRuleManager;
import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.PluginId;
import org.thingsboard.server.common.data.id.RuleId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
import org.thingsboard.server.extensions.api.rules.ToRuleActorMsg;
public class TenantActor extends ContextAwareActor {
import java.util.HashMap;
import java.util.Map;
private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
public class TenantActor extends RuleChainManagerActor {
private final TenantId tenantId;
private final RuleManager ruleManager;
private final PluginManager pluginManager;
private final Map<DeviceId, ActorRef> deviceActors;
private TenantActor(ActorSystemContext systemContext, TenantId tenantId) {
super(systemContext);
super(systemContext, new TenantRuleChainManager(systemContext, tenantId), new TenantPluginManager(systemContext, tenantId));
this.tenantId = tenantId;
this.ruleManager = new TenantRuleManager(systemContext, tenantId);
this.pluginManager = new TenantPluginManager(systemContext, tenantId);
this.deviceActors = new HashMap<>();
}
@ -68,8 +54,7 @@ public class TenantActor extends ContextAwareActor {
public void preStart() {
logger.info("[{}] Starting tenant actor.", tenantId);
try {
ruleManager.init(this.context());
pluginManager.init(this.context());
initRuleChains();
logger.info("[{}] Tenant actor started.", tenantId);
} catch (Exception e) {
logger.error(e, "[{}] Unknown failure", tenantId);
@ -77,29 +62,45 @@ public class TenantActor extends ContextAwareActor {
}
@Override
public void onReceive(Object msg) throws Exception {
logger.debug("[{}] Received message: {}", tenantId, msg);
if (msg instanceof RuleChainDeviceMsg) {
process((RuleChainDeviceMsg) msg);
} else if (msg instanceof ToDeviceActorMsg) {
onToDeviceActorMsg((ToDeviceActorMsg) msg);
} else if (msg instanceof ToPluginActorMsg) {
onToPluginMsg((ToPluginActorMsg) msg);
} else if (msg instanceof ToRuleActorMsg) {
onToRuleMsg((ToRuleActorMsg) msg);
} else if (msg instanceof ToDeviceActorNotificationMsg) {
onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
} else if (msg instanceof ClusterEventMsg) {
broadcast(msg);
} else if (msg instanceof ComponentLifecycleMsg) {
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
} else if (msg instanceof PluginTerminationMsg) {
onPluginTerminated((PluginTerminationMsg) msg);
} else {
logger.warning("[{}] Unknown message: {}!", tenantId, msg);
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
case COMPONENT_LIFE_CYCLE_MSG:
onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
break;
case SERVICE_TO_RULE_ENGINE_MSG:
onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
break;
default:
return false;
}
return true;
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
ruleChainManager.getRootChainActor().tell(msg, self());
}
// @Override
// public void onReceive(Object msg) throws Exception {
// logger.debug("[{}] Received message: {}", tenantId, msg);
// if (msg instanceof ToDeviceActorMsg) {
// onToDeviceActorMsg((ToDeviceActorMsg) msg);
// } else if (msg instanceof ToPluginActorMsg) {
// onToPluginMsg((ToPluginActorMsg) msg);
// } else if (msg instanceof ToDeviceActorNotificationMsg) {
// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
// } else if (msg instanceof ClusterEventMsg) {
// broadcast(msg);
// } else if (msg instanceof ComponentLifecycleMsg) {
// onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
// } else if (msg instanceof PluginTerminationMsg) {
// onPluginTerminated((PluginTerminationMsg) msg);
// } else {
// logger.warning("[{}] Unknown message: {}!", tenantId, msg);
// }
// }
private void broadcast(Object msg) {
pluginManager.broadcast(msg);
deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
@ -113,14 +114,9 @@ public class TenantActor extends ContextAwareActor {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
private void onToRuleMsg(ToRuleActorMsg msg) {
ActorRef target = ruleManager.getOrCreateRuleActor(this.context(), msg.getRuleId());
target.tell(msg, ActorRef.noSender());
}
private void onToPluginMsg(ToPluginActorMsg msg) {
if (msg.getPluginTenantId().equals(tenantId)) {
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), msg.getPluginId());
ActorRef pluginActor = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
pluginActor.tell(msg, ActorRef.noSender());
} else {
context().parent().tell(msg, ActorRef.noSender());
@ -128,23 +124,11 @@ public class TenantActor extends ContextAwareActor {
}
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
Optional<PluginId> pluginId = msg.getPluginId();
Optional<RuleId> ruleId = msg.getRuleId();
if (pluginId.isPresent()) {
ActorRef pluginActor = pluginManager.getOrCreatePluginActor(this.context(), pluginId.get());
pluginActor.tell(msg, ActorRef.noSender());
} else if (ruleId.isPresent()) {
ActorRef target;
Optional<ActorRef> ref = ruleManager.update(this.context(), ruleId.get(), msg.getEvent());
if (ref.isPresent()) {
target = ref.get();
} else {
logger.debug("Failed to find actor for rule: [{}]", ruleId);
return;
}
ActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) {
target.tell(msg, ActorRef.noSender());
} else {
logger.debug("[{}] Invalid component lifecycle msg.", tenantId);
logger.debug("Invalid component lifecycle msg: {}", msg);
}
}
@ -152,13 +136,6 @@ public class TenantActor extends ContextAwareActor {
pluginManager.remove(msg.getId());
}
private void process(RuleChainDeviceMsg msg) {
ToDeviceActorMsg toDeviceActorMsg = msg.getToDeviceActorMsg();
ActorRef deviceActor = getOrCreateDeviceActor(toDeviceActorMsg.getDeviceId());
RuleActorChain tenantChain = ruleManager.getRuleChain(this.context());
RuleActorChain chain = new ComplexRuleActorChain(msg.getRuleChain(), tenantChain);
deviceActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, chain), context().self());
}
private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId))

2
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -20,7 +20,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@ -37,7 +36,6 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.cors.CorsUtils;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.thingsboard.server.dao.audit.AuditLogLevelFilter;

12
application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java

@ -17,9 +17,9 @@ package org.thingsboard.server.config;
import java.util.Map;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.controller.plugin.PluginWebSocketHandler;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.controller.plugin.TbWebSocketHandler;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -54,7 +54,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(pluginWsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
registry.addHandler(wsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*")
.addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() {
@Override
@ -82,8 +82,8 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
}
@Bean
public WebSocketHandler pluginWsHandler() {
return new PluginWebSocketHandler();
public WebSocketHandler wsHandler() {
return new TbWebSocketHandler();
}
protected SecurityUser getCurrentUser() throws ThingsboardException {

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

@ -20,8 +20,8 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.service.update.UpdateService;
import org.thingsboard.server.service.update.model.UpdateMessage;

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

@ -23,8 +23,8 @@ import org.thingsboard.server.common.data.alarm.*;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@RestController
@RequestMapping("/api")

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

@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;

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

@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.UUID;

6
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -28,9 +28,9 @@ import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;

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

@ -15,12 +15,9 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ -30,7 +27,6 @@ import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.page.TextPageLink;
@ -49,6 +45,7 @@ import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -57,12 +54,13 @@ import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleService;
import org.thingsboard.server.dao.tenant.TenantService;
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.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -71,6 +69,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -84,6 +83,9 @@ public abstract class BaseController {
@Autowired
private ThingsboardErrorResponseHandler errorResponseHandler;
@Autowired
protected TenantService tenantService;
@Autowired
protected CustomerService customerService;
@ -132,6 +134,9 @@ public abstract class BaseController {
@Autowired
protected AuditLogService auditLogService;
@Autowired
protected DeviceOfflineService offlineService;
@ExceptionHandler(ThingsboardException.class)
public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
errorResponseHandler.handle(ex, response);
@ -480,6 +485,15 @@ public abstract class BaseController {
}
}
List<ComponentDescriptor> checkComponentDescriptorsByTypes(Set<ComponentType> types) throws ThingsboardException {
try {
log.debug("[{}] Lookup component descriptors", types);
return componentDescriptorService.getComponents(types);
} catch (Exception e) {
throw handleException(e, false);
}
}
List<ComponentDescriptor> checkPluginActionsByPluginClazz(String pluginClazz) throws ThingsboardException {
try {
checkComponentDescriptorByClazz(pluginClazz);
@ -550,6 +564,8 @@ public abstract class BaseController {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
} else if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
ruleChain.setConfiguration(null);
}
}
return ruleChain;
@ -590,5 +606,8 @@ public abstract class BaseController {
auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
}
protected static Exception toException(Throwable error) {
return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
}
}

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

@ -19,9 +19,11 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@RestController
@RequestMapping("/api")
@ -51,6 +53,22 @@ public class ComponentDescriptorController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
@ResponseBody
public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes) throws ThingsboardException {
checkArrayParameter("componentTypes", strComponentTypes);
try {
Set<ComponentType> componentTypes = new HashSet<>();
for (String strComponentType : strComponentTypes) {
componentTypes.add(ComponentType.valueOf(strComponentType));
}
return checkComponentDescriptorsByTypes(componentTypes);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components/actions/{pluginClazz:.+}", method = RequestMethod.GET)
@ResponseBody

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

@ -28,7 +28,7 @@ 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.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@RestController
@RequestMapping("/api")

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

@ -27,9 +27,7 @@ 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.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.HashSet;
import java.util.Set;

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

@ -23,9 +23,11 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -35,8 +37,6 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList;
@ -70,7 +70,7 @@ public class DeviceController extends BaseController {
device.setTenantId(getCurrentUser().getTenantId());
if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
if (device.getId() == null || device.getId().isNullUid() ||
device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
throw new ThingsboardException("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED);
} else {
@ -368,4 +368,32 @@ public class DeviceController extends BaseController {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/offline", method = RequestMethod.GET)
@ResponseBody
public List<Device> getOfflineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
@RequestParam("threshold") long threshold) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
ListenableFuture<List<Device>> offlineDevices = offlineService.findOfflineDevices(tenantId.getId(), contactType, threshold);
return checkNotNull(offlineDevices.get());
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/online", method = RequestMethod.GET)
@ResponseBody
public List<Device> getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
@RequestParam("threshold") long threshold) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
ListenableFuture<List<Device>> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold);
return checkNotNull(offlineDevices.get());
} catch (Exception e) {
throw handleException(e);
}
}
}

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

@ -24,8 +24,8 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.List;

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

@ -24,8 +24,8 @@ import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@RestController
@RequestMapping("/api")

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

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2018 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.controller;
import com.google.common.util.concurrent.FutureCallback;
import org.springframework.http.ResponseEntity;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.service.security.ValidationCallback;
/**
* Created by ashvayka on 21.02.17.
*/
public class HttpValidationCallback extends ValidationCallback<DeferredResult<ResponseEntity>> {
public HttpValidationCallback(DeferredResult<ResponseEntity> response, FutureCallback<DeferredResult<ResponseEntity>> action) {
super(response, action);
}
}

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

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.List;
@ -71,7 +71,7 @@ public class PluginController extends BaseController {
boolean created = source.getId() == null;
source.setTenantId(getCurrentUser().getTenantId());
PluginMetaData plugin = checkNotNull(pluginService.savePlugin(source));
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(),
actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(plugin.getId(), plugin,
@ -97,7 +97,7 @@ public class PluginController extends BaseController {
PluginId pluginId = new PluginId(toUUID(strPluginId));
PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
pluginService.activatePluginById(pluginId);
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.ACTIVATED);
logEntityAction(plugin.getId(), plugin,
null,
@ -123,7 +123,7 @@ public class PluginController extends BaseController {
PluginId pluginId = new PluginId(toUUID(strPluginId));
PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
pluginService.suspendPluginById(pluginId);
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.SUSPENDED);
logEntityAction(plugin.getId(), plugin,
null,
@ -221,7 +221,7 @@ public class PluginController extends BaseController {
PluginId pluginId = new PluginId(toUUID(strPluginId));
PluginMetaData plugin = checkPlugin(pluginService.findPluginById(pluginId));
pluginService.deletePluginById(pluginId);
actorService.onPluginStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
actorService.onEntityStateChange(plugin.getTenantId(), plugin.getId(), ComponentLifecycleEvent.DELETED);
logEntityAction(pluginId, plugin,
null,

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

@ -15,31 +15,46 @@
*/
package org.thingsboard.server.controller;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.PluginId;
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.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.script.NashornJsEngine;
import java.util.List;
import java.util.Map;
import java.util.Set;
@Slf4j
@RestController
@RequestMapping("/api")
public class RuleChainController extends BaseController {
public static final String RULE_CHAIN_ID = "ruleChainId";
private static final ObjectMapper objectMapper = new ObjectMapper();
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}", method = RequestMethod.GET)
@ResponseBody
@ -53,6 +68,21 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/metadata", method = RequestMethod.GET)
@ResponseBody
public RuleChainMetaData getRuleChainMetaData(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId);
return ruleChainService.loadRuleChainMetaData(ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain", method = RequestMethod.POST)
@ResponseBody
@ -62,6 +92,9 @@ public class RuleChainController extends BaseController {
ruleChain.setTenantId(getCurrentUser().getTenantId());
RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
actorService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(savedRuleChain.getId(), savedRuleChain,
null,
created ? ActionType.ADDED : ActionType.UPDATED, null);
@ -76,6 +109,30 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/metadata", method = RequestMethod.POST)
@ResponseBody
public RuleChainMetaData saveRuleChainMetaData(@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException {
try {
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId());
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(ruleChainMetaData));
actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
logEntityAction(ruleChain.getId(), ruleChain,
null,
ActionType.UPDATED, null, ruleChainMetaData);
return savedRuleChainMetaData;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
null, ActionType.UPDATED, e, ruleChainMetaData);
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/ruleChains", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody
@ -145,6 +202,8 @@ public class RuleChainController extends BaseController {
RuleChain ruleChain = checkRuleChain(ruleChainId);
ruleChainService.deleteRuleChainById(ruleChainId);
actorService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
logEntityAction(ruleChainId, ruleChain,
null,
ActionType.DELETED, null, strRuleChainId);
@ -158,4 +217,78 @@ public class RuleChainController extends BaseController {
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST)
@ResponseBody
public JsonNode testScript(@RequestBody JsonNode inputParams) throws ThingsboardException {
try {
String script = inputParams.get("script").asText();
String scriptType = inputParams.get("scriptType").asText();
String functionName = inputParams.get("functionName").asText();
JsonNode argNamesJson = inputParams.get("argNames");
String[] argNames = objectMapper.treeToValue(argNamesJson, String[].class);
String data = inputParams.get("msg").asText();
JsonNode metadataJson = inputParams.get("metadata");
Map<String, String> metadata = objectMapper.convertValue(metadataJson, new TypeReference<Map<String, String>>() {});
String msgType = inputParams.get("msgType").asText();
String output = "";
String errorText = "";
ScriptEngine engine = null;
try {
engine = new NashornJsEngine(script, functionName, argNames);
TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data);
switch (scriptType) {
case "update":
output = msgToOutput(engine.executeUpdate(inMsg));
break;
case "generate":
output = msgToOutput(engine.executeGenerate(inMsg));
break;
case "filter":
boolean result = engine.executeFilter(inMsg);
output = Boolean.toString(result);
break;
case "switch":
Set<String> states = engine.executeSwitch(inMsg);
output = objectMapper.writeValueAsString(states);
break;
case "json":
JsonNode json = engine.executeJson(inMsg);
output = objectMapper.writeValueAsString(json);
break;
case "string":
output = engine.executeToString(inMsg);
break;
default:
throw new IllegalArgumentException("Unsupported script type: " + scriptType);
}
} catch (Exception e) {
log.error("Error evaluating JS function", e);
errorText = e.getMessage();
} finally {
if (engine != null) {
engine.destroy();
}
}
ObjectNode result = objectMapper.createObjectNode();
result.put("output", output);
result.put("error", errorText);
return result;
} catch (Exception e) {
throw handleException(e);
}
}
private String msgToOutput(TbMsg msg) throws Exception {
ObjectNode msgData = objectMapper.createObjectNode();
if (!StringUtils.isEmpty(msg.getData())) {
msgData.set("msg", objectMapper.readTree(msg.getData()));
}
Map<String, String> metadata = msg.getMetaData().getData();
msgData.set("metadata", objectMapper.valueToTree(metadata));
msgData.put("msgType", msg.getType());
return objectMapper.writeValueAsString(msgData);
}
}

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

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.List;
@ -73,7 +73,7 @@ public class RuleController extends BaseController {
boolean created = source.getId() == null;
source.setTenantId(getCurrentUser().getTenantId());
RuleMetaData rule = checkNotNull(ruleService.saveRule(source));
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(),
actorService.onEntityStateChange(rule.getTenantId(), rule.getId(),
created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
logEntityAction(rule.getId(), rule,
@ -99,7 +99,7 @@ public class RuleController extends BaseController {
RuleId ruleId = new RuleId(toUUID(strRuleId));
RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
ruleService.activateRuleById(ruleId);
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.ACTIVATED);
logEntityAction(rule.getId(), rule,
null,
@ -125,7 +125,7 @@ public class RuleController extends BaseController {
RuleId ruleId = new RuleId(toUUID(strRuleId));
RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
ruleService.suspendRuleById(ruleId);
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.SUSPENDED);
logEntityAction(rule.getId(), rule,
null,
@ -219,7 +219,7 @@ public class RuleController extends BaseController {
RuleId ruleId = new RuleId(toUUID(strRuleId));
RuleMetaData rule = checkRule(ruleService.findRuleById(ruleId));
ruleService.deleteRuleById(ruleId);
actorService.onRuleStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
actorService.onEntityStateChange(rule.getTenantId(), rule.getId(), ComponentLifecycleEvent.DELETED);
logEntityAction(ruleId, rule,
null,

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

@ -0,0 +1,586 @@
/**
* Copyright © 2016-2018 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.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.extensions.api.exception.InvalidParametersException;
import org.thingsboard.server.extensions.api.exception.UncheckedApiException;
import org.thingsboard.server.extensions.api.plugins.PluginConstants;
import org.thingsboard.server.extensions.core.plugin.telemetry.AttributeData;
import org.thingsboard.server.extensions.core.plugin.telemetry.TsData;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
/**
* Created by ashvayka on 22.03.18.
*/
@RestController
@RequestMapping(PluginConstants.TELEMETRY_URL_PREFIX)
@Slf4j
public class TelemetryController extends BaseController {
@Autowired
private AttributesService attributesService;
@Autowired
private TimeseriesService tsService;
@Autowired
private TelemetrySubscriptionService tsSubService;
@Autowired
private AccessValidator accessValidator;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET)
@ResponseBody
public DeferredResult<ResponseEntity> getAttributeKeys(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr, this::getAttributeKeysCallback);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET)
@ResponseBody
public DeferredResult<ResponseEntity> getAttributeKeysByScope(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr
, @PathVariable("scope") String scope) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
(result, entityId) -> getAttributeKeysCallback(result, entityId, scope));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET)
@ResponseBody
public DeferredResult<ResponseEntity> getAttributes(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
(result, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET)
@ResponseBody
public DeferredResult<ResponseEntity> getAttributesByScope(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("scope") String scope,
@RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
(result, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET)
@ResponseBody
public DeferredResult<ResponseEntity> getTimeseriesKeys(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
(result, entityId) -> {
Futures.addCallback(tsService.findAllLatest(entityId), getTsKeysToResponseCallback(result));
});
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET)
@ResponseBody
public DeferredResult<ResponseEntity> getLatestTimeseries(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
(result, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
@ResponseBody
public DeferredResult<ResponseEntity> getTimeseries(
@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@RequestParam(name = "keys") String keys,
@RequestParam(name = "startTs") Long startTs,
@RequestParam(name = "endTs") Long endTs,
@RequestParam(name = "interval", defaultValue = "0") Long interval,
@RequestParam(name = "limit", defaultValue = "100") Integer limit,
@RequestParam(name = "agg", defaultValue = "NONE") String aggStr
) throws ThingsboardException {
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityType, entityIdStr,
(result, entityId) -> {
// If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
List<TsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(entityId, queries), getTsKvListCallback(result));
});
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> saveDeviceAttributes(@PathVariable("deviceId") String deviceIdStr, @PathVariable("scope") String scope,
@RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return saveAttributes(entityId, scope, request);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> saveEntityAttributesV1(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("scope") String scope,
@RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(entityId, scope, request);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/attributes/{scope}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> saveEntityAttributesV2(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("scope") String scope,
@RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(entityId, scope, request);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> saveEntityTelemetry(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("scope") String scope,
@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(entityId, requestBody, 0L);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", method = RequestMethod.POST)
@ResponseBody
public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("scope") String scope, @PathVariable("ttl") Long ttl,
@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(entityId, requestBody, ttl);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
@ResponseBody
public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("deviceId") String deviceIdStr,
@PathVariable("scope") String scope,
@RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return deleteAttributes(entityId, scope, keysStr);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE)
@ResponseBody
public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
@PathVariable("scope") String scope,
@RequestParam(name = "keys") String keysStr) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return deleteAttributes(entityId, scope, keysStr);
}
private DeferredResult<ResponseEntity> deleteAttributes(EntityId entityIdStr, String scope, String keysStr) throws ThingsboardException {
List<String> keys = toKeysList(keysStr);
if (keys.isEmpty()) {
return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST);
}
SecurityUser user = getCurrentUser();
if (DataConstants.SERVER_SCOPE.equals(scope) ||
DataConstants.SHARED_SCOPE.equals(scope) ||
DataConstants.CLIENT_SCOPE.equals(scope)) {
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdStr, (result, entityId) -> {
ListenableFuture<List<Void>> future = attributesService.removeAll(entityId, scope, keys);
Futures.addCallback(future, new FutureCallback<List<Void>>() {
@Override
public void onSuccess(@Nullable List<Void> tmp) {
logAttributesDeleted(user, entityId, scope, keys, null);
result.setResult(new ResponseEntity<>(HttpStatus.OK));
}
@Override
public void onFailure(Throwable t) {
logAttributesDeleted(user, entityId, scope, keys, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
}
}, executor);
});
} else {
return getImmediateDeferredResult("Invalid attribute scope: " + scope, HttpStatus.BAD_REQUEST);
}
}
private DeferredResult<ResponseEntity> saveAttributes(EntityId entityIdSrc, String scope, JsonNode json) throws ThingsboardException {
if (!DataConstants.SERVER_SCOPE.equals(scope) && !DataConstants.SHARED_SCOPE.equals(scope)) {
return getImmediateDeferredResult("Invalid scope: " + scope, HttpStatus.BAD_REQUEST);
}
if (json.isObject()) {
List<AttributeKvEntry> attributes = extractRequestAttributes(json);
if (attributes.isEmpty()) {
return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
}
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdSrc, (result, entityId) -> {
tsSubService.saveAndNotify(entityId, scope, attributes, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
logAttributesUpdated(user, entityId, scope, attributes, null);
result.setResult(new ResponseEntity(HttpStatus.OK));
}
@Override
public void onFailure(Throwable t) {
logAttributesUpdated(user, entityId, scope, attributes, t);
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
}
});
});
} else {
return getImmediateDeferredResult("Request is not a JSON object", HttpStatus.BAD_REQUEST);
}
}
private DeferredResult<ResponseEntity> saveTelemetry(EntityId entityIdSrc, String requestBody, long ttl) throws ThingsboardException {
TelemetryUploadRequest telemetryRequest;
JsonElement telemetryJson;
try {
telemetryJson = new JsonParser().parse(requestBody);
} catch (Exception e) {
return getImmediateDeferredResult("Unable to parse timeseries payload: Invalid JSON body!", HttpStatus.BAD_REQUEST);
}
try {
telemetryRequest = JsonConverter.convertToTelemetry(telemetryJson);
} catch (Exception e) {
return getImmediateDeferredResult("Unable to parse timeseries payload. Invalid JSON body: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
List<TsKvEntry> entries = new ArrayList<>();
for (Map.Entry<Long, List<KvEntry>> entry : telemetryRequest.getData().entrySet()) {
for (KvEntry kv : entry.getValue()) {
entries.add(new BasicTsKvEntry(entry.getKey(), kv));
}
}
if (entries.isEmpty()) {
return getImmediateDeferredResult("No timeseries data found in request body!", HttpStatus.BAD_REQUEST);
}
SecurityUser user = getCurrentUser();
return accessValidator.validateEntityAndCallback(getCurrentUser(), entityIdSrc, (result, entityId) -> {
tsSubService.saveAndNotify(entityId, entries, ttl, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void tmp) {
result.setResult(new ResponseEntity(HttpStatus.OK));
}
@Override
public void onFailure(Throwable t) {
AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR);
}
});
});
}
private void getLatestTimeseriesValuesCallback(@Nullable DeferredResult<ResponseEntity> result, SecurityUser user, EntityId entityId, String keys) {
ListenableFuture<List<TsKvEntry>> future;
if (StringUtils.isEmpty(keys)) {
future = tsService.findAllLatest(entityId);
} else {
future = tsService.findLatest(entityId, toKeysList(keys));
}
Futures.addCallback(future, getTsKvListCallback(result));
}
private void getAttributeValuesCallback(@Nullable DeferredResult<ResponseEntity> result, SecurityUser user, EntityId entityId, String scope, String keys) {
List<String> keyList = toKeysList(keys);
FutureCallback<List<AttributeKvEntry>> callback = getAttributeValuesToResponseCallback(result, user, scope, entityId, keyList);
if (!StringUtils.isEmpty(scope)) {
if (keyList != null && !keyList.isEmpty()) {
Futures.addCallback(attributesService.find(entityId, scope, keyList), callback);
} else {
Futures.addCallback(attributesService.findAll(entityId, scope), callback);
}
} else {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
for (String tmpScope : DataConstants.allScopes()) {
if (keyList != null && !keyList.isEmpty()) {
futures.add(attributesService.find(entityId, tmpScope, keyList));
} else {
futures.add(attributesService.findAll(entityId, tmpScope));
}
}
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, callback);
}
}
private void getAttributeKeysCallback(@Nullable DeferredResult<ResponseEntity> result, EntityId entityId, String scope) {
Futures.addCallback(attributesService.findAll(entityId, scope), getAttributeKeysToResponseCallback(result));
}
private void getAttributeKeysCallback(@Nullable DeferredResult<ResponseEntity> result, EntityId entityId) {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
for (String scope : DataConstants.allScopes()) {
futures.add(attributesService.findAll(entityId, scope));
}
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, getAttributeKeysToResponseCallback(result));
}
private FutureCallback<List<TsKvEntry>> getTsKeysToResponseCallback(final DeferredResult<ResponseEntity> response) {
return new FutureCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(List<TsKvEntry> values) {
List<String> keys = values.stream().map(KvEntry::getKey).collect(Collectors.toList());
response.setResult(new ResponseEntity<>(keys, HttpStatus.OK));
}
@Override
public void onFailure(Throwable e) {
log.error("Failed to fetch attributes", e);
AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
};
}
private FutureCallback<List<AttributeKvEntry>> getAttributeKeysToResponseCallback(final DeferredResult<ResponseEntity> response) {
return new FutureCallback<List<AttributeKvEntry>>() {
@Override
public void onSuccess(List<AttributeKvEntry> attributes) {
List<String> keys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList());
response.setResult(new ResponseEntity<>(keys, HttpStatus.OK));
}
@Override
public void onFailure(Throwable e) {
log.error("Failed to fetch attributes", e);
AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
};
}
private FutureCallback<List<AttributeKvEntry>> getAttributeValuesToResponseCallback(final DeferredResult<ResponseEntity> response,
final SecurityUser user, final String scope,
final EntityId entityId, final List<String> keyList) {
return new FutureCallback<List<AttributeKvEntry>>() {
@Override
public void onSuccess(List<AttributeKvEntry> attributes) {
List<AttributeData> values = attributes.stream().map(attribute -> new AttributeData(attribute.getLastUpdateTs(),
attribute.getKey(), attribute.getValue())).collect(Collectors.toList());
logAttributesRead(user, entityId, scope, keyList, null);
response.setResult(new ResponseEntity<>(values, HttpStatus.OK));
}
@Override
public void onFailure(Throwable e) {
log.error("Failed to fetch attributes", e);
logAttributesRead(user, entityId, scope, keyList, e);
AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
};
}
private FutureCallback<List<TsKvEntry>> getTsKvListCallback(final DeferredResult<ResponseEntity> response) {
return new FutureCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(List<TsKvEntry> data) {
Map<String, List<TsData>> result = new LinkedHashMap<>();
for (TsKvEntry entry : data) {
result.computeIfAbsent(entry.getKey(), k -> new ArrayList<>())
.add(new TsData(entry.getTs(), entry.getValueAsString()));
}
response.setResult(new ResponseEntity<>(result, HttpStatus.OK));
}
@Override
public void onFailure(Throwable e) {
log.error("Failed to fetch historical data", e);
AccessValidator.handleError(e, response, HttpStatus.INTERNAL_SERVER_ERROR);
}
};
}
private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
auditLogService.logEntityAction(
user.getTenantId(),
user.getCustomerId(),
user.getId(),
user.getName(),
(UUIDBased & EntityId) entityId,
null,
ActionType.ATTRIBUTES_DELETED,
toException(e),
scope,
keys);
}
private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List<AttributeKvEntry> attributes, Throwable e) {
auditLogService.logEntityAction(
user.getTenantId(),
user.getCustomerId(),
user.getId(),
user.getName(),
(UUIDBased & EntityId) entityId,
null,
ActionType.ATTRIBUTES_UPDATED,
toException(e),
scope,
attributes);
}
private void logAttributesRead(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
auditLogService.logEntityAction(
user.getTenantId(),
user.getCustomerId(),
user.getId(),
user.getName(),
(UUIDBased & EntityId) entityId,
null,
ActionType.ATTRIBUTES_READ,
toException(e),
scope,
keys);
}
private ListenableFuture<List<AttributeKvEntry>> mergeAllAttributesFutures(List<ListenableFuture<List<AttributeKvEntry>>> futures) {
return Futures.transform(Futures.successfulAsList(futures),
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
List<AttributeKvEntry> tmp = new ArrayList<>();
if (input != null) {
input.forEach(tmp::addAll);
}
return tmp;
}, executor);
}
private List<String> toKeysList(String keys) {
List<String> keyList = null;
if (!StringUtils.isEmpty(keys)) {
keyList = Arrays.asList(keys.split(","));
}
return keyList;
}
private DeferredResult<ResponseEntity> getImmediateDeferredResult(String message, HttpStatus status) {
DeferredResult<ResponseEntity> result = new DeferredResult<>();
result.setResult(new ResponseEntity<>(message, status));
return result;
}
private List<AttributeKvEntry> extractRequestAttributes(JsonNode jsonNode) {
long ts = System.currentTimeMillis();
List<AttributeKvEntry> attributes = new ArrayList<>();
jsonNode.fields().forEachRemaining(entry -> {
String key = entry.getKey();
JsonNode value = entry.getValue();
if (entry.getValue().isTextual()) {
attributes.add(new BaseAttributeKvEntry(new StringDataEntry(key, value.textValue()), ts));
} else if (entry.getValue().isBoolean()) {
attributes.add(new BaseAttributeKvEntry(new BooleanDataEntry(key, value.booleanValue()), ts));
} else if (entry.getValue().isDouble()) {
attributes.add(new BaseAttributeKvEntry(new DoubleDataEntry(key, value.doubleValue()), ts));
} else if (entry.getValue().isNumber()) {
if (entry.getValue().isBigInteger()) {
throw new UncheckedApiException(new InvalidParametersException("Big integer values are not supported!"));
} else {
attributes.add(new BaseAttributeKvEntry(new LongDataEntry(key, value.longValue()), ts));
}
}
});
return attributes;
}
}

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

@ -24,7 +24,7 @@ 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.dao.tenant.TenantService;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
@RestController
@RequestMapping("/api")

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

@ -29,9 +29,9 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.service.mail.MailService;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.servlet.http.HttpServletRequest;

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

@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.List;

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

@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import java.util.List;

131
application/src/main/java/org/thingsboard/server/controller/plugin/PluginApiController.java

@ -17,90 +17,69 @@ package org.thingsboard.server.controller.plugin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.controller.BaseController;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.exception.ThingsboardException;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.PluginConstants;
import org.thingsboard.server.extensions.api.plugins.rest.BasicPluginRestMsg;
import org.thingsboard.server.extensions.api.plugins.rest.RestRequest;
import javax.servlet.http.HttpServletRequest;
@RestController
@RequestMapping(PluginConstants.PLUGIN_URL_PREFIX)
@Slf4j
public class PluginApiController extends BaseController {
@SuppressWarnings("rawtypes")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{pluginToken}/**")
@ResponseStatus(value = HttpStatus.OK)
public DeferredResult<ResponseEntity> processRequest(
@PathVariable("pluginToken") String pluginToken,
RequestEntity<byte[]> requestEntity,
HttpServletRequest request)
throws ThingsboardException {
log.debug("[{}] Going to process requst uri: {}", pluginToken, requestEntity.getUrl());
DeferredResult<ResponseEntity> result = new DeferredResult<ResponseEntity>();
PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
if (pluginMd == null) {
result.setErrorResult(new PluginNotFoundException("Plugin with token: " + pluginToken + " not found!"));
} else {
TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = getCurrentUser().getCustomerId();
if (validatePluginAccess(pluginMd, tenantId, customerId)) {
if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){
tenantId = null;
}
UserId userId = getCurrentUser().getId();
String userName = getCurrentUser().getName();
PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(),
tenantId, customerId, userId, userName);
actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
} else {
result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
}
}
return result;
}
public static boolean validatePluginAccess(PluginMetaData pluginMd, TenantId tenantId, CustomerId customerId) {
boolean systemAdministrator = tenantId == null || ModelConstants.NULL_UUID.equals(tenantId.getId());
boolean tenantAdministrator = !systemAdministrator && (customerId == null || ModelConstants.NULL_UUID.equals(customerId.getId()));
boolean systemPlugin = ModelConstants.NULL_UUID.equals(pluginMd.getTenantId().getId());
boolean validUser = false;
if (systemPlugin) {
if (pluginMd.isPublicAccess() || systemAdministrator) {
// All users can access public system plugins. Only system
// users can access private system plugins
validUser = true;
}
} else {
if ((pluginMd.isPublicAccess() || tenantAdministrator) && tenantId != null && tenantId.equals(pluginMd.getTenantId())) {
// All tenant users can access public tenant plugins. Only tenant
// administrator can access private tenant plugins
validUser = true;
}
}
return validUser;
}
// @SuppressWarnings("rawtypes")
// @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
// @RequestMapping(value = "/{pluginToken}/**")
// @ResponseStatus(value = HttpStatus.OK)
// public DeferredResult<ResponseEntity> processRequest(
// @PathVariable("pluginToken") String pluginToken,
// RequestEntity<byte[]> requestEntity,
// HttpServletRequest request)
// throws ThingsboardException {
// log.debug("[{}] Going to process requst uri: {}", pluginToken, requestEntity.getUrl());
// DeferredResult<ResponseEntity> result = new DeferredResult<ResponseEntity>();
// PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
// if (pluginMd == null) {
// result.setErrorResult(new PluginNotFoundException("Plugin with token: " + pluginToken + " not found!"));
// } else {
// TenantId tenantId = getCurrentUser().getTenantId();
// CustomerId customerId = getCurrentUser().getCustomerId();
// if (validatePluginAccess(pluginMd, tenantId, customerId)) {
// if(tenantId != null && ModelConstants.NULL_UUID.equals(tenantId.getId())){
// tenantId = null;
// }
// UserId userId = getCurrentUser().getId();
// String userName = getCurrentUser().getName();
// PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(),
// tenantId, customerId, userId, userName);
// actorService.process(new BasicPluginRestMsg(securityCtx, new RestRequest(requestEntity, request), result));
// } else {
// result.setResult(new ResponseEntity<>(HttpStatus.FORBIDDEN));
// }
//
// }
// return result;
// }
//
// public static boolean validatePluginAccess(PluginMetaData pluginMd, TenantId tenantId, CustomerId customerId) {
// boolean systemAdministrator = tenantId == null || ModelConstants.NULL_UUID.equals(tenantId.getId());
// boolean tenantAdministrator = !systemAdministrator && (customerId == null || ModelConstants.NULL_UUID.equals(customerId.getId()));
// boolean systemPlugin = ModelConstants.NULL_UUID.equals(pluginMd.getTenantId().getId());
//
// boolean validUser = false;
// if (systemPlugin) {
// if (pluginMd.isPublicAccess() || systemAdministrator) {
// // All users can access public system plugins. Only system
// // users can access private system plugins
// validUser = true;
// }
// } else {
// if ((pluginMd.isPublicAccess() || tenantAdministrator) && tenantId != null && tenantId.equals(pluginMd.getTenantId())) {
// // All tenant users can access public tenant plugins. Only tenant
// // administrator can access private tenant plugins
// validUser = true;
// }
// }
// return validUser;
// }
}

133
application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java → application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -15,57 +15,42 @@
*/
package org.thingsboard.server.controller.plugin;
import java.io.IOException;
import java.net.URI;
import java.security.InvalidParameterException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.context.annotation.Lazy;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.extensions.api.plugins.PluginConstants;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.dao.plugin.PluginService;
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
import org.thingsboard.server.extensions.api.plugins.ws.BasicPluginWebsocketSessionRef;
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.SessionEventPluginWebSocketMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.TextPluginWebSocketMsg;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
import org.thingsboard.server.extensions.api.plugins.ws.msg.TextPluginWebSocketMsg;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketSessionRef;
import java.io.IOException;
import java.net.URI;
import java.security.InvalidParameterException;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Service
@Slf4j
public class PluginWebSocketHandler extends TextWebSocketHandler implements PluginWebSocketMsgEndpoint {
public class TbWebSocketHandler extends TextWebSocketHandler implements PluginWebSocketMsgEndpoint, TelemetryWebSocketMsgEndpoint {
private static final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
@Autowired
@Lazy
private ActorService actorService;
@Autowired
@Lazy
private PluginService pluginService;
private TelemetryWebSocketService webSocketService;
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
@ -73,7 +58,7 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
log.info("[{}] Processing {}", session.getId(), message);
SessionMetaData sessionMd = internalSessionMap.get(session.getId());
if (sessionMd != null) {
actorService.process(new TextPluginWebSocketMsg(sessionMd.sessionRef, message.getPayload()));
webSocketService.handleWebSocketMsg(sessionMd.sessionRef, message.getPayload());
} else {
log.warn("[{}] Failed to find session", session.getId());
session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!"));
@ -88,11 +73,11 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
super.afterConnectionEstablished(session);
try {
String internalSessionId = session.getId();
PluginWebsocketSessionRef sessionRef = toRef(session);
TelemetryWebSocketSessionRef sessionRef = toRef(session);
String externalSessionId = sessionRef.getSessionId();
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef));
externalSessionMap.put(externalSessionId, internalSessionId);
actorService.process(new SessionEventPluginWebSocketMsg(sessionRef, SessionEvent.onEstablished()));
processInWebSocketService(sessionRef, SessionEvent.onEstablished());
log.info("[{}][{}] Session is started", externalSessionId, session.getId());
} catch (InvalidParameterException e) {
log.warn("[[{}] Failed to start session", session.getId(), e);
@ -108,7 +93,7 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
super.handleTransportError(session, tError);
SessionMetaData sessionMd = internalSessionMap.get(session.getId());
if (sessionMd != null) {
processInActorService(new SessionEventPluginWebSocketMsg(sessionMd.sessionRef, SessionEvent.onError(tError)));
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onError(tError));
} else {
log.warn("[{}] Failed to find session", session.getId());
}
@ -121,20 +106,20 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
SessionMetaData sessionMd = internalSessionMap.remove(session.getId());
if (sessionMd != null) {
externalSessionMap.remove(sessionMd.sessionRef.getSessionId());
processInActorService(new SessionEventPluginWebSocketMsg(sessionMd.sessionRef, SessionEvent.onClosed()));
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
}
log.info("[{}] Session is closed", session.getId());
}
private void processInActorService(SessionEventPluginWebSocketMsg msg) {
private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {
try {
actorService.process(msg);
webSocketService.handleWebSocketSessionEvent(sessionRef, event);
} catch (BeanCreationNotAllowedException e) {
log.warn("[{}] Failed to close session due to possible shutdown state", msg.getSessionRef().getSessionId());
log.warn("[{}] Failed to close session due to possible shutdown state", sessionRef.getSessionId());
}
}
private PluginWebsocketSessionRef toRef(WebSocketSession session) throws IOException {
private TelemetryWebSocketSessionRef toRef(WebSocketSession session) throws IOException {
URI sessionUri = session.getUri();
String path = sessionUri.getPath();
path = path.substring(WebSocketConfiguration.WS_PLUGIN_PREFIX.length());
@ -142,39 +127,63 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
throw new IllegalArgumentException("URL should contain plugin token!");
}
String[] pathElements = path.split("/");
String pluginToken = pathElements[0];
// TODO: cache
PluginMetaData pluginMd = pluginService.findPluginByApiToken(pluginToken);
if (pluginMd == null) {
String serviceToken = pathElements[0];
if (!"telemetry".equalsIgnoreCase(serviceToken)) {
throw new InvalidParameterException("Can't find plugin with specified token!");
} else {
SecurityUser currentUser = (SecurityUser) session.getAttributes().get(WebSocketConfiguration.WS_SECURITY_USER_ATTRIBUTE);
TenantId tenantId = currentUser.getTenantId();
CustomerId customerId = currentUser.getCustomerId();
if (PluginApiController.validatePluginAccess(pluginMd, tenantId, customerId)) {
UserId userId = currentUser.getId();
String userName = currentUser.getName();
PluginApiCallSecurityContext securityCtx = new PluginApiCallSecurityContext(pluginMd.getTenantId(), pluginMd.getId(), tenantId,
currentUser.getCustomerId(), userId, userName);
return new BasicPluginWebsocketSessionRef(UUID.randomUUID().toString(), securityCtx, session.getUri(), session.getAttributes(),
session.getLocalAddress(), session.getRemoteAddress());
} else {
throw new SecurityException("Current user is not allowed to use this plugin!");
}
return new TelemetryWebSocketSessionRef(UUID.randomUUID().toString(), currentUser, session.getLocalAddress(), session.getRemoteAddress());
}
}
private static class SessionMetaData {
private final WebSocketSession session;
private final PluginWebsocketSessionRef sessionRef;
private final TelemetryWebSocketSessionRef sessionRef;
public SessionMetaData(WebSocketSession session, PluginWebsocketSessionRef sessionRef) {
public SessionMetaData(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) {
super();
this.session = session;
this.sessionRef = sessionRef;
}
}
@Override
public void send(TelemetryWebSocketSessionRef sessionRef, String msg) throws IOException {
String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing {}", externalId, msg);
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) {
synchronized (sessionMd) {
sessionMd.session.sendMessage(new TextMessage(msg));
}
} else {
log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
}
} else {
log.warn("[{}] Failed to find session by external id", externalId);
}
}
@Override
public void close(TelemetryWebSocketSessionRef sessionRef) throws IOException {
String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing close request", externalId);
String internalId = externalSessionMap.get(externalId);
if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) {
sessionMd.session.close(CloseStatus.NORMAL);
} else {
log.warn("[{}][{}] Failed to find session by internal id", externalId, internalId);
}
} else {
log.warn("[{}] Failed to find session by external id", externalId);
}
}
//TODO: remove
@Override
public void send(PluginWebsocketMsg<?> wsMsg) throws IOException {
PluginWebsocketSessionRef sessionRef = wsMsg.getSessionRef();
@ -196,6 +205,7 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
}
}
//TODO: remove
@Override
public void close(PluginWebsocketSessionRef sessionRef) throws IOException {
String externalId = sessionRef.getSessionId();
@ -212,5 +222,4 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug
log.warn("[{}] Failed to find session by external id", externalId);
}
}
}

1
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java

@ -16,6 +16,7 @@
package org.thingsboard.server.exception;
import org.springframework.http.HttpStatus;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import java.util.Date;

4
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java

@ -17,8 +17,6 @@ package org.thingsboard.server.exception;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -27,6 +25,8 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
import org.thingsboard.server.service.security.exception.JwtExpiredTokenException;

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

@ -16,6 +16,8 @@
package org.thingsboard.server.service.component;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
@ -26,12 +28,14 @@ import org.springframework.context.annotation.ClassPathScanningCandidateComponen
import org.springframework.core.env.Environment;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.extensions.api.component.*;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.stream.Collectors;
@ -66,6 +70,24 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
}
}
private void registerRuleNodeComponents() {
Set<BeanDefinition> ruleNodeBeanDefinitions = getBeanDefinitions(RuleNode.class);
for (BeanDefinition def : ruleNodeBeanDefinitions) {
try {
String clazzName = def.getBeanClassName();
Class<?> clazz = Class.forName(clazzName);
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
ComponentType type = ruleNodeAnnotation.type();
ComponentDescriptor component = scanAndPersistComponent(def, type);
components.put(component.getClazz(), component);
componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
} catch (Exception e) {
log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
private void registerComponents(ComponentType type, Class<? extends Annotation> annotation) {
List<ComponentDescriptor> components = persist(getBeanDefinitions(annotation), type);
componentsMap.put(type, components);
@ -79,8 +101,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
private List<ComponentDescriptor> persist(Set<BeanDefinition> filterDefs, ComponentType type) {
List<ComponentDescriptor> result = new ArrayList<>();
for (BeanDefinition def : filterDefs) {
ComponentDescriptor scannedComponent = scanAndPersistComponent(def, type);
result.add(scannedComponent);
result.add(scanAndPersistComponent(def, type));
}
return result;
}
@ -93,23 +114,26 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
Class<?> clazz = Class.forName(clazzName);
String descriptorResourceName;
switch (type) {
case ENRICHMENT:
case FILTER:
Filter filterAnnotation = clazz.getAnnotation(Filter.class);
scannedComponent.setName(filterAnnotation.name());
scannedComponent.setScope(filterAnnotation.scope());
descriptorResourceName = filterAnnotation.descriptor();
break;
case PROCESSOR:
Processor processorAnnotation = clazz.getAnnotation(Processor.class);
scannedComponent.setName(processorAnnotation.name());
scannedComponent.setScope(processorAnnotation.scope());
descriptorResourceName = processorAnnotation.descriptor();
break;
case TRANSFORMATION:
case ACTION:
Action actionAnnotation = clazz.getAnnotation(Action.class);
scannedComponent.setName(actionAnnotation.name());
scannedComponent.setScope(actionAnnotation.scope());
descriptorResourceName = actionAnnotation.descriptor();
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setConfigurationDescriptor(configurationDescriptor);
break;
case OLD_ACTION:
Action oldActionAnnotation = clazz.getAnnotation(Action.class);
scannedComponent.setName(oldActionAnnotation.name());
scannedComponent.setScope(oldActionAnnotation.scope());
descriptorResourceName = oldActionAnnotation.descriptor();
scannedComponent.setConfigurationDescriptor(mapper.readTree(
Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8)));
break;
case PLUGIN:
Plugin pluginAnnotation = clazz.getAnnotation(Plugin.class);
@ -122,18 +146,18 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
log.error("Can't initialize plugin {}, due to missing action {}!", def.getBeanClassName(), actionClazz.getName());
return new ClassNotFoundException("Action: " + actionClazz.getName() + "is missing!");
});
if (actionComponent.getType() != ComponentType.ACTION) {
if (actionComponent.getType() != ComponentType.OLD_ACTION) {
log.error("Plugin {} action {} has wrong component type!", def.getBeanClassName(), actionClazz.getName(), actionComponent.getType());
throw new RuntimeException("Plugin " + def.getBeanClassName() + "action " + actionClazz.getName() + " has wrong component type!");
}
}
scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(action -> action.getName()).collect(Collectors.joining(",")));
scannedComponent.setActions(Arrays.stream(pluginAnnotation.actions()).map(Class::getName).collect(Collectors.joining(",")));
scannedComponent.setConfigurationDescriptor(mapper.readTree(
Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8)));
break;
default:
throw new RuntimeException(type + " is not supported yet!");
}
scannedComponent.setConfigurationDescriptor(mapper.readTree(
Resources.toString(Resources.getResource(descriptorResourceName), Charsets.UTF_8)));
scannedComponent.setClazz(clazzName);
log.info("Processing scanned component: {}", scannedComponent);
} catch (Exception e) {
@ -156,6 +180,23 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
return scannedComponent;
}
private NodeDefinition prepareNodeDefinition(RuleNode nodeAnnotation) throws Exception {
NodeDefinition nodeDefinition = new NodeDefinition();
nodeDefinition.setDetails(nodeAnnotation.nodeDetails());
nodeDefinition.setDescription(nodeAnnotation.nodeDescription());
nodeDefinition.setInEnabled(nodeAnnotation.inEnabled());
nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled());
nodeDefinition.setRelationTypes(nodeAnnotation.relationTypes());
nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
NodeConfiguration config = configClazz.newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration();
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
nodeDefinition.setUiResources(nodeAnnotation.uiResources());
nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
return nodeDefinition;
}
private Set<BeanDefinition> getBeanDefinitions(Class<? extends Annotation> componentType) {
ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(componentType));
@ -168,11 +209,10 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
@Override
public void discoverComponents() {
registerComponents(ComponentType.FILTER, Filter.class);
registerComponents(ComponentType.PROCESSOR, Processor.class);
registerRuleNodeComponents();
registerComponents(ComponentType.ACTION, Action.class);
registerComponents(ComponentType.OLD_ACTION, Action.class);
registerComponents(ComponentType.PLUGIN, Plugin.class);
@ -181,7 +221,20 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
@Override
public List<ComponentDescriptor> getComponents(ComponentType type) {
return Collections.unmodifiableList(componentsMap.get(type));
if (componentsMap.containsKey(type)) {
return Collections.unmodifiableList(componentsMap.get(type));
} else {
return Collections.emptyList();
}
}
@Override
public List<ComponentDescriptor> getComponents(Set<ComponentType> types) {
List<ComponentDescriptor> result = new ArrayList<>();
types.stream().filter(type -> componentsMap.containsKey(type)).forEach(type -> {
result.addAll(componentsMap.get(type));
});
return Collections.unmodifiableList(result);
}
@Override
@ -199,7 +252,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
}
List<ComponentDescriptor> result = new ArrayList<>();
for (String action : plugin.getActions().split(",")) {
getComponent(action).ifPresent(v -> result.add(v));
getComponent(action).ifPresent(result::add);
}
return result;
} else {

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

@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import java.util.List;
import java.util.Optional;
import java.util.Set;
/**
* @author Andrew Shvayka
@ -30,6 +31,8 @@ public interface ComponentDiscoveryService {
List<ComponentDescriptor> getComponents(ComponentType type);
List<ComponentDescriptor> getComponents(Set<ComponentType> types);
Optional<ComponentDescriptor> getComponent(String clazz);
List<ComponentDescriptor> getPluginActions(String pluginClazz);

59
application/src/main/java/org/thingsboard/server/service/executors/AbstractListeningExecutor.java

@ -0,0 +1,59 @@
/**
* Copyright © 2016-2018 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.executors;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.thingsboard.rule.engine.api.ListeningExecutor;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
/**
* Created by igor on 4/13/18.
*/
public abstract class AbstractListeningExecutor implements ListeningExecutor {
private ListeningExecutorService service;
@PostConstruct
public void init() {
this.service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(getThreadPollSize()));
}
@PreDestroy
public void destroy() {
if (this.service != null) {
this.service.shutdown();
}
}
@Override
public <T> ListenableFuture<T> executeAsync(Callable<T> task) {
return service.submit(task);
}
@Override
public void execute(Runnable command) {
service.execute(command);
}
protected abstract int getThreadPollSize();
}

32
application/src/main/java/org/thingsboard/server/service/executors/DbCallbackExecutorService.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2018 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.executors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DbCallbackExecutorService extends AbstractListeningExecutor {
@Value("${actors.rule.db_callback_thread_pool_size}")
private int dbCallbackExecutorThreadPoolSize;
@Override
protected int getThreadPollSize() {
return dbCallbackExecutorThreadPoolSize;
}
}

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

@ -187,7 +187,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@Override
public void loadSystemRules() throws Exception {
loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null);
// loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null);
}
@Override
@ -228,7 +228,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
"Raspberry Pi GPIO control sample application");
loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId());
loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId());
// loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId());
loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null);
}

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

@ -27,13 +27,15 @@ import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.ui.velocity.VelocityEngineUtils;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.exception.ThingsboardErrorCode;
import org.thingsboard.server.exception.ThingsboardException;
import javax.annotation.PostConstruct;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.util.HashMap;
import java.util.Locale;
@ -49,18 +51,18 @@ public class DefaultMailService implements MailService {
public static final String UTF_8 = "UTF-8";
@Autowired
private MessageSource messages;
@Autowired
@Qualifier("velocityEngine")
private VelocityEngine engine;
private JavaMailSenderImpl mailSender;
private String mailFrom;
@Autowired
private AdminSettingsService adminSettingsService;
private AdminSettingsService adminSettingsService;
@PostConstruct
private void init() {
updateMailConfiguration();
@ -77,7 +79,7 @@ public class DefaultMailService implements MailService {
throw new IncorrectParameterException("Failed to date mail configuration. Settings not found!");
}
}
private JavaMailSenderImpl createMailSender(JsonNode jsonConfig) {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(jsonConfig.get("smtpHost").asText());
@ -99,7 +101,7 @@ public class DefaultMailService implements MailService {
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", jsonConfig.has("enableTls") ? jsonConfig.get("enableTls").asText() : "false");
return javaMailProperties;
}
private int parsePort(String strPort) {
try {
return Integer.valueOf(strPort);
@ -112,86 +114,102 @@ public class DefaultMailService implements MailService {
public void sendEmail(String email, String subject, String message) throws ThingsboardException {
sendMail(mailSender, mailFrom, email, subject, message);
}
@Override
public void sendTestMail(JsonNode jsonConfig, String email) throws ThingsboardException {
JavaMailSenderImpl testMailSender = createMailSender(jsonConfig);
String mailFrom = jsonConfig.get("mailFrom").asText();
String subject = messages.getMessage("test.message.subject", null, Locale.US);
Map<String, Object> model = new HashMap<String, Object>();
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
"test.vm", UTF_8, model);
sendMail(testMailSender, mailFrom, email, subject, message);
sendMail(testMailSender, mailFrom, email, subject, message);
}
@Override
public void sendActivationEmail(String activationLink, String email) throws ThingsboardException {
String subject = messages.getMessage("activation.subject", null, Locale.US);
Map<String, Object> model = new HashMap<String, Object>();
model.put("activationLink", activationLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
"activation.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
sendMail(mailSender, mailFrom, email, subject, message);
}
@Override
public void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException {
String subject = messages.getMessage("account.activated.subject", null, Locale.US);
Map<String, Object> model = new HashMap<String, Object>();
model.put("loginLink", loginLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
"account.activated.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
sendMail(mailSender, mailFrom, email, subject, message);
}
@Override
public void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException {
String subject = messages.getMessage("reset.password.subject", null, Locale.US);
Map<String, Object> model = new HashMap<String, Object>();
model.put("passwordResetLink", passwordResetLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
"reset.password.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
sendMail(mailSender, mailFrom, email, subject, message);
}
@Override
public void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException {
String subject = messages.getMessage("password.was.reset.subject", null, Locale.US);
Map<String, Object> model = new HashMap<String, Object>();
model.put("loginLink", loginLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
"password.was.reset.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
sendMail(mailSender, mailFrom, email, subject, message);
}
@Override
public void send(String from, String to, String cc, String bcc, String subject, String body) throws MessagingException {
MimeMessage mailMsg = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8");
helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from);
helper.setTo(to.split("\\s*,\\s*"));
if (!StringUtils.isBlank(cc)) {
helper.setCc(cc.split("\\s*,\\s*"));
}
if (!StringUtils.isBlank(bcc)) {
helper.setBcc(bcc.split("\\s*,\\s*"));
}
helper.setSubject(subject);
helper.setText(body);
mailSender.send(helper.getMimeMessage());
}
private void sendMail(JavaMailSenderImpl mailSender,
String mailFrom, String email,
String subject, String message) throws ThingsboardException {
private void sendMail(JavaMailSenderImpl mailSender,
String mailFrom, String email,
String subject, String message) throws ThingsboardException {
try {
MimeMessage mimeMsg = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMsg, UTF_8);
@ -208,7 +226,7 @@ public class DefaultMailService implements MailService {
protected ThingsboardException handleException(Exception exception) {
String message;
if (exception instanceof NestedRuntimeException) {
message = ((NestedRuntimeException)exception).getMostSpecificCause().getMessage();
message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage();
} else {
message = exception.getMessage();
}

33
application/src/main/java/org/thingsboard/server/service/mail/MailExecutorService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2018 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.mail;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.service.executors.AbstractListeningExecutor;
@Component
public class MailExecutorService extends AbstractListeningExecutor {
@Value("${actors.rule.mail_thread_pool_size}")
private int mailExecutorThreadPoolSize;
@Override
protected int getThreadPollSize() {
return mailExecutorThreadPoolSize;
}
}

33
application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.script;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.service.executors.AbstractListeningExecutor;
@Component
public class JsExecutorService extends AbstractListeningExecutor {
@Value("${actors.rule.js_thread_pool_size}")
private int jsExecutorThreadPoolSize;
@Override
protected int getThreadPollSize() {
return jsExecutorThreadPoolSize;
}
}

211
application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java

@ -0,0 +1,211 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.script;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
@Slf4j
public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEngine {
public static final String MSG = "msg";
public static final String METADATA = "metadata";
public static final String MSG_TYPE = "msgType";
private static final String JS_WRAPPER_PREFIX_TEMPLATE = "function %s(msgStr, metadataStr, msgType) { " +
" var msg = JSON.parse(msgStr); " +
" var metadata = JSON.parse(metadataStr); " +
" return JSON.stringify(%s(msg, metadata, msgType));" +
" function %s(%s, %s, %s) {";
private static final String JS_WRAPPER_SUFFIX = "}" +
"\n}";
private static final ObjectMapper mapper = new ObjectMapper();
private static NashornScriptEngineFactory factory = new NashornScriptEngineFactory();
private ScriptEngine engine = factory.getScriptEngine(new String[]{"--no-java"});
private final String invokeFunctionName;
public NashornJsEngine(String script, String functionName, String... argNames) {
this.invokeFunctionName = "invokeInternal" + this.hashCode();
String msgArg;
String metadataArg;
String msgTypeArg;
if (argNames != null && argNames.length == 3) {
msgArg = argNames[0];
metadataArg = argNames[1];
msgTypeArg = argNames[2];
} else {
msgArg = MSG;
metadataArg = METADATA;
msgTypeArg = MSG_TYPE;
}
String jsWrapperPrefix = String.format(JS_WRAPPER_PREFIX_TEMPLATE, this.invokeFunctionName,
functionName, functionName, msgArg, metadataArg, msgTypeArg);
compileScript(jsWrapperPrefix + script + JS_WRAPPER_SUFFIX);
}
private void compileScript(String script) {
try {
engine.eval(script);
} catch (ScriptException e) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
}
}
private static String[] prepareArgs(TbMsg msg) {
try {
String[] args = new String[3];
if (msg.getData() != null) {
args[0] = msg.getData();
} else {
args[0] = "";
}
args[1] = mapper.writeValueAsString(msg.getMetaData().getData());
args[2] = msg.getType();
return args;
} catch (Throwable th) {
throw new IllegalArgumentException("Cannot bind js args", th);
}
}
private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) {
try {
String data = null;
Map<String, String> metadata = null;
String messageType = null;
if (msgData.has(MSG)) {
JsonNode msgPayload = msgData.get(MSG);
data = mapper.writeValueAsString(msgPayload);
}
if (msgData.has(METADATA)) {
JsonNode msgMetadata = msgData.get(METADATA);
metadata = mapper.convertValue(msgMetadata, new TypeReference<Map<String, String>>() {
});
}
if (msgData.has(MSG_TYPE)) {
messageType = msgData.get(MSG_TYPE).asText();
}
String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData);
} catch (Throwable th) {
th.printStackTrace();
throw new RuntimeException("Failed to unbind message data from javascript result", th);
}
}
@Override
public TbMsg executeUpdate(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (!result.isObject()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
}
return unbindMsg(result, msg);
}
@Override
public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException {
JsonNode result = executeScript(prevMsg);
if (!result.isObject()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
}
return unbindMsg(result, prevMsg);
}
@Override
public JsonNode executeJson(TbMsg msg) throws ScriptException {
return executeScript(msg);
}
@Override
public String executeToString(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (!result.isTextual()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
}
return result.asText();
}
@Override
public boolean executeFilter(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (!result.isBoolean()) {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
}
return result.asBoolean();
}
@Override
public Set<String> executeSwitch(TbMsg msg) throws ScriptException {
JsonNode result = executeScript(msg);
if (result.isTextual()) {
return Collections.singleton(result.asText());
} else if (result.isArray()) {
Set<String> nextStates = Sets.newHashSet();
for (JsonNode val : result) {
if (!val.isTextual()) {
log.warn("Wrong result type: {}", val.getNodeType());
throw new ScriptException("Wrong result type: " + val.getNodeType());
} else {
nextStates.add(val.asText());
}
}
return nextStates;
} else {
log.warn("Wrong result type: {}", result.getNodeType());
throw new ScriptException("Wrong result type: " + result.getNodeType());
}
}
private JsonNode executeScript(TbMsg msg) throws ScriptException {
try {
String[] inArgs = prepareArgs(msg);
String eval = ((Invocable)engine).invokeFunction(this.invokeFunctionName, inArgs[0], inArgs[1], inArgs[2]).toString();
return mapper.readTree(eval);
} catch (ScriptException | IllegalArgumentException th) {
throw th;
} catch (Throwable th) {
th.printStackTrace();
throw new RuntimeException("Failed to execute js script", th);
}
}
public void destroy() {
engine = null;
}
}

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

@ -0,0 +1,295 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.actors.plugin.ValidationResult;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.controller.HttpValidationCallback;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
import org.thingsboard.server.service.security.model.SecurityUser;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiConsumer;
/**
* Created by ashvayka on 27.03.18.
*/
@Component
public class AccessValidator {
public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
@Autowired
protected TenantService tenantService;
@Autowired
protected CustomerService customerService;
@Autowired
protected UserService userService;
@Autowired
protected DeviceService deviceService;
@Autowired
protected AssetService assetService;
@Autowired
protected AlarmService alarmService;
@Autowired
protected RuleChainService ruleChainService;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, String entityType, String entityIdStr,
BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess) throws ThingsboardException {
return validateEntityAndCallback(currentUser, entityType, entityIdStr, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
}
public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, String entityType, String entityIdStr,
BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess,
BiConsumer<DeferredResult<ResponseEntity>, Throwable> onFailure) throws ThingsboardException {
return validateEntityAndCallback(currentUser, EntityIdFactory.getByTypeAndId(entityType, entityIdStr),
onSuccess, onFailure);
}
public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, EntityId entityId,
BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess) throws ThingsboardException {
return validateEntityAndCallback(currentUser, entityId, onSuccess, (result, t) -> handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR));
}
public DeferredResult<ResponseEntity> validateEntityAndCallback(SecurityUser currentUser, EntityId entityId,
BiConsumer<DeferredResult<ResponseEntity>, EntityId> onSuccess,
BiConsumer<DeferredResult<ResponseEntity>, Throwable> onFailure) throws ThingsboardException {
final DeferredResult<ResponseEntity> response = new DeferredResult<>();
validate(currentUser, entityId, new HttpValidationCallback(response,
new FutureCallback<DeferredResult<ResponseEntity>>() {
@Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
onSuccess.accept(response, entityId);
}
@Override
public void onFailure(Throwable t) {
onFailure.accept(response, t);
}
}));
return response;
}
public <T> void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
switch (entityId.getEntityType()) {
case DEVICE:
validateDevice(currentUser, entityId, callback);
return;
case ASSET:
validateAsset(currentUser, entityId, callback);
return;
case RULE_CHAIN:
validateRuleChain(currentUser, entityId, callback);
return;
case CUSTOMER:
validateCustomer(currentUser, entityId, callback);
return;
case TENANT:
validateTenant(currentUser, entityId, callback);
return;
default:
//TODO: add support of other entities
throw new IllegalStateException("Not Implemented!");
}
}
private void validateDevice(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<Device> deviceFuture = deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId()));
Futures.addCallback(deviceFuture, getCallback(callback, device -> {
if (device == null) {
return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND);
} else {
if (!device.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!");
} else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
}
}
}), executor);
}
}
private <T> void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<Asset> assetFuture = assetService.findAssetByIdAsync(new AssetId(entityId.getId()));
Futures.addCallback(assetFuture, getCallback(callback, asset -> {
if (asset == null) {
return ValidationResult.entityNotFound("Asset with requested id wasn't found!");
} else {
if (!asset.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!");
} else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
} else {
return ValidationResult.ok();
}
}
}), executor);
}
}
private <T> void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<RuleChain> ruleChainFuture = ruleChainService.findRuleChainByIdAsync(new RuleChainId(entityId.getId()));
Futures.addCallback(ruleChainFuture, getCallback(callback, ruleChain -> {
if (ruleChain == null) {
return ValidationResult.entityNotFound("Rule chain with requested id wasn't found!");
} else {
if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
} else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
return ValidationResult.accessDenied("Rule chain is not in system scope!");
} else {
return ValidationResult.ok();
}
}
}), executor);
}
}
private <T> void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else {
ListenableFuture<Customer> customerFuture = customerService.findCustomerByIdAsync(new CustomerId(entityId.getId()));
Futures.addCallback(customerFuture, getCallback(callback, customer -> {
if (customer == null) {
return ValidationResult.entityNotFound("Customer with requested id wasn't found!");
} else {
if (!customer.getTenantId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!");
} else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
} else {
return ValidationResult.ok();
}
}
}), executor);
}
}
private <T> void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
if (currentUser.isCustomerUser()) {
callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
} else if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.ok());
} else {
ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
if (tenant == null) {
return ValidationResult.entityNotFound("Tenant with requested id wasn't found!");
} else if (!tenant.getId().equals(currentUser.getTenantId())) {
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
} else {
return ValidationResult.ok();
}
}), executor);
}
}
private <T> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult> transformer) {
return new FutureCallback<T>() {
@Override
public void onSuccess(@Nullable T result) {
callback.onSuccess(transformer.apply(result));
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
public static void handleError(Throwable e, final DeferredResult<ResponseEntity> response, HttpStatus defaultErrorStatus) {
ResponseEntity responseEntity;
if (e != null && e instanceof ToErrorResponseEntity) {
responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
} else if (e != null && e instanceof IllegalArgumentException) {
responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
} else {
responseEntity = new ResponseEntity<>(defaultErrorStatus);
}
response.setResult(responseEntity);
}
}

71
application/src/main/java/org/thingsboard/server/service/security/ValidationCallback.java

@ -0,0 +1,71 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security;
import com.google.common.util.concurrent.FutureCallback;
import org.thingsboard.server.actors.plugin.ValidationResult;
import org.thingsboard.server.actors.plugin.ValidationResultCode;
import org.thingsboard.server.extensions.api.exception.AccessDeniedException;
import org.thingsboard.server.extensions.api.exception.EntityNotFoundException;
import org.thingsboard.server.extensions.api.exception.InternalErrorException;
import org.thingsboard.server.extensions.api.exception.UnauthorizedException;
/**
* Created by ashvayka on 31.03.18.
*/
public class ValidationCallback<T> implements FutureCallback<ValidationResult> {
private final T response;
private final FutureCallback<T> action;
public ValidationCallback(T response, FutureCallback<T> action) {
this.response = response;
this.action = action;
}
@Override
public void onSuccess(ValidationResult result) {
ValidationResultCode resultCode = result.getResultCode();
if (resultCode == ValidationResultCode.OK) {
action.onSuccess(response);
} else {
Exception e;
switch (resultCode) {
case ENTITY_NOT_FOUND:
e = new EntityNotFoundException(result.getMessage());
break;
case UNAUTHORIZED:
e = new UnauthorizedException(result.getMessage());
break;
case ACCESS_DENIED:
e = new AccessDeniedException(result.getMessage());
break;
case INTERNAL_ERROR:
e = new InternalErrorException(result.getMessage());
break;
default:
e = new UnauthorizedException("Permission denied.");
break;
}
onFailure(e);
}
}
@Override
public void onFailure(Throwable e) {
action.onFailure(e);
}
}

1
application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.model;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId;
import java.util.Collection;

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

@ -0,0 +1,335 @@
/**
* Copyright © 2016-2018 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.telemetry;
import com.google.common.util.concurrent.FutureCallback;
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.springframework.util.StringUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryFeature;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
* Created by ashvayka on 27.03.18.
*/
@Service
@Slf4j
public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptionService {
@Autowired
private TelemetryWebSocketService wsService;
@Autowired
private AttributesService attrService;
@Autowired
private TimeseriesService tsService;
@Autowired
private ClusterRoutingService routingService;
private ExecutorService tsCallBackExecutor;
private ExecutorService wsCallBackExecutor;
@PostConstruct
public void initExecutor() {
tsCallBackExecutor = Executors.newSingleThreadExecutor();
wsCallBackExecutor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (tsCallBackExecutor != null) {
tsCallBackExecutor.shutdownNow();
}
if (wsCallBackExecutor != null) {
wsCallBackExecutor.shutdownNow();
}
}
private final Map<EntityId, Set<Subscription>> subscriptionsByEntityId = new HashMap<>();
private final Map<String, Map<Integer, Subscription>> subscriptionsByWsSessionId = new HashMap<>();
@Override
public void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub) {
Optional<ServerAddress> server = routingService.resolveById(entityId);
Subscription subscription;
if (server.isPresent()) {
ServerAddress address = server.get();
log.trace("[{}] Forwarding subscription [{}] for device [{}] to [{}]", sessionId, sub.getSubscriptionId(), entityId, address);
subscription = new Subscription(sub, true, address);
// rpcHandler.onNewSubscription(ctx, address, sessionId, subscription);
} else {
log.trace("[{}] Registering local subscription [{}] for device [{}]", sessionId, sub.getSubscriptionId(), entityId);
subscription = new Subscription(sub, true);
}
registerSubscription(sessionId, entityId, subscription);
}
@Override
public void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId) {
cleanupLocalWsSessionSubscriptions(sessionId);
}
@Override
public void removeSubscription(String sessionId, int subscriptionId) {
log.debug("[{}][{}] Going to remove subscription.", sessionId, subscriptionId);
Map<Integer, Subscription> sessionSubscriptions = subscriptionsByWsSessionId.get(sessionId);
if (sessionSubscriptions != null) {
Subscription subscription = sessionSubscriptions.remove(subscriptionId);
if (subscription != null) {
processSubscriptionRemoval(sessionId, sessionSubscriptions, subscription);
} else {
log.debug("[{}][{}] Subscription not found!", sessionId, subscriptionId);
}
} else {
log.debug("[{}] No session subscriptions found!", sessionId);
}
}
@Override
public void saveAndNotify(EntityId entityId, List<TsKvEntry> ts, FutureCallback<Void> callback) {
saveAndNotify(entityId, ts, 0L, callback);
}
@Override
public void saveAndNotify(EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
ListenableFuture<List<Void>> saveFuture = tsService.save(entityId, ts, ttl);
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeseriesUpdate(entityId, ts));
}
@Override
public void saveAndNotify(EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
ListenableFuture<List<Void>> saveFuture = attrService.save(entityId, scope, attributes);
addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onAttributesUpdate(entityId, scope, attributes));
}
private void onAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
Optional<ServerAddress> serverAddress = routingService.resolveById(entityId);
if (!serverAddress.isPresent()) {
onLocalAttributesUpdate(entityId, scope, attributes);
} else {
// rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, entries);
}
}
private void onTimeseriesUpdate(EntityId entityId, List<TsKvEntry> ts) {
Optional<ServerAddress> serverAddress = routingService.resolveById(entityId);
if (!serverAddress.isPresent()) {
onLocalTimeseriesUpdate(entityId, ts);
} else {
// rpcHandler.onTimeseriesUpdate(ctx, serverAddress.get(), entityId, entries);
}
}
private void onLocalAttributesUpdate(EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
onLocalSubUpdate(entityId, s -> TelemetryFeature.ATTRIBUTES == s.getType() && (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope())), s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (AttributeKvEntry kv : attributes) {
if (s.isAllKeys() || s.getKeyStates().containsKey(kv.getKey())) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
subscriptionUpdate.add(new BasicTsKvEntry(kv.getLastUpdateTs(), kv));
}
}
return subscriptionUpdate;
});
}
private void onLocalTimeseriesUpdate(EntityId entityId, List<TsKvEntry> ts) {
onLocalSubUpdate(entityId, s -> TelemetryFeature.TIMESERIES == s.getType(), s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (TsKvEntry kv : ts) {
if (s.isAllKeys() || s.getKeyStates().containsKey((kv.getKey()))) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
subscriptionUpdate.add(kv);
}
}
return subscriptionUpdate;
});
}
private void onLocalSubUpdate(EntityId entityId, Predicate<Subscription> filter, Function<Subscription, List<TsKvEntry>> f) {
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) {
deviceSubscriptions.stream().filter(filter).forEach(s -> {
String sessionId = s.getWsSessionId();
List<TsKvEntry> subscriptionUpdate = f.apply(s);
if (subscriptionUpdate == null || !subscriptionUpdate.isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(s.getSubscriptionId(), subscriptionUpdate);
if (s.isLocal()) {
updateSubscriptionState(sessionId, s, update);
wsService.sendWsMsg(sessionId, update);
} else {
//TODO: ashvayka
// rpcHandler.onSubscriptionUpdate(ctx, s.getServer(), sessionId, update);
}
}
});
} else {
log.debug("[{}] No device subscriptions to process!", entityId);
}
}
private void updateSubscriptionState(String sessionId, Subscription subState, SubscriptionUpdate update) {
log.trace("[{}] updating subscription state {} using onUpdate {}", sessionId, subState, update);
update.getLatestValues().entrySet().forEach(e -> subState.setKeyState(e.getKey(), e.getValue()));
}
private void registerSubscription(String sessionId, EntityId entityId, Subscription subscription) {
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.computeIfAbsent(entityId, k -> new HashSet<>());
deviceSubscriptions.add(subscription);
Map<Integer, Subscription> sessionSubscriptions = subscriptionsByWsSessionId.computeIfAbsent(sessionId, k -> new HashMap<>());
sessionSubscriptions.put(subscription.getSubscriptionId(), subscription);
}
public void cleanupLocalWsSessionSubscriptions(String sessionId) {
cleanupWsSessionSubscriptions(sessionId, true);
}
public void cleanupRemoteWsSessionSubscriptions(String sessionId) {
cleanupWsSessionSubscriptions(sessionId, false);
}
private void cleanupWsSessionSubscriptions(String sessionId, boolean localSession) {
log.debug("[{}] Removing all subscriptions for particular session.", sessionId);
Map<Integer, Subscription> sessionSubscriptions = subscriptionsByWsSessionId.get(sessionId);
if (sessionSubscriptions != null) {
int sessionSubscriptionSize = sessionSubscriptions.size();
for (Subscription subscription : sessionSubscriptions.values()) {
EntityId entityId = subscription.getEntityId();
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
deviceSubscriptions.remove(subscription);
if (deviceSubscriptions.isEmpty()) {
subscriptionsByEntityId.remove(entityId);
}
}
subscriptionsByWsSessionId.remove(sessionId);
log.debug("[{}] Removed {} subscriptions for particular session.", sessionId, sessionSubscriptionSize);
if (localSession) {
notifyWsSubscriptionClosed(sessionId, sessionSubscriptions);
}
} else {
log.debug("[{}] No subscriptions found!", sessionId);
}
}
private void notifyWsSubscriptionClosed(String sessionId, Map<Integer, Subscription> sessionSubscriptions) {
Set<ServerAddress> affectedServers = new HashSet<>();
for (Subscription subscription : sessionSubscriptions.values()) {
if (subscription.getServer() != null) {
affectedServers.add(subscription.getServer());
}
}
for (ServerAddress address : affectedServers) {
log.debug("[{}] Going to onSubscriptionUpdate [{}] server about session close event", sessionId, address);
// rpcHandler.onSessionClose(ctx, address, sessionId);
}
}
private void processSubscriptionRemoval(String sessionId, Map<Integer, Subscription> sessionSubscriptions, Subscription subscription) {
EntityId entityId = subscription.getEntityId();
if (subscription.isLocal() && subscription.getServer() != null) {
// rpcHandler.onSubscriptionClose(ctx, subscription.getServer(), sessionId, subscription.getSubscriptionId());
}
if (sessionSubscriptions.isEmpty()) {
log.debug("[{}] Removed last subscription for particular session.", sessionId);
subscriptionsByWsSessionId.remove(sessionId);
} else {
log.debug("[{}] Removed session subscription.", sessionId);
}
Set<Subscription> deviceSubscriptions = subscriptionsByEntityId.get(entityId);
if (deviceSubscriptions != null) {
boolean result = deviceSubscriptions.remove(subscription);
if (result) {
if (deviceSubscriptions.size() == 0) {
log.debug("[{}] Removed last subscription for particular device.", sessionId);
subscriptionsByEntityId.remove(entityId);
} else {
log.debug("[{}] Removed device subscription.", sessionId);
}
} else {
log.debug("[{}] Subscription not found!", sessionId);
}
} else {
log.debug("[{}] No device subscriptions found!", sessionId);
}
}
private void addMainCallback(ListenableFuture<List<Void>> saveFuture, final FutureCallback<Void> callback) {
Futures.addCallback(saveFuture, new FutureCallback<List<Void>>() {
@Override
public void onSuccess(@Nullable List<Void> result) {
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
}, tsCallBackExecutor);
}
private void addWsCallback(ListenableFuture<List<Void>> saveFuture, Consumer<Void> callback) {
Futures.addCallback(saveFuture, new FutureCallback<List<Void>>() {
@Override
public void onSuccess(@Nullable List<Void> result) {
callback.accept(null);
}
@Override
public void onFailure(Throwable t) {
}
}, wsCallBackExecutor);
}
}

563
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -0,0 +1,563 @@
/**
* Copyright © 2016-2018 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.telemetry;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.hazelcast.util.function.Consumer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.actors.plugin.ValidationResult;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.extensions.api.exception.UnauthorizedException;
import org.thingsboard.server.extensions.api.plugins.PluginContext;
import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.AttributesSubscriptionCmd;
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.GetHistoryCmd;
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.SubscriptionCmd;
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TelemetryPluginCmd;
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.extensions.core.plugin.telemetry.cmd.TimeseriesSubscriptionCmd;
import org.thingsboard.server.extensions.core.plugin.telemetry.handlers.TelemetryFeature;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
import org.thingsboard.server.service.security.AccessValidator;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
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.stream.Collectors;
/**
* Created by ashvayka on 27.03.18.
*/
@Service
@Slf4j
public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService {
public static final int DEFAULT_LIMIT = 100;
public static final Aggregation DEFAULT_AGGREGATION = Aggregation.NONE;
private static final int UNKNOWN_SUBSCRIPTION_ID = 0;
private static final String PROCESSING_MSG = "[{}] Processing: {}";
private static final ObjectMapper jsonMapper = new ObjectMapper();
private static final String FAILED_TO_FETCH_DATA = "Failed to fetch data!";
private static final String FAILED_TO_FETCH_ATTRIBUTES = "Failed to fetch attributes!";
private static final String SESSION_META_DATA_NOT_FOUND = "Session meta-data not found!";
private final ConcurrentMap<String, WsSessionMetaData> wsSessionsMap = new ConcurrentHashMap<>();
@Autowired
private TelemetrySubscriptionService subscriptionManager;
@Autowired
private TelemetryWebSocketMsgEndpoint msgEndpoint;
@Autowired
private AccessValidator accessValidator;
@Autowired
private AttributesService attributesService;
@Autowired
private TimeseriesService tsService;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = Executors.newSingleThreadExecutor();
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
@Override
public void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {
String sessionId = sessionRef.getSessionId();
log.debug(PROCESSING_MSG, sessionId, event);
switch (event.getEventType()) {
case ESTABLISHED:
wsSessionsMap.put(sessionId, new WsSessionMetaData(sessionRef));
break;
case ERROR:
log.debug("[{}] Unknown websocket session error: {}. ", sessionId, event.getError().orElse(null));
break;
case CLOSED:
wsSessionsMap.remove(sessionId);
subscriptionManager.cleanupLocalWsSessionSubscriptions(sessionRef, sessionId);
break;
}
}
@Override
public void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg) {
if (log.isTraceEnabled()) {
log.trace("[{}] Processing: {}", sessionRef.getSessionId(), msg);
}
try {
TelemetryPluginCmdsWrapper cmdsWrapper = jsonMapper.readValue(msg, TelemetryPluginCmdsWrapper.class);
if (cmdsWrapper != null) {
if (cmdsWrapper.getAttrSubCmds() != null) {
cmdsWrapper.getAttrSubCmds().forEach(cmd -> handleWsAttributesSubscriptionCmd(sessionRef, cmd));
}
if (cmdsWrapper.getTsSubCmds() != null) {
cmdsWrapper.getTsSubCmds().forEach(cmd -> handleWsTimeseriesSubscriptionCmd(sessionRef, cmd));
}
if (cmdsWrapper.getHistoryCmds() != null) {
cmdsWrapper.getHistoryCmds().forEach(cmd -> handleWsHistoryCmd(sessionRef, cmd));
}
}
} catch (IOException e) {
log.warn("Failed to decode subscription cmd: {}", e.getMessage(), e);
SubscriptionUpdate update = new SubscriptionUpdate(UNKNOWN_SUBSCRIPTION_ID, SubscriptionErrorCode.INTERNAL_ERROR, SESSION_META_DATA_NOT_FOUND);
sendWsMsg(sessionRef, update);
}
}
@Override
public void sendWsMsg(String sessionId, SubscriptionUpdate update) {
WsSessionMetaData md = wsSessionsMap.get(sessionId);
if (md != null) {
sendWsMsg(md.getSessionRef(), update);
}
}
private void handleWsAttributesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, AttributesSubscriptionCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
if (validateSessionMetadata(sessionRef, cmd, sessionId)) {
if (cmd.isUnsubscribe()) {
unsubscribe(sessionRef, cmd, sessionId);
} else if (validateSubscriptionCmd(sessionRef, cmd)) {
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
log.debug("[{}] fetching latest attributes ({}) values for device: {}", sessionId, cmd.getKeys(), entityId);
Optional<Set<String>> keysOptional = getKeys(cmd);
if (keysOptional.isPresent()) {
List<String> keys = new ArrayList<>(keysOptional.get());
handleWsAttributesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId, keys);
} else {
handleWsAttributesSubscription(sessionRef, cmd, sessionId, entityId);
}
}
}
}
private void handleWsAttributesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef,
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId,
List<String> keys) {
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<List<AttributeKvEntry>>() {
@Override
public void onSuccess(List<AttributeKvEntry> data) {
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
Map<String, Long> subState = new HashMap<>(keys.size());
keys.forEach(key -> subState.put(key, 0L));
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, TelemetryFeature.ATTRIBUTES, false, subState, cmd.getScope());
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub);
}
@Override
public void onFailure(Throwable e) {
log.error(FAILED_TO_FETCH_ATTRIBUTES, e);
SubscriptionUpdate update;
if (UnauthorizedException.class.isInstance(e)) {
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED,
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg());
} else {
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_ATTRIBUTES);
}
sendWsMsg(sessionRef, update);
}
};
if (StringUtils.isEmpty(cmd.getScope())) {
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, keys, callback));
} else {
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, cmd.getScope(), keys, callback));
}
}
private void handleWsHistoryCmd(TelemetryWebSocketSessionRef sessionRef, GetHistoryCmd cmd) {
String sessionId = sessionRef.getSessionId();
WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
if (sessionMD == null) {
log.warn("[{}] Session meta data not found. ", sessionId);
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
SESSION_META_DATA_NOT_FOUND);
sendWsMsg(sessionRef, update);
return;
}
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty() || cmd.getEntityType() == null || cmd.getEntityType().isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Device id is empty!");
sendWsMsg(sessionRef, update);
return;
}
if (cmd.getKeys() == null || cmd.getKeys().isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Keys are empty!");
sendWsMsg(sessionRef, update);
return;
}
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
.collect(Collectors.toList());
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(List<TsKvEntry> data) {
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
}
@Override
public void onFailure(Throwable e) {
SubscriptionUpdate update;
if (UnauthorizedException.class.isInstance(e)) {
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED,
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg());
} else {
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_DATA);
}
sendWsMsg(sessionRef, update);
}
};
accessValidator.validate(sessionRef.getSecurityCtx(), entityId,
on(r -> Futures.addCallback(tsService.findAll(entityId, queries), callback, executor), callback::onFailure));
}
private void handleWsAttributesSubscription(TelemetryWebSocketSessionRef sessionRef,
AttributesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
FutureCallback<List<AttributeKvEntry>> callback = new FutureCallback<List<AttributeKvEntry>>() {
@Override
public void onSuccess(List<AttributeKvEntry> data) {
List<TsKvEntry> attributesData = data.stream().map(d -> new BasicTsKvEntry(d.getLastUpdateTs(), d)).collect(Collectors.toList());
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), attributesData));
Map<String, Long> subState = new HashMap<>(attributesData.size());
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, TelemetryFeature.ATTRIBUTES, true, subState, cmd.getScope());
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub);
}
@Override
public void onFailure(Throwable e) {
log.error(FAILED_TO_FETCH_ATTRIBUTES, e);
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_ATTRIBUTES);
sendWsMsg(sessionRef, update);
}
};
if (StringUtils.isEmpty(cmd.getScope())) {
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, callback));
} else {
accessValidator.validate(sessionRef.getSecurityCtx(), entityId, getAttributesFetchCallback(entityId, cmd.getScope(), callback));
}
}
private void handleWsTimeseriesSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, TimeseriesSubscriptionCmd cmd) {
String sessionId = sessionRef.getSessionId();
log.debug("[{}] Processing: {}", sessionId, cmd);
if (validateSessionMetadata(sessionRef, cmd, sessionId)) {
if (cmd.isUnsubscribe()) {
unsubscribe(sessionRef, cmd, sessionId);
} else if (validateSubscriptionCmd(sessionRef, cmd)) {
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
Optional<Set<String>> keysOptional = getKeys(cmd);
if (keysOptional.isPresent()) {
handleWsTimeseriesSubscriptionByKeys(sessionRef, cmd, sessionId, entityId);
} else {
handleWsTimeseriesSubscription(sessionRef, cmd, sessionId, entityId);
}
}
}
}
private void handleWsTimeseriesSubscriptionByKeys(TelemetryWebSocketSessionRef sessionRef,
TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
long startTs;
if (cmd.getTimeWindow() > 0) {
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId);
startTs = cmd.getStartTs();
long endTs = cmd.getStartTs() + cmd.getTimeWindow();
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(),
getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys);
accessValidator.validate(sessionRef.getSecurityCtx(), entityId,
on(r -> Futures.addCallback(tsService.findAll(entityId, queries), callback, executor), callback::onFailure));
} else {
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
startTs = System.currentTimeMillis();
log.debug("[{}] fetching latest timeseries data for keys: ({}) for device : {}", sessionId, cmd.getKeys(), entityId);
final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys);
accessValidator.validate(sessionRef.getSecurityCtx(), entityId,
on(r -> Futures.addCallback(tsService.findLatest(entityId, keys), callback, executor), callback::onFailure));
}
}
private void handleWsTimeseriesSubscription(TelemetryWebSocketSessionRef sessionRef,
TimeseriesSubscriptionCmd cmd, String sessionId, EntityId entityId) {
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(List<TsKvEntry> data) {
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
Map<String, Long> subState = new HashMap<>(data.size());
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, TelemetryFeature.TIMESERIES, true, subState, cmd.getScope());
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub);
}
@Override
public void onFailure(Throwable e) {
SubscriptionUpdate update;
if (UnauthorizedException.class.isInstance(e)) {
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.UNAUTHORIZED,
SubscriptionErrorCode.UNAUTHORIZED.getDefaultMsg());
} else {
update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_DATA);
}
sendWsMsg(sessionRef, update);
}
};
accessValidator.validate(sessionRef.getSecurityCtx(), entityId,
on(r -> Futures.addCallback(tsService.findAllLatest(entityId), callback, executor), callback::onFailure));
}
private FutureCallback<List<TsKvEntry>> getSubscriptionCallback(final TelemetryWebSocketSessionRef sessionRef, final TimeseriesSubscriptionCmd cmd, final String sessionId, final EntityId entityId, final long startTs, final List<String> keys) {
return new FutureCallback<List<TsKvEntry>>() {
@Override
public void onSuccess(List<TsKvEntry> data) {
sendWsMsg(sessionRef, new SubscriptionUpdate(cmd.getCmdId(), data));
Map<String, Long> subState = new HashMap<>(keys.size());
keys.forEach(key -> subState.put(key, startTs));
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
SubscriptionState sub = new SubscriptionState(sessionId, cmd.getCmdId(), entityId, TelemetryFeature.TIMESERIES, false, subState, cmd.getScope());
subscriptionManager.addLocalWsSubscription(sessionId, entityId, sub);
}
@Override
public void onFailure(Throwable e) {
log.error(FAILED_TO_FETCH_DATA, e);
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
FAILED_TO_FETCH_DATA);
sendWsMsg(sessionRef, update);
}
};
}
private void unsubscribe(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
subscriptionManager.cleanupLocalWsSessionSubscriptions(sessionRef, sessionId);
} else {
subscriptionManager.removeSubscription(sessionId, cmd.getCmdId());
}
}
private boolean validateSubscriptionCmd(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) {
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.BAD_REQUEST,
"Device id is empty!");
sendWsMsg(sessionRef, update);
return false;
}
return true;
}
private boolean validateSessionMetadata(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
WsSessionMetaData sessionMD = wsSessionsMap.get(sessionId);
if (sessionMD == null) {
log.warn("[{}] Session meta data not found. ", sessionId);
SubscriptionUpdate update = new SubscriptionUpdate(cmd.getCmdId(), SubscriptionErrorCode.INTERNAL_ERROR,
SESSION_META_DATA_NOT_FOUND);
sendWsMsg(sessionRef, update);
return false;
} else {
return true;
}
}
private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, SubscriptionUpdate update) {
try {
msgEndpoint.send(sessionRef, jsonMapper.writeValueAsString(update));
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to encode reply: {}", sessionRef.getSessionId(), update, e);
} catch (IOException e) {
log.warn("[{}] Failed to send reply: {}", sessionRef.getSessionId(), update, e);
}
}
private static Optional<Set<String>> getKeys(TelemetryPluginCmd cmd) {
if (!StringUtils.isEmpty(cmd.getKeys())) {
Set<String> keys = new HashSet<>();
Collections.addAll(keys, cmd.getKeys().split(","));
return Optional.of(keys);
} else {
return Optional.empty();
}
}
private ListenableFuture<List<AttributeKvEntry>> mergeAllAttributesFutures(List<ListenableFuture<List<AttributeKvEntry>>> futures) {
return Futures.transform(Futures.successfulAsList(futures),
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> {
List<AttributeKvEntry> tmp = new ArrayList<>();
if (input != null) {
input.forEach(tmp::addAll);
}
return tmp;
}, executor);
}
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final List<String> keys, final FutureCallback<List<AttributeKvEntry>> callback) {
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
for (String scope : DataConstants.allScopes()) {
futures.add(attributesService.find(entityId, scope, keys));
}
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, callback);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final String scope, final List<String> keys, final FutureCallback<List<AttributeKvEntry>> callback) {
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
Futures.addCallback(attributesService.find(entityId, scope, keys), callback);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final FutureCallback<List<AttributeKvEntry>> callback) {
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>();
for (String scope : DataConstants.allScopes()) {
futures.add(attributesService.findAll(entityId, scope));
}
ListenableFuture<List<AttributeKvEntry>> future = mergeAllAttributesFutures(futures);
Futures.addCallback(future, callback);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
private <T> FutureCallback<ValidationResult> getAttributesFetchCallback(final EntityId entityId, final String scope, final FutureCallback<List<AttributeKvEntry>> callback) {
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
Futures.addCallback(attributesService.findAll(entityId, scope), callback);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
private FutureCallback<ValidationResult> on(Consumer<ValidationResult> success, Consumer<Throwable> failure) {
return new FutureCallback<ValidationResult>() {
@Override
public void onSuccess(@Nullable ValidationResult result) {
success.accept(result);
}
@Override
public void onFailure(Throwable t) {
failure.accept(t);
}
};
}
private static Aggregation getAggregation(String agg) {
return StringUtils.isEmpty(agg) ? DEFAULT_AGGREGATION : Aggregation.valueOf(agg);
}
private int getLimit(int limit) {
return limit == 0 ? DEFAULT_LIMIT : limit;
}
}

33
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2018 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.telemetry;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState;
/**
* Created by ashvayka on 27.03.18.
*/
public interface TelemetrySubscriptionService extends RuleEngineTelemetryService {
void addLocalWsSubscription(String sessionId, EntityId entityId, SubscriptionState sub);
void cleanupLocalWsSessionSubscriptions(TelemetryWebSocketSessionRef sessionRef, String sessionId);
void removeSubscription(String sessionId, int cmdId);
}

29
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketMsgEndpoint.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2018 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.telemetry;
import java.io.IOException;
/**
* Created by ashvayka on 27.03.18.
*/
public interface TelemetryWebSocketMsgEndpoint {
void send(TelemetryWebSocketSessionRef sessionRef, String msg) throws IOException;
void close(TelemetryWebSocketSessionRef sessionRef) throws IOException;
}

31
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketService.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2018 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.telemetry;
import org.thingsboard.server.extensions.api.plugins.ws.SessionEvent;
import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate;
/**
* Created by ashvayka on 27.03.18.
*/
public interface TelemetryWebSocketService {
void handleWebSocketSessionEvent(TelemetryWebSocketSessionRef sessionRef, SessionEvent sessionEvent);
void handleWebSocketMsg(TelemetryWebSocketSessionRef sessionRef, String msg);
void sendWsMsg(String sessionId, SubscriptionUpdate update);
}

68
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketSessionRef.java

@ -0,0 +1,68 @@
/**
* Copyright © 2016-2018 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.telemetry;
import lombok.Getter;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.net.InetSocketAddress;
import java.util.Objects;
/**
* Created by ashvayka on 27.03.18.
*/
public class TelemetryWebSocketSessionRef {
private static final long serialVersionUID = 1L;
@Getter
private final String sessionId;
@Getter
private final SecurityUser securityCtx;
@Getter
private final InetSocketAddress localAddress;
@Getter
private final InetSocketAddress remoteAddress;
public TelemetryWebSocketSessionRef(String sessionId, SecurityUser securityCtx, InetSocketAddress localAddress, InetSocketAddress remoteAddress) {
this.sessionId = sessionId;
this.securityCtx = securityCtx;
this.localAddress = localAddress;
this.remoteAddress = remoteAddress;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
TelemetryWebSocketSessionRef that = (TelemetryWebSocketSessionRef) o;
return Objects.equals(sessionId, that.sessionId);
}
@Override
public int hashCode() {
return Objects.hash(sessionId);
}
@Override
public String toString() {
return "TelemetryWebSocketSessionRef{" +
"sessionId='" + sessionId + '\'' +
", localAddress=" + localAddress +
", remoteAddress=" + remoteAddress +
'}';
}
}

34
application/src/main/java/org/thingsboard/server/service/telemetry/TelemetryWebSocketTextMsg.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2018 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.telemetry;
import lombok.Data;
import lombok.Getter;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.net.InetSocketAddress;
import java.util.Objects;
/**
* Created by ashvayka on 27.03.18.
*/
@Data
public class TelemetryWebSocketTextMsg {
private final TelemetryWebSocketSessionRef sessionRef;
private final String payload;
}

53
application/src/main/java/org/thingsboard/server/service/telemetry/WsSessionMetaData.java

@ -0,0 +1,53 @@
/**
* Copyright © 2016-2018 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.telemetry;
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef;
/**
* Created by ashvayka on 27.03.18.
*/
public class WsSessionMetaData {
private TelemetryWebSocketSessionRef sessionRef;
private long lastActivityTime;
public WsSessionMetaData(TelemetryWebSocketSessionRef sessionRef) {
super();
this.sessionRef = sessionRef;
this.lastActivityTime = System.currentTimeMillis();
}
public TelemetryWebSocketSessionRef getSessionRef() {
return sessionRef;
}
public void setSessionRef(TelemetryWebSocketSessionRef sessionRef) {
this.sessionRef = sessionRef;
}
public long getLastActivityTime() {
return lastActivityTime;
}
public void setLastActivityTime(long lastActivityTime) {
this.lastActivityTime = lastActivityTime;
}
@Override
public String toString() {
return "WsSessionMetaData [sessionRef=" + sessionRef + ", lastActivityTime=" + lastActivityTime + "]";
}
}

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

@ -62,7 +62,7 @@ cluster:
# Plugins configuration parameters
plugins:
# Comma seperated package list used during classpath scanning for plugins
scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions}"
scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}"
# JWT Token parameters
security.jwt:
@ -181,6 +181,10 @@ cassandra:
default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}"
# Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS
ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}"
permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}"
rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:30000}"
queue:
msg.ttl: 604800 # 7 days
@ -215,6 +219,18 @@ actors:
termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}"
# Errors for particular actor are persisted once per specified amount of milliseconds
error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}"
# Specify thread pool size for database request callbacks executor service
db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:1}"
# Specify thread pool size for javascript executor service
js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:10}"
# Specify thread pool size for mail sender executor service
mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:10}"
chain:
# Errors for particular actor are persisted once per specified amount of milliseconds
error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}"
node:
# Errors for particular actor are persisted once per specified amount of milliseconds
error_persist_frequency: "${ACTORS_RULE_NODE_ERROR_FREQUENCY:3000}"
statistics:
# Enable/disable actor statistics
enabled: "${ACTORS_STATISTICS_ENABLED:true}"

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

Loading…
Cancel
Save