Browse Source

Merge branch 'develop/1.5' into develop/cluster-refactoring

pull/789/head
Andrew Shvayka 9 years ago
parent
commit
6855e2a885
  1. 2
      application/src/main/data/upgrade/1.5.0/schema_update.cql
  2. 5
      application/src/main/data/upgrade/1.5.0/schema_update.sql
  3. 4
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  4. 14
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  5. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  6. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  7. 35
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  9. 2
      application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java
  10. 8
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  11. 123
      application/src/test/java/org/thingsboard/server/system/BaseDeviceOfflineTest.java
  12. 35
      application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
  13. 23
      application/src/test/java/org/thingsboard/server/system/nosql/DeviceOfflineNoSqlTest.java
  14. 23
      application/src/test/java/org/thingsboard/server/system/sql/DeviceOfflineSqlTest.java
  15. 22
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  16. 40
      common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceStatusQuery.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java
  18. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  19. 74
      dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java
  20. 49
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
  21. 34
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineService.java
  22. 62
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineServiceImpl.java
  23. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  24. 28
      dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java
  25. 13
      dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java
  26. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java
  27. 71
      dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java
  28. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java
  29. 2
      dao/src/main/resources/cassandra/schema.cql
  30. 4
      dao/src/main/resources/sql/schema.sql
  31. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/NodeDefinition.java
  32. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java
  33. 4
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  34. 10
      rule-engine/rule-engine-components/pom.xml
  35. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java
  36. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java
  37. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java
  38. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java
  39. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
  40. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java
  41. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java
  42. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java
  43. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java
  44. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNodeConfiguration.java
  45. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java
  46. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
  47. 68
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/RuleVelocityUtils.java
  48. 51
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java
  49. 7
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java
  50. 74
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java
  51. 16
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNodeConfiguration.java
  52. 28
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java
  53. 32
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java
  54. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java
  55. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java
  56. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java
  57. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
  58. 5
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java
  59. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
  60. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java
  61. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
  62. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  63. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
  64. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TelemetryNodeCallback.java
  65. 23
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java
  66. 18
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java
  67. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java
  68. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java
  69. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java
  70. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java
  71. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java
  72. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java
  73. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java
  74. 6
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  75. 9
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java
  76. 23
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java
  77. 17
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java
  78. 33
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java
  79. 8
      transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  80. 6
      transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java
  81. 28
      transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java
  82. 20
      transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  83. 25
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  84. 7
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
  85. 6
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java
  86. 13
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
  87. 9
      ui/src/app/common/types.constant.js
  88. 2
      ui/src/app/locale/locale.constant.js
  89. 23
      ui/src/app/rulechain/rulechain.controller.js
  90. 5
      ui/src/app/rulechain/rulechain.scss
  91. 8
      ui/src/app/rulechain/rulechain.tpl.html
  92. 4
      ui/src/app/rulechain/rulenode.tpl.html
  93. 23
      ui/src/scss/main.scss

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

@ -94,5 +94,3 @@ CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
PRIMARY KEY (id) PRIMARY KEY (id)
); );
ALTER TABLE thingsboard.device ADD last_connect bigint;
ALTER TABLE thingsboard.device ADD last_update bigint;

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

@ -35,7 +35,4 @@ CREATE TABLE IF NOT EXISTS rule_node (
name varchar(255), name varchar(255),
debug_mode boolean, debug_mode boolean,
search_text varchar(255) 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;

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

@ -340,7 +340,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
} }
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1); SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1);
pushToRuleEngineWithTimeout(context, tbMsg, msgData); pushToRuleEngineWithTimeout(context, tbMsg, msgData);
@ -365,7 +365,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
} }
json.add("values", values); json.add("values", values);
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData.copy(), TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
pushToRuleEngineWithTimeout(context, tbMsg, msgData); pushToRuleEngineWithTimeout(context, tbMsg, msgData);
} }
} }

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

@ -61,11 +61,6 @@ class DefaultTbContext implements TbContext {
this.nodeCtx = nodeCtx; this.nodeCtx = nodeCtx;
} }
@Override
public void tellNext(TbMsg msg) {
tellNext(msg, (String) null);
}
@Override @Override
public void tellNext(TbMsg msg, String relationType) { public void tellNext(TbMsg msg, String relationType) {
tellNext(msg, relationType, null); tellNext(msg, relationType, null);
@ -99,11 +94,6 @@ class DefaultTbContext implements TbContext {
throw new RuntimeException("Not Implemented!"); throw new RuntimeException("Not Implemented!");
} }
@Override
public void spawn(TbMsg msg) {
throw new RuntimeException("Not Implemented!");
}
@Override @Override
public void ack(TbMsg msg) { public void ack(TbMsg msg) {
@ -124,12 +114,12 @@ class DefaultTbContext implements TbContext {
@Override @Override
public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(UUIDs.timeBased(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); return new TbMsg(UUIDs.timeBased(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
} }
@Override @Override
public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(origMsg.getId(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
} }
@Override @Override

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

@ -269,6 +269,6 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private TbMsg enrichWithRuleChainId(TbMsg tbMsg) { private TbMsg enrichWithRuleChainId(TbMsg tbMsg) {
// We don't put firstNodeId because it may change over time; // We don't put firstNodeId because it may change over time;
return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData(), tbMsg.getData(), entityId, null, 0L); return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getData(), entityId, null, 0L);
} }
} }

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

@ -45,7 +45,6 @@ import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceCredentialsService; 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.device.DeviceService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
@ -135,9 +134,6 @@ public abstract class BaseController {
@Autowired @Autowired
protected AuditLogService auditLogService; protected AuditLogService auditLogService;
@Autowired
protected DeviceOfflineService offlineService;
@Autowired @Autowired
protected DeviceStateService deviceStateService; protected DeviceStateService deviceStateService;

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

@ -25,9 +25,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.device.DeviceSearchQuery; 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.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -37,6 +34,8 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
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 org.thingsboard.server.service.security.model.SecurityUser;
import java.util.ArrayList; import java.util.ArrayList;
@ -70,7 +69,7 @@ public class DeviceController extends BaseController {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) { if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
if (device.getId() == null || device.getId().isNullUid() || 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!", throw new ThingsboardException("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED); ThingsboardErrorCode.PERMISSION_DENIED);
} else { } else {
@ -374,32 +373,4 @@ public class DeviceController extends BaseController {
throw handleException(e); 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);
}
}
} }

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

@ -118,6 +118,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
case FILTER: case FILTER:
case TRANSFORMATION: case TRANSFORMATION:
case ACTION: case ACTION:
case EXTERNAL:
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setName(ruleNodeAnnotation.name());
scannedComponent.setScope(ruleNodeAnnotation.scope()); scannedComponent.setScope(ruleNodeAnnotation.scope());
@ -194,6 +195,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration));
nodeDefinition.setUiResources(nodeAnnotation.uiResources()); nodeDefinition.setUiResources(nodeAnnotation.uiResources());
nodeDefinition.setConfigDirective(nodeAnnotation.configDirective()); nodeDefinition.setConfigDirective(nodeAnnotation.configDirective());
nodeDefinition.setIcon(nodeAnnotation.icon());
nodeDefinition.setIconUrl(nodeAnnotation.iconUrl());
return nodeDefinition; return nodeDefinition;
} }

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

@ -116,7 +116,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
messageType = msgData.get(MSG_TYPE).asText(); messageType = msgData.get(MSG_TYPE).asText();
} }
String newData = data != null ? data : msg.getData(); String newData = data != null ? data : msg.getData();
TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData(); TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy();
String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition()); return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition());
} catch (Throwable th) { } catch (Throwable th) {

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

@ -262,7 +262,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
state.setInactivityTimeout(inactivityTimeout); state.setInactivityTimeout(inactivityTimeout);
boolean oldActive = state.isActive(); boolean oldActive = state.isActive();
state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
if (!oldActive && state.isActive()) { if (!oldActive && state.isActive() || oldActive && !state.isActive()) {
saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
} }
} }
@ -333,10 +333,6 @@ public class DefaultDeviceStateService implements DeviceStateService {
}); });
} }
private long getLastPersistTime(List<AttributeKvEntry> attributes) {
return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L);
}
private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) { private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) {
for (AttributeKvEntry attribute : attributes) { for (AttributeKvEntry attribute : attributes) {
if (attribute.getKey().equals(attributeName)) { if (attribute.getKey().equals(attributeName)) {
@ -349,7 +345,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) { private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
DeviceState state = stateData.getState(); DeviceState state = stateData.getState();
try { try {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData(), TbMsgDataType.JSON TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON
, json.writeValueAsString(state) , json.writeValueAsString(state)
, null, null, 0L); , null, null, 0L);
actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg)); actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));

123
application/src/test/java/org/thingsboard/server/system/BaseDeviceOfflineTest.java

@ -1,123 +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.system;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.controller.AbstractControllerTest;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
public class BaseDeviceOfflineTest extends AbstractControllerTest {
private Device deviceA;
private Device deviceB;
private DeviceCredentials credA;
private DeviceCredentials credB;
@Before
public void before() throws Exception {
loginTenantAdmin();
deviceA = createDevice("DevA", "VMS");
credA = getCredentials(deviceA.getUuidId());
deviceB = createDevice("DevB", "SOLAR");
credB = getCredentials(deviceB.getUuidId());
}
@Test
public void offlineDevicesCanBeFoundByLastConnectField() throws Exception {
makeDeviceContact(credA);
Thread.sleep(1000);
makeDeviceContact(credB);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/offline?contactType=CONNECT&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
@Test
public void offlineDevicesCanBeFoundByLastUpdateField() throws Exception {
makeDeviceUpdate(credA);
Thread.sleep(1000);
makeDeviceUpdate(credB);
makeDeviceContact(credA);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/offline?contactType=UPLOAD&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
@Test
public void onlineDevicesCanBeFoundByLastConnectField() throws Exception {
makeDeviceContact(credB);
Thread.sleep(1000);
makeDeviceContact(credA);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/online?contactType=CONNECT&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
@Test
public void onlineDevicesCanBeFoundByLastUpdateField() throws Exception {
makeDeviceUpdate(credB);
Thread.sleep(1000);
makeDeviceUpdate(credA);
makeDeviceContact(credB);
Thread.sleep(100);
List<Device> devices = doGetTyped("/api/device/online?contactType=UPLOAD&threshold=700", new TypeReference<List<Device>>() {
});
assertEquals(devices.toString(),1, devices.size());
assertEquals("DevA", devices.get(0).getName());
}
private Device createDevice(String name, String type) throws Exception {
Device device = new Device();
device.setName(name);
device.setType(type);
long currentTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(10);
device.setLastConnectTs(currentTime);
device.setLastUpdateTs(currentTime);
return doPost("/api/device", device, Device.class);
}
private DeviceCredentials getCredentials(UUID deviceId) throws Exception {
return doGet("/api/device/" + deviceId.toString() + "/credentials", DeviceCredentials.class);
}
private void makeDeviceUpdate(DeviceCredentials credentials) throws Exception {
doPost("/api/v1/" + credentials.getCredentialsId() + "/attributes", ImmutableMap.of("keyA", "valueA"), new String[]{});
}
private void makeDeviceContact(DeviceCredentials credentials) throws Exception {
doGet("/api/v1/" + credentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC");
}
}

35
application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.system; package org.thingsboard.server.system;
import com.google.common.collect.ImmutableMap;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
@ -29,9 +28,6 @@ import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -52,9 +48,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
device = new Device(); device = new Device();
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
long currentTime = System.currentTimeMillis();
device.setLastConnectTs(currentTime);
device.setLastUpdateTs(currentTime);
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
deviceCredentials = deviceCredentials =
@ -74,34 +67,6 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk()); doGetAsync("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC").andExpect(status().isOk());
} }
@Test
public void deviceLastContactAndUpdateFieldsAreUpdated() throws Exception {
Device actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);
Long initConnectTs = actualDevice.getLastConnectTs();
Long initUpdateTs = actualDevice.getLastUpdateTs();
assertNotNull(initConnectTs);
assertNotNull(initUpdateTs);
Thread.sleep(50);
doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes", ImmutableMap.of("keyA", "valueA"), new String[]{});
actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);
Long postConnectTs = actualDevice.getLastConnectTs();
Long postUpdateTs = actualDevice.getLastUpdateTs();
System.out.println(postConnectTs + " - " + postUpdateTs + " -> " + (postConnectTs - initConnectTs) + " : " + (postUpdateTs - initUpdateTs));
assertTrue(postConnectTs > initConnectTs);
assertEquals(postConnectTs, postUpdateTs);
Thread.sleep(50);
doGet("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes?clientKeys=keyA,keyB,keyC");
Thread.sleep(50);
actualDevice = doGet("/api/device/" + this.device.getId(), Device.class);
Long getConnectTs = actualDevice.getLastConnectTs();
Long getUpdateTs = actualDevice.getLastUpdateTs();
assertTrue(getConnectTs > postConnectTs);
assertEquals(getUpdateTs, postUpdateTs);
}
protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception { protected ResultActions doGetAsync(String urlTemplate, Object... urlVariables) throws Exception {
MockHttpServletRequestBuilder getRequest; MockHttpServletRequestBuilder getRequest;
getRequest = get(urlTemplate, urlVariables); getRequest = get(urlTemplate, urlVariables);

23
application/src/test/java/org/thingsboard/server/system/nosql/DeviceOfflineNoSqlTest.java

@ -1,23 +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.system.nosql;
import org.thingsboard.server.dao.service.DaoNoSqlTest;
import org.thingsboard.server.system.BaseDeviceOfflineTest;
@DaoNoSqlTest
public class DeviceOfflineNoSqlTest extends BaseDeviceOfflineTest {
}

23
application/src/test/java/org/thingsboard/server/system/sql/DeviceOfflineSqlTest.java

@ -1,23 +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.system.sql;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.system.BaseDeviceOfflineTest;
@DaoSqlTest
public class DeviceOfflineSqlTest extends BaseDeviceOfflineTest {
}

22
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -31,8 +31,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
private CustomerId customerId; private CustomerId customerId;
private String name; private String name;
private String type; private String type;
private Long lastConnectTs;
private Long lastUpdateTs;
public Device() { public Device() {
super(); super();
@ -83,22 +81,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.type = type; this.type = type;
} }
public Long getLastConnectTs() {
return lastConnectTs;
}
public void setLastConnectTs(Long lastConnectTs) {
this.lastConnectTs = lastConnectTs;
}
public Long getLastUpdateTs() {
return lastUpdateTs;
}
public void setLastUpdateTs(Long lastUpdateTs) {
this.lastUpdateTs = lastUpdateTs;
}
@Override @Override
public String getSearchText() { public String getSearchText() {
return getName(); return getName();
@ -119,10 +101,6 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
builder.append(getAdditionalInfo()); builder.append(getAdditionalInfo());
builder.append(", createdTime="); builder.append(", createdTime=");
builder.append(createdTime); builder.append(createdTime);
builder.append(", lastUpdateTs=");
builder.append(lastUpdateTs);
builder.append(", lastConnectTs=");
builder.append(lastConnectTs);
builder.append(", id="); builder.append(", id=");
builder.append(id); builder.append(id);
builder.append("]"); builder.append("]");

40
common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceStatusQuery.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.common.data.device;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.ToString;
@Data
@AllArgsConstructor
@ToString
public class DeviceStatusQuery {
private Status status;
private ContactType contactType;
private long threshold;
public enum Status {
ONLINE, OFFLINE
}
public enum ContactType {
CONNECT, UPLOAD
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentType.java

@ -20,6 +20,6 @@ package org.thingsboard.server.common.data.plugin;
*/ */
public enum ComponentType { public enum ComponentType {
ENRICHMENT, FILTER, TRANSFORMATION, ACTION, OLD_ACTION, PLUGIN ENRICHMENT, FILTER, TRANSFORMATION, ACTION, EXTERNAL, OLD_ACTION, PLUGIN
} }

2
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -106,7 +106,7 @@ public final class TbMsg implements Serializable {
} }
public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) { public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) {
return new TbMsg(newId, type, originator, metaData, dataType, data, ruleChainId, ruleNodeId, clusterPartition); return new TbMsg(newId, type, originator, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, clusterPartition);
} }
} }

74
dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java

@ -15,9 +15,9 @@
*/ */
package org.thingsboard.server.dao.device; package org.thingsboard.server.dao.device;
import com.datastax.driver.core.*; import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.querybuilder.Clause; import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.Select; import com.datastax.driver.core.querybuilder.Select;
import com.datastax.driver.mapping.Result; import com.datastax.driver.mapping.Result;
import com.google.common.base.Function; import com.google.common.base.Function;
@ -28,11 +28,9 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.EntitySubtypeEntity; import org.thingsboard.server.dao.model.EntitySubtypeEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.model.nosql.DeviceEntity; import org.thingsboard.server.dao.model.nosql.DeviceEntity;
import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao; import org.thingsboard.server.dao.nosql.CassandraAbstractSearchTextDao;
import org.thingsboard.server.dao.util.NoSqlDao; import org.thingsboard.server.dao.util.NoSqlDao;
@ -159,7 +157,7 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
if (result != null) { if (result != null) {
List<EntitySubtype> entitySubtypes = new ArrayList<>(); List<EntitySubtype> entitySubtypes = new ArrayList<>();
result.all().forEach((entitySubtypeEntity) -> result.all().forEach((entitySubtypeEntity) ->
entitySubtypes.add(entitySubtypeEntity.toEntitySubtype()) entitySubtypes.add(entitySubtypeEntity.toEntitySubtype())
); );
return entitySubtypes; return entitySubtypes;
} else { } else {
@ -169,68 +167,4 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
}); });
} }
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery) {
log.debug("Try to find [{}] devices by tenantId [{}]", statusQuery.getStatus(), tenantId);
Select select = select().from(DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME).allowFiltering();
Select.Where query = select.where();
query.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId));
Clause clause = statusClause(statusQuery);
query.and(clause);
return findListByStatementAsync(query);
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery) {
log.debug("Try to find [{}] devices by tenantId [{}] and type [{}]", statusQuery.getStatus(), tenantId, type);
Select select = select().from(DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME).allowFiltering();
Select.Where query = select.where()
.and(eq(DEVICE_TENANT_ID_PROPERTY, tenantId))
.and(eq(DEVICE_TYPE_PROPERTY, type));
query.and(statusClause(statusQuery));
return findListByStatementAsync(query);
}
@Override
public void saveDeviceStatus(Device device) {
PreparedStatement statement = prepare("insert into " +
"device (id, tenant_id, customer_id, type, last_connect, last_update) values (?, ?, ?, ?, ?, ?)");
BoundStatement boundStatement = statement.bind(device.getUuidId(), device.getTenantId().getId(), device.getCustomerId().getId(),
device.getType(), device.getLastConnectTs(), device.getLastUpdateTs());
ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement);
Futures.withFallback(resultSetFuture, t -> {
log.error("Can't update device status for [{}]", device, t);
throw new IllegalArgumentException("Can't update device status for {" + device + "}", t);
});
}
private String getStatusProperty(DeviceStatusQuery statusQuery) {
switch (statusQuery.getContactType()) {
case UPLOAD:
return DEVICE_LAST_UPDATE_PROPERTY;
case CONNECT:
return DEVICE_LAST_CONNECT_PROPERTY;
}
return null;
}
private Clause statusClause(DeviceStatusQuery statusQuery) {
long minTime = System.currentTimeMillis() - statusQuery.getThreshold();
String statusProperty = getStatusProperty(statusQuery);
if (statusProperty != null) {
switch (statusQuery.getStatus()) {
case ONLINE:
return gt(statusProperty, minTime);
case OFFLINE:
return lt(statusProperty, minTime);
}
}
log.error("Could not build status query from [{}]", statusQuery);
throw new IllegalStateException("Could not build status query for device []");
}
} }

49
dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java

@ -18,7 +18,6 @@ package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.Dao;
@ -28,6 +27,7 @@ import java.util.UUID;
/** /**
* The Interface DeviceDao. * The Interface DeviceDao.
*
*/ */
public interface DeviceDao extends Dao<Device> { public interface DeviceDao extends Dao<Device> {
@ -52,7 +52,7 @@ public interface DeviceDao extends Dao<Device> {
* Find devices by tenantId, type and page link. * Find devices by tenantId, type and page link.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param type the type * @param type the type
* @param pageLink the page link * @param pageLink the page link
* @return the list of device objects * @return the list of device objects
*/ */
@ -61,7 +61,7 @@ public interface DeviceDao extends Dao<Device> {
/** /**
* Find devices by tenantId and devices Ids. * Find devices by tenantId and devices Ids.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param deviceIds the device Ids * @param deviceIds the device Ids
* @return the list of device objects * @return the list of device objects
*/ */
@ -70,9 +70,9 @@ public interface DeviceDao extends Dao<Device> {
/** /**
* Find devices by tenantId, customerId and page link. * Find devices by tenantId, customerId and page link.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param customerId the customerId * @param customerId the customerId
* @param pageLink the page link * @param pageLink the page link
* @return the list of device objects * @return the list of device objects
*/ */
List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink); List<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
@ -80,10 +80,10 @@ public interface DeviceDao extends Dao<Device> {
/** /**
* Find devices by tenantId, customerId, type and page link. * Find devices by tenantId, customerId, type and page link.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param customerId the customerId * @param customerId the customerId
* @param type the type * @param type the type
* @param pageLink the page link * @param pageLink the page link
* @return the list of device objects * @return the list of device objects
*/ */
List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink); List<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
@ -92,9 +92,9 @@ public interface DeviceDao extends Dao<Device> {
/** /**
* Find devices by tenantId, customerId and devices Ids. * Find devices by tenantId, customerId and devices Ids.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param customerId the customerId * @param customerId the customerId
* @param deviceIds the device Ids * @param deviceIds the device Ids
* @return the list of device objects * @return the list of device objects
*/ */
ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds); ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds);
@ -103,7 +103,7 @@ public interface DeviceDao extends Dao<Device> {
* Find devices by tenantId and device name. * Find devices by tenantId and device name.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param name the device name * @param name the device name
* @return the optional device object * @return the optional device object
*/ */
Optional<Device> findDeviceByTenantIdAndName(UUID tenantId, String name); Optional<Device> findDeviceByTenantIdAndName(UUID tenantId, String name);
@ -114,31 +114,4 @@ public interface DeviceDao extends Dao<Device> {
* @return the list of tenant device type objects * @return the list of tenant device type objects
*/ */
ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId); ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId);
/**
* Find devices by tenantId, statusQuery and page link.
*
* @param tenantId the tenantId
* @param statusQuery the page link
* @return the list of device objects
*/
ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery);
/**
* Find devices by tenantId, type, statusQuery and page link.
*
* @param tenantId the tenantId
* @param type the type
* @param statusQuery the page link
* @return the list of device objects
*/
ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery);
/**
* Update device last contact and update timestamp async
*
* @param device the device object
*/
void saveDeviceStatus(Device device);
} }

34
dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineService.java

@ -1,34 +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.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import java.util.List;
import java.util.UUID;
public interface DeviceOfflineService {
void online(Device device, boolean isUpdate);
void offline(Device device);
ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold);
ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold);
}

62
dao/src/main/java/org/thingsboard/server/dao/device/DeviceOfflineServiceImpl.java

@ -1,62 +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.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.common.data.device.DeviceStatusQuery.Status.OFFLINE;
import static org.thingsboard.server.common.data.device.DeviceStatusQuery.Status.ONLINE;
@Service
public class DeviceOfflineServiceImpl implements DeviceOfflineService {
@Autowired
private DeviceDao deviceDao;
@Override
public void online(Device device, boolean isUpdate) {
long current = System.currentTimeMillis();
device.setLastConnectTs(current);
if(isUpdate) {
device.setLastUpdateTs(current);
}
deviceDao.saveDeviceStatus(device);
}
@Override
public void offline(Device device) {
online(device, false);
}
@Override
public ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
DeviceStatusQuery statusQuery = new DeviceStatusQuery(OFFLINE, contactType, offlineThreshold);
return deviceDao.findDevicesByTenantIdAndStatus(tenantId, statusQuery);
}
@Override
public ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
DeviceStatusQuery statusQuery = new DeviceStatusQuery(ONLINE, contactType, offlineThreshold);
return deviceDao.findDevicesByTenantIdAndStatus(tenantId, statusQuery);
}
}

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

@ -133,8 +133,6 @@ public class ModelConstants {
public static final String DEVICE_NAME_PROPERTY = "name"; public static final String DEVICE_NAME_PROPERTY = "name";
public static final String DEVICE_TYPE_PROPERTY = "type"; public static final String DEVICE_TYPE_PROPERTY = "type";
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_LAST_CONNECT_PROPERTY = "last_connect";
public static final String DEVICE_LAST_UPDATE_PROPERTY = "last_update";
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";

28
dao/src/main/java/org/thingsboard/server/dao/model/nosql/DeviceEntity.java

@ -63,12 +63,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
@Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class) @Column(name = DEVICE_ADDITIONAL_INFO_PROPERTY, codec = JsonCodec.class)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@Column(name = DEVICE_LAST_CONNECT_PROPERTY)
private Long lastConnectTs;
@Column(name = DEVICE_LAST_UPDATE_PROPERTY)
private Long lastUpdateTs;
public DeviceEntity() { public DeviceEntity() {
super(); super();
} }
@ -86,8 +80,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
this.name = device.getName(); this.name = device.getName();
this.type = device.getType(); this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo(); this.additionalInfo = device.getAdditionalInfo();
this.lastConnectTs = device.getLastConnectTs();
this.lastUpdateTs = device.getLastUpdateTs();
} }
public UUID getId() { public UUID getId() {
@ -137,23 +129,7 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
public void setAdditionalInfo(JsonNode additionalInfo) { public void setAdditionalInfo(JsonNode additionalInfo) {
this.additionalInfo = additionalInfo; this.additionalInfo = additionalInfo;
} }
public Long getLastConnectTs() {
return lastConnectTs;
}
public void setLastConnectTs(Long lastConnectTs) {
this.lastConnectTs = lastConnectTs;
}
public Long getLastUpdateTs() {
return lastUpdateTs;
}
public void setLastUpdateTs(Long lastUpdateTs) {
this.lastUpdateTs = lastUpdateTs;
}
@Override @Override
public String getSearchTextSource() { public String getSearchTextSource() {
return getName(); return getName();
@ -181,8 +157,6 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
device.setName(name); device.setName(name);
device.setType(type); device.setType(type);
device.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
device.setLastConnectTs(lastConnectTs);
device.setLastUpdateTs(lastUpdateTs);
return device; return device;
} }

13
dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceEntity.java

@ -34,9 +34,6 @@ import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Table; import javax.persistence.Table;
import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_LAST_CONNECT_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.DEVICE_LAST_UPDATE_PROPERTY;
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Entity @Entity
@ -63,12 +60,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
@Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@Column(name = DEVICE_LAST_CONNECT_PROPERTY)
private Long lastConnectTs;
@Column(name = DEVICE_LAST_UPDATE_PROPERTY)
private Long lastUpdateTs;
public DeviceEntity() { public DeviceEntity() {
super(); super();
} }
@ -86,8 +77,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
this.name = device.getName(); this.name = device.getName();
this.type = device.getType(); this.type = device.getType();
this.additionalInfo = device.getAdditionalInfo(); this.additionalInfo = device.getAdditionalInfo();
this.lastConnectTs = device.getLastConnectTs();
this.lastUpdateTs = device.getLastUpdateTs();
} }
@Override @Override
@ -113,8 +102,6 @@ public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchT
device.setName(name); device.setName(name);
device.setType(type); device.setType(type);
device.setAdditionalInfo(additionalInfo); device.setAdditionalInfo(additionalInfo);
device.setLastConnectTs(lastConnectTs);
device.setLastUpdateTs(lastUpdateTs);
return device; return device;
} }
} }

24
dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java

@ -79,28 +79,4 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> {
List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds); List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds);
List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds); List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs > :time")
List<DeviceEntity> findConnectOnlineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs < :time")
List<DeviceEntity> findConnectOfflineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs > :time")
List<DeviceEntity> findUpdateOnlineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs < :time")
List<DeviceEntity> findUpdateOfflineByTenantId(@Param("tenantId") String tenantId, @Param("time") long time);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs > :time AND d.type = :type")
List<DeviceEntity> findConnectOnlineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastConnectTs < :time AND d.type = :type")
List<DeviceEntity> findConnectOfflineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs > :time AND d.type = :type")
List<DeviceEntity> findUpdateOnlineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
@Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId AND d.lastUpdateTs < :time AND d.type = :type")
List<DeviceEntity> findUpdateOfflineByTenantIdAndType(@Param("tenantId") String tenantId, @Param("time") long time, @Param("type") String type);
} }

71
dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java

@ -15,9 +15,7 @@
*/ */
package org.thingsboard.server.dao.sql.device; package org.thingsboard.server.dao.sql.device;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
@ -26,7 +24,6 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter; import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
@ -46,7 +43,6 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID_STR;
*/ */
@Component @Component
@SqlDao @SqlDao
@Slf4j
public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao { public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> implements DeviceDao {
@Autowired @Autowired
@ -128,73 +124,6 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId))));
} }
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdAndStatus(UUID tenantId, DeviceStatusQuery statusQuery) {
String strTenantId = fromTimeUUID(tenantId);
long minTime = System.currentTimeMillis() - statusQuery.getThreshold();
switch (statusQuery.getStatus()) {
case OFFLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOfflineByTenantId(strTenantId, minTime)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOfflineByTenantId(strTenantId, minTime)));
}
break;
}
case ONLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOnlineByTenantId(strTenantId, minTime)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOnlineByTenantId(strTenantId, minTime)));
}
break;
}
}
log.error("Could not build status query from [{}]", statusQuery);
throw new IllegalStateException("Could not build status query for device []");
}
@Override
public ListenableFuture<List<Device>> findDevicesByTenantIdTypeAndStatus(UUID tenantId, String type, DeviceStatusQuery statusQuery) {
String strTenantId = fromTimeUUID(tenantId);
long minTime = System.currentTimeMillis() - statusQuery.getThreshold();
switch (statusQuery.getStatus()) {
case OFFLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOfflineByTenantIdAndType(strTenantId, minTime, type)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOfflineByTenantIdAndType(strTenantId, minTime, type)));
}
break;
}
case ONLINE: {
switch (statusQuery.getContactType()) {
case UPLOAD:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findUpdateOnlineByTenantIdAndType(strTenantId, minTime, type)));
case CONNECT:
return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findConnectOnlineByTenantIdAndType(strTenantId, minTime, type)));
}
break;
}
}
log.error("Could not build status query from [{}]", statusQuery);
throw new IllegalStateException("Could not build status query for device []");
}
@Override
public void saveDeviceStatus(Device device) {
ListenableFuture<Device> future = service.submit(() -> save(device));
Futures.withFallback(future, t -> {
log.error("Can't update device status for [{}]", device, t);
throw new IllegalArgumentException("Can't update device status for {" + device + "}", t);
});
}
private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) { private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
List<EntitySubtype> list = Collections.emptyList(); List<EntitySubtype> list = Collections.emptyList();
if (types != null && !types.isEmpty()) { if (types != null && !types.isEmpty()) {

12
dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java

@ -22,7 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.queue.MsgQueue; import org.thingsboard.server.dao.queue.MsgQueue;
@ -30,12 +29,7 @@ import org.thingsboard.server.dao.util.SqlDao;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -72,13 +66,13 @@ public class InMemoryMsgQueue implements MsgQueue {
@Override @Override
public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition) { public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusterPartition) {
if (pendingMsgCount.get() < maxSize) { if (pendingMsgCount.incrementAndGet() < maxSize) {
return queueExecutor.submit(() -> { return queueExecutor.submit(() -> {
data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg);
pendingMsgCount.incrementAndGet();
return null; return null;
}); });
} else { } else {
pendingMsgCount.decrementAndGet();
return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!"));
} }
} }

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

@ -159,8 +159,6 @@ CREATE TABLE IF NOT EXISTS thingsboard.device (
type text, type text,
search_text text, search_text text,
additional_info text, additional_info text,
last_connect bigint,
last_update bigint,
PRIMARY KEY (id, tenant_id, customer_id, type) PRIMARY KEY (id, tenant_id, customer_id, type)
); );

4
dao/src/main/resources/sql/schema.sql

@ -118,9 +118,7 @@ CREATE TABLE IF NOT EXISTS device (
type varchar(255), type varchar(255),
name varchar(255), name varchar(255),
search_text varchar(255), search_text varchar(255),
tenant_id varchar(31), tenant_id varchar(31)
last_connect bigint,
last_update bigint
); );
CREATE TABLE IF NOT EXISTS device_credentials ( CREATE TABLE IF NOT EXISTS device_credentials (

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

@ -31,5 +31,7 @@ public class NodeDefinition {
JsonNode defaultConfiguration; JsonNode defaultConfiguration;
String[] uiResources; String[] uiResources;
String configDirective; String configDirective;
String icon;
String iconUrl;
} }

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleNode.java

@ -49,6 +49,10 @@ public @interface RuleNode {
String configDirective() default ""; String configDirective() default "";
String icon() default "";
String iconUrl() default "";
boolean customRelations() default false; boolean customRelations() default false;
} }

4
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -40,8 +40,6 @@ import java.util.Set;
*/ */
public interface TbContext { public interface TbContext {
void tellNext(TbMsg msg);
void tellNext(TbMsg msg, String relationType); void tellNext(TbMsg msg, String relationType);
void tellNext(TbMsg msg, String relationType, Throwable th); void tellNext(TbMsg msg, String relationType, Throwable th);
@ -54,8 +52,6 @@ public interface TbContext {
void tellSibling(TbMsg msg, ServerAddress address); void tellSibling(TbMsg msg, ServerAddress address);
void spawn(TbMsg msg);
void ack(TbMsg msg); void ack(TbMsg msg);
void tellError(TbMsg msg, Throwable th); void tellError(TbMsg msg, Throwable th);

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

@ -72,16 +72,6 @@
<groupId>com.google.guava</groupId> <groupId>com.google.guava</groupId>
<artifactId>guava</artifactId> <artifactId>guava</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-tools</artifactId>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>

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

@ -47,7 +47,9 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeAlarmConfig") configDirective = "tbActionNodeAlarmConfig",
icon = "notifications_active"
)
public class TbAlarmNode implements TbNode { public class TbAlarmNode implements TbNode {

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

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -29,11 +30,13 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
name = "log", name = "log",
configClazz = TbLogNodeConfiguration.class, configClazz = TbLogNodeConfiguration.class,
nodeDescription = "Log incoming messages using JS script for transformation Message into String", nodeDescription = "Log incoming messages using JS script for transformation Message into String",
nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " + nodeDetails = "Transform incoming Message with configured JS function to String and log final value into Thingsboard log file. " +
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>", "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeLogConfig") configDirective = "tbActionNodeLogConfig",
icon = "menu"
)
public class TbLogNode implements TbNode { public class TbLogNode implements TbNode {
@ -52,7 +55,7 @@ public class TbLogNode implements TbNode {
withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)), withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)),
toString -> { toString -> {
log.info(toString); log.info(toString);
ctx.tellNext(msg); ctx.tellNext(msg, SUCCESS);
}, },
t -> ctx.tellError(msg, t)); t -> ctx.tellError(msg, t));
} }

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java

@ -38,13 +38,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "aws sns", name = "aws sns",
configClazz = TbSnsNodeConfiguration.class, configClazz = TbSnsNodeConfiguration.class,
nodeDescription = "Publish messages to AWS SNS", nodeDescription = "Publish messages to AWS SNS",
nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.", nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeSnsConfig" configDirective = "tbActionNodeSnsConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
) )
public class TbSnsNode implements TbNode { public class TbSnsNode implements TbNode {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java

@ -41,13 +41,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "aws sqs", name = "aws sqs",
configClazz = TbSqsNodeConfiguration.class, configClazz = TbSqsNodeConfiguration.class,
nodeDescription = "Publish messages to AWS SQS", nodeDescription = "Publish messages to AWS SQS",
nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.", nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeSqsConfig" configDirective = "tbActionNodeSqsConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgd2lkdGg9IjQ4IiBoZWlnaHQ9IjQ4Ij48cGF0aCBkPSJNMTMuMjMgMTAuNTZWMTBjLTEuOTQgMC0zLjk5LjM5LTMuOTkgMi42NyAwIDEuMTYuNjEgMS45NSAxLjYzIDEuOTUuNzYgMCAxLjQzLS40NyAxLjg2LTEuMjIuNTItLjkzLjUtMS44LjUtMi44NG0yLjcgNi41M2MtLjE4LjE2LS40My4xNy0uNjMuMDYtLjg5LS43NC0xLjA1LTEuMDgtMS41NC0xLjc5LTEuNDcgMS41LTIuNTEgMS45NS00LjQyIDEuOTUtMi4yNSAwLTQuMDEtMS4zOS00LjAxLTQuMTcgMC0yLjE4IDEuMTctMy42NCAyLjg2LTQuMzggMS40Ni0uNjQgMy40OS0uNzYgNS4wNC0uOTNWNy41YzAtLjY2LjA1LTEuNDEtLjMzLTEuOTYtLjMyLS40OS0uOTUtLjctMS41LS43LTEuMDIgMC0xLjkzLjUzLTIuMTUgMS42MS0uMDUuMjQtLjI1LjQ4LS40Ny40OWwtMi42LS4yOGMtLjIyLS4wNS0uNDYtLjIyLS40LS41Ni42LTMuMTUgMy40NS00LjEgNi00LjEgMS4zIDAgMyAuMzUgNC4wMyAxLjMzQzE3LjExIDQuNTUgMTcgNi4xOCAxNyA3Ljk1djQuMTdjMCAxLjI1LjUgMS44MSAxIDIuNDguMTcuMjUuMjEuNTQgMCAuNzFsLTIuMDYgMS43OGgtLjAxIj48L3BhdGg+PHBhdGggZD0iTTIwLjE2IDE5LjU0QzE4IDIxLjE0IDE0LjgyIDIyIDEyLjEgMjJjLTMuODEgMC03LjI1LTEuNDEtOS44NS0zLjc2LS4yLS4xOC0uMDItLjQzLjI1LS4yOSAyLjc4IDEuNjMgNi4yNSAyLjYxIDkuODMgMi42MSAyLjQxIDAgNS4wNy0uNSA3LjUxLTEuNTMuMzctLjE2LjY2LjI0LjMyLjUxIj48L3BhdGg+PHBhdGggZD0iTTIxLjA3IDE4LjVjLS4yOC0uMzYtMS44NS0uMTctMi41Ny0uMDgtLjE5LjAyLS4yMi0uMTYtLjAzLS4zIDEuMjQtLjg4IDMuMjktLjYyIDMuNTMtLjMzLjI0LjMtLjA3IDIuMzUtMS4yNCAzLjMyLS4xOC4xNi0uMzUuMDctLjI2LS4xMS4yNi0uNjcuODUtMi4xNC41Ny0yLjV6Ij48L3BhdGg+PC9zdmc+"
) )
public class TbSqsNode implements TbNode { public class TbSqsNode implements TbNode {

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

@ -30,6 +30,7 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
@ -37,10 +38,11 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
name = "generator", name = "generator",
configClazz = TbMsgGeneratorNodeConfiguration.class, configClazz = TbMsgGeneratorNodeConfiguration.class,
nodeDescription = "Periodically generates messages", nodeDescription = "Periodically generates messages",
nodeDetails = "Generates messages with configurable period. ", nodeDetails = "Generates messages with configurable period. Javascript function used fore message generation.",
inEnabled = false, inEnabled = false,
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeGeneratorConfig" configDirective = "tbActionNodeGeneratorConfig",
icon = "repeat"
) )
public class TbMsgGeneratorNode implements TbNode { public class TbMsgGeneratorNode implements TbNode {
@ -71,7 +73,7 @@ public class TbMsgGeneratorNode implements TbNode {
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) {
withCallback(generate(ctx), withCallback(generate(ctx),
m -> {ctx.tellNext(m); sentTickMsg(ctx);}, m -> {ctx.tellNext(m, SUCCESS); sentTickMsg(ctx);},
t -> {ctx.tellError(msg, t); sentTickMsg(ctx);}); t -> {ctx.tellError(msg, t); sentTickMsg(ctx);});
} }
} }

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

@ -26,7 +26,8 @@ public class TbJsFilterNodeConfiguration implements NodeConfiguration<TbJsFilter
@Override @Override
public TbJsFilterNodeConfiguration defaultConfiguration() { public TbJsFilterNodeConfiguration defaultConfiguration() {
TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration();
configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 10 && msg.bigObj.prop == 42;"); configuration.setJsScript("return msg.passed < 15 && msg.name === 'Vit' " +
"&& metadata.temp == 10 && msg.bigObj.prop == 42 && msgType === 'POST_TELEMETRY';");
return configuration; return configuration;
} }
} }

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

@ -29,6 +29,7 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@RuleNode( @RuleNode(
type = ComponentType.FILTER, type = ComponentType.FILTER,
name = "switch", customRelations = true, name = "switch", customRelations = true,
relationTypes = {},
configClazz = TbJsSwitchNodeConfiguration.class, configClazz = TbJsSwitchNodeConfiguration.class,
nodeDescription = "Route incoming Message to one or multiple output chains", nodeDescription = "Route incoming Message to one or multiple output chains",
nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " + nodeDetails = "Node executes configured JS script. Script should return array of next Chain names where Message should be routed. " +

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

@ -30,9 +30,11 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration<TbJsSwitch
public TbJsSwitchNodeConfiguration defaultConfiguration() { public TbJsSwitchNodeConfiguration defaultConfiguration() {
TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration();
configuration.setJsScript("function nextRelation(metadata, msg) {\n" + configuration.setJsScript("function nextRelation(metadata, msg) {\n" +
" return ['one','nine'];" + " return ['one','nine'];\n" +
"};\n" + "};\n" +
"\n" + "if(msgType === 'POST_TELEMETRY') {\n" +
" return ['two'];\n" +
"}\n" +
"return nextRelation(metadata, msg);"); "return nextRelation(metadata, msg);");
return configuration; return configuration;
} }

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

@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg;
configClazz = TbMsgTypeFilterNodeConfiguration.class, configClazz = TbMsgTypeFilterNodeConfiguration.class,
relationTypes = {"True", "False"}, relationTypes = {"True", "False"},
nodeDescription = "Filter incoming messages by Message Type", nodeDescription = "Filter incoming messages by Message Type",
nodeDetails = "If incoming MessageType is expected - send Message via <b>Success</b> chain, otherwise <b>Failure</b> chain is used.", nodeDetails = "If incoming MessageType is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbFilterNodeMessageTypeConfig") configDirective = "tbFilterNodeMessageTypeConfig")
public class TbMsgTypeFilterNode implements TbNode { public class TbMsgTypeFilterNode implements TbNode {

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

@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.filter;
import lombok.Data; import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -33,7 +34,10 @@ public class TbMsgTypeFilterNodeConfiguration implements NodeConfiguration<TbMsg
@Override @Override
public TbMsgTypeFilterNodeConfiguration defaultConfiguration() { public TbMsgTypeFilterNodeConfiguration defaultConfiguration() {
TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration(); TbMsgTypeFilterNodeConfiguration configuration = new TbMsgTypeFilterNodeConfiguration();
configuration.setMessageTypes(Arrays.asList("POST_ATTRIBUTES","POST_TELEMETRY","RPC_REQUEST")); configuration.setMessageTypes(Arrays.asList(
SessionMsgType.POST_ATTRIBUTES_REQUEST.name(),
SessionMsgType.POST_TELEMETRY_REQUEST.name(),
SessionMsgType.TO_SERVER_RPC_REQUEST.name()));
return configuration; return configuration;
} }
} }

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

@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
configClazz = EmptyNodeConfiguration.class, configClazz = EmptyNodeConfiguration.class,
relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"}, relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"},
nodeDescription = "Route incoming messages by Message Type", nodeDescription = "Route incoming messages by Message Type",
nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> via corresponding chain, otherwise <b>Other</b> chain is used.", nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbNodeEmptyConfig") configDirective = "tbNodeEmptyConfig")
public class TbMsgTypeSwitchNode implements TbNode { public class TbMsgTypeSwitchNode implements TbNode {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java

@ -28,13 +28,14 @@ import java.util.concurrent.ExecutionException;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "kafka", name = "kafka",
configClazz = TbKafkaNodeConfiguration.class, configClazz = TbKafkaNodeConfiguration.class,
nodeDescription = "Publish messages to Kafka server", nodeDescription = "Publish messages to Kafka server",
nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.", nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeKafkaConfig" configDirective = "tbActionNodeKafkaConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUzOCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDQxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTIwMS44MTYgMjMwLjIxNmMtMTYuMTg2IDAtMzAuNjk3IDcuMTcxLTQwLjYzNCAxOC40NjFsLTI1LjQ2My0xOC4wMjZjMi43MDMtNy40NDIgNC4yNTUtMTUuNDMzIDQuMjU1LTIzLjc5NyAwLTguMjE5LTEuNDk4LTE2LjA3Ni00LjExMi0yMy40MDhsMjUuNDA2LTE3LjgzNWM5LjkzNiAxMS4yMzMgMjQuNDA5IDE4LjM2NSA0MC41NDggMTguMzY1IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI5Ljg3OS0yNC4zMDktNTQuMTg0LTU0LjE4NC01NC4xODQtMjkuODc1IDAtNTQuMTg0IDI0LjMwNS01NC4xODQgNTQuMTg0IDAgNS4zNDguODA4IDEwLjUwNSAyLjI1OCAxNS4zODlsLTI1LjQyMyAxNy44NDRjLTEwLjYyLTEzLjE3NS0yNS45MTEtMjIuMzc0LTQzLjMzMy0yNS4xODJ2LTMwLjY0YzI0LjU0NC01LjE1NSA0My4wMzctMjYuOTYyIDQzLjAzNy01My4wMTlDMTI0LjE3MSAyNC4zMDUgOTkuODYyIDAgNjkuOTg3IDAgNDAuMTEyIDAgMTUuODAzIDI0LjMwNSAxNS44MDMgNTQuMTg0YzAgMjUuNzA4IDE4LjAxNCA0Ny4yNDYgNDIuMDY3IDUyLjc2OXYzMS4wMzhDMjUuMDQ0IDE0My43NTMgMCAxNzIuNDAxIDAgMjA2Ljg1NGMwIDM0LjYyMSAyNS4yOTIgNjMuMzc0IDU4LjM1NSA2OC45NHYzMi43NzRjLTI0LjI5OSA1LjM0MS00Mi41NTIgMjcuMDExLTQyLjU1MiA1Mi44OTQgMCAyOS44NzkgMjQuMzA5IDU0LjE4NCA1NC4xODQgNTQuMTg0IDI5Ljg3NSAwIDU0LjE4NC0yNC4zMDUgNTQuMTg0LTU0LjE4NCAwLTI1Ljg4My0xOC4yNTMtNDcuNTUzLTQyLjU1Mi01Mi44OTR2LTMyLjc3NWE2OS45NjUgNjkuOTY1IDAgMCAwIDQyLjYtMjQuNzc2bDI1LjYzMyAxOC4xNDNjLTEuNDIzIDQuODQtMi4yMiA5Ljk0Ni0yLjIyIDE1LjI0IDAgMjkuODc5IDI0LjMwOSA1NC4xODQgNTQuMTg0IDU0LjE4NCAyOS44NzUgMCA1NC4xODQtMjQuMzA1IDU0LjE4NC01NC4xODQgMC0yOS44NzktMjQuMzA5LTU0LjE4NC01NC4xODQtNTQuMTg0em0wLTEyNi42OTVjMTQuNDg3IDAgMjYuMjcgMTEuNzg4IDI2LjI3IDI2LjI3MXMtMTEuNzgzIDI2LjI3LTI2LjI3IDI2LjI3LTI2LjI3LTExLjc4Ny0yNi4yNy0yNi4yN2MwLTE0LjQ4MyAxMS43ODMtMjYuMjcxIDI2LjI3LTI2LjI3MXptLTE1OC4xLTQ5LjMzN2MwLTE0LjQ4MyAxMS43ODQtMjYuMjcgMjYuMjcxLTI2LjI3czI2LjI3IDExLjc4NyAyNi4yNyAyNi4yN2MwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3em01Mi41NDEgMzA3LjI3OGMwIDE0LjQ4My0xMS43ODMgMjYuMjctMjYuMjcgMjYuMjdzLTI2LjI3MS0xMS43ODctMjYuMjcxLTI2LjI3YzAtMTQuNDgzIDExLjc4NC0yNi4yNyAyNi4yNzEtMjYuMjdzMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3em0tMjYuMjcyLTExNy45N2MtMjAuMjA1IDAtMzYuNjQyLTE2LjQzNC0zNi42NDItMzYuNjM4IDAtMjAuMjA1IDE2LjQzNy0zNi42NDIgMzYuNjQyLTM2LjY0MiAyMC4yMDQgMCAzNi42NDEgMTYuNDM3IDM2LjY0MSAzNi42NDIgMCAyMC4yMDQtMTYuNDM3IDM2LjYzOC0zNi42NDEgMzYuNjM4em0xMzEuODMxIDY3LjE3OWMtMTQuNDg3IDAtMjYuMjctMTEuNzg4LTI2LjI3LTI2LjI3MXMxMS43ODMtMjYuMjcgMjYuMjctMjYuMjcgMjYuMjcgMTEuNzg3IDI2LjI3IDI2LjI3YzAgMTQuNDgzLTExLjc4MyAyNi4yNzEtMjYuMjcgMjYuMjcxeiIvPjwvc3ZnPg=="
) )
public class TbKafkaNode implements TbNode { public class TbKafkaNode implements TbNode {

68
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/RuleVelocityUtils.java

@ -1,68 +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.rule.engine.mail;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.RuntimeSingleton;
import org.apache.velocity.runtime.parser.ParseException;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Map;
import static org.thingsboard.server.common.msg.TbMsgDataType.JSON;
public class RuleVelocityUtils {
public static VelocityContext createContext(TbMsg msg) throws IOException {
VelocityContext context = new VelocityContext();
context.put("originator", msg.getOriginator());
context.put("type", msg.getType());
context.put("metadata", msg.getMetaData().values());
if (msg.getDataType() == JSON) {
Map map = new ObjectMapper().readValue(msg.getData(), Map.class);
context.put("msg", map);
} else {
context.put("msg", msg.getData());
}
return context;
}
public static String merge(Template template, VelocityContext context) {
StringWriter writer = new StringWriter();
template.merge(context, writer);
return writer.toString();
}
public static Template create(String source, String templateName) throws ParseException {
RuntimeServices runtimeServices = RuntimeSingleton.getRuntimeServices();
StringReader reader = new StringReader(source);
SimpleNode node = runtimeServices.parse(reader, templateName);
Template template = new Template();
template.setRuntimeServices(runtimeServices);
template.setData(node);
template.initDocument();
return template;
}
}

51
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java

@ -18,18 +18,16 @@ package org.thingsboard.rule.engine.mail;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.runtime.parser.ParseException;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.io.IOException; import java.io.IOException;
import java.util.Optional;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
@Slf4j @Slf4j
@ -41,34 +39,18 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE;
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbTransformationNodeToEmailConfig") configDirective = "tbTransformationNodeToEmailConfig",
icon = "email"
)
public class TbMsgToEmailNode implements TbNode { public class TbMsgToEmailNode implements TbNode {
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
private TbMsgToEmailNodeConfiguration config; private TbMsgToEmailNodeConfiguration config;
private Optional<Template> fromTemplate;
private Optional<Template> toTemplate;
private Optional<Template> ccTemplate;
private Optional<Template> bccTemplate;
private Optional<Template> subjectTemplate;
private Optional<Template> bodyTemplate;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class);
try {
fromTemplate = toTemplate(config.getFromTemplate(), "From Template");
toTemplate = toTemplate(config.getToTemplate(), "To Template");
ccTemplate = toTemplate(config.getCcTemplate(), "Cc Template");
bccTemplate = toTemplate(config.getBccTemplate(), "Bcc Template");
subjectTemplate = toTemplate(config.getSubjectTemplate(), "Subject Template");
bodyTemplate = toTemplate(config.getBodyTemplate(), "Body Template");
} catch (ParseException e) {
log.error("Failed to create templates based on provided configuration!", e);
throw new TbNodeException(e);
}
} }
@Override @Override
@ -76,7 +58,7 @@ public class TbMsgToEmailNode implements TbNode {
try { try {
EmailPojo email = convert(msg); EmailPojo email = convert(msg);
TbMsg emailMsg = buildEmailMsg(ctx, msg, email); TbMsg emailMsg = buildEmailMsg(ctx, msg, email);
ctx.tellNext(emailMsg); ctx.tellNext(emailMsg, SUCCESS);
} catch (Exception ex) { } catch (Exception ex) {
log.warn("Can not convert message to email " + ex.getMessage()); log.warn("Can not convert message to email " + ex.getMessage());
ctx.tellError(msg, ex); ctx.tellError(msg, ex);
@ -90,21 +72,20 @@ public class TbMsgToEmailNode implements TbNode {
private EmailPojo convert(TbMsg msg) throws IOException { private EmailPojo convert(TbMsg msg) throws IOException {
EmailPojo.EmailPojoBuilder builder = EmailPojo.builder(); EmailPojo.EmailPojoBuilder builder = EmailPojo.builder();
VelocityContext context = RuleVelocityUtils.createContext(msg); builder.from(fromTemplate(this.config.getFromTemplate(), msg.getMetaData()));
fromTemplate.ifPresent(t -> builder.from(RuleVelocityUtils.merge(t, context))); builder.to(fromTemplate(this.config.getToTemplate(), msg.getMetaData()));
toTemplate.ifPresent(t -> builder.to(RuleVelocityUtils.merge(t, context))); builder.cc(fromTemplate(this.config.getCcTemplate(), msg.getMetaData()));
ccTemplate.ifPresent(t -> builder.cc(RuleVelocityUtils.merge(t, context))); builder.bcc(fromTemplate(this.config.getBccTemplate(), msg.getMetaData()));
bccTemplate.ifPresent(t -> builder.bcc(RuleVelocityUtils.merge(t, context))); builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg.getMetaData()));
subjectTemplate.ifPresent(t -> builder.subject(RuleVelocityUtils.merge(t, context))); builder.body(fromTemplate(this.config.getBodyTemplate(), msg.getMetaData()));
bodyTemplate.ifPresent(t -> builder.body(RuleVelocityUtils.merge(t, context)));
return builder.build(); return builder.build();
} }
private Optional<Template> toTemplate(String source, String name) throws ParseException { private String fromTemplate(String template, TbMsgMetaData metaData) {
if (!StringUtils.isEmpty(source)) { if (!StringUtils.isEmpty(template)) {
return Optional.of(RuleVelocityUtils.create(source, name)); return TbNodeUtils.processPattern(template, metaData);
} else { } else {
return Optional.empty(); return null;
} }
} }

7
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java

@ -32,10 +32,9 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration {
public TbMsgToEmailNodeConfiguration defaultConfiguration() { public TbMsgToEmailNodeConfiguration defaultConfiguration() {
TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration(); TbMsgToEmailNodeConfiguration configuration = new TbMsgToEmailNodeConfiguration();
configuration.fromTemplate = "info@testmail.org"; configuration.fromTemplate = "info@testmail.org";
configuration.toTemplate = "$metadata.userEmail"; configuration.toTemplate = "${userEmail}";
configuration.subjectTemplate = "Device $deviceType temperature high"; configuration.subjectTemplate = "Device ${deviceType} temperature high";
configuration.bodyTemplate = "Device $metadata.deviceName has high temperature $msg.temp"; configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}";
return configuration; return configuration;
} }
} }

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

@ -15,37 +15,57 @@
*/ */
package org.thingsboard.rule.engine.mail; package org.thingsboard.rule.engine.mail;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import javax.mail.internet.MimeMessage;
import java.io.IOException; import java.io.IOException;
import java.util.Properties;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "send email", name = "send email",
configClazz = TbSendEmailNodeConfiguration.class, configClazz = TbSendEmailNodeConfiguration.class,
nodeDescription = "Log incoming messages using JS script for transformation Message into String", nodeDescription = "Log incoming messages using JS script for transformation Message into String",
nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " + nodeDetails = "Transform incoming Message with configured JS condition to String and log final value. " +
"Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" + "Message payload can be accessed via <code>msg</code> property. For example <code>'temperature = ' + msg.temperature ;</code>" +
"Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>") "Message metadata can be accessed via <code>metadata</code> property. For example <code>'name = ' + metadata.customerName;</code>",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeSendEmailConfig",
icon = "send"
)
public class TbSendEmailNode implements TbNode { public class TbSendEmailNode implements TbNode {
private static final String MAIL_PROP = "mail.";
static final String SEND_EMAIL_TYPE = "SEND_EMAIL"; static final String SEND_EMAIL_TYPE = "SEND_EMAIL";
private static final ObjectMapper MAPPER = new ObjectMapper(); private static final ObjectMapper MAPPER = new ObjectMapper();
private TbSendEmailNodeConfiguration config; private TbSendEmailNodeConfiguration config;
private JavaMailSenderImpl mailSender;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class); try {
this.config = TbNodeUtils.convert(configuration, TbSendEmailNodeConfiguration.class);
if (!this.config.isUseSystemSmtpSettings()) {
mailSender = createMailSender();
}
} catch (Exception e) {
throw new TbNodeException(e);
}
} }
@Override @Override
@ -54,17 +74,37 @@ public class TbSendEmailNode implements TbNode {
validateType(msg.getType()); validateType(msg.getType());
EmailPojo email = getEmail(msg); EmailPojo email = getEmail(msg);
withCallback(ctx.getMailExecutor().executeAsync(() -> { withCallback(ctx.getMailExecutor().executeAsync(() -> {
ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(), sendEmail(ctx, email);
email.getBcc(), email.getSubject(), email.getBody());
return null; return null;
}), }),
ok -> ctx.tellNext(msg), ok -> ctx.tellNext(msg, SUCCESS),
fail -> ctx.tellError(msg, fail)); fail -> ctx.tellError(msg, fail));
} catch (Exception ex) { } catch (Exception ex) {
ctx.tellError(msg, ex); ctx.tellError(msg, ex);
} }
} }
private void sendEmail(TbContext ctx, EmailPojo email) throws Exception {
if (this.config.isUseSystemSmtpSettings()) {
ctx.getMailService().send(email.getFrom(), email.getTo(), email.getCc(),
email.getBcc(), email.getSubject(), email.getBody());
} else {
MimeMessage mailMsg = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8");
helper.setFrom(email.getFrom());
helper.setTo(email.getTo().split("\\s*,\\s*"));
if (!StringUtils.isBlank(email.getCc())) {
helper.setCc(email.getCc().split("\\s*,\\s*"));
}
if (!StringUtils.isBlank(email.getBcc())) {
helper.setBcc(email.getBcc().split("\\s*,\\s*"));
}
helper.setSubject(email.getSubject());
helper.setText(email.getBody());
mailSender.send(helper.getMimeMessage());
}
}
private EmailPojo getEmail(TbMsg msg) throws IOException { private EmailPojo getEmail(TbMsg msg) throws IOException {
EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class); EmailPojo email = MAPPER.readValue(msg.getData(), EmailPojo.class);
if (StringUtils.isBlank(email.getTo())) { if (StringUtils.isBlank(email.getTo())) {
@ -83,4 +123,26 @@ public class TbSendEmailNode implements TbNode {
@Override @Override
public void destroy() { public void destroy() {
} }
private JavaMailSenderImpl createMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(this.config.getSmtpHost());
mailSender.setPort(this.config.getSmtpPort());
mailSender.setUsername(this.config.getUsername());
mailSender.setPassword(this.config.getPassword());
mailSender.setJavaMailProperties(createJavaMailProperties());
return mailSender;
}
private Properties createJavaMailProperties() {
Properties javaMailProperties = new Properties();
String protocol = this.config.getSmtpProtocol();
javaMailProperties.put("mail.transport.protocol", protocol);
javaMailProperties.put(MAIL_PROP + protocol + ".host", this.config.getSmtpHost());
javaMailProperties.put(MAIL_PROP + protocol + ".port", this.config.getSmtpPort()+"");
javaMailProperties.put(MAIL_PROP + protocol + ".timeout", this.config.getTimeout()+"");
javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(this.config.getUsername())));
javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", Boolean.valueOf(this.config.isEnableTls()).toString());
return javaMailProperties;
}
} }

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

@ -21,12 +21,24 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
@Data @Data
public class TbSendEmailNodeConfiguration implements NodeConfiguration { public class TbSendEmailNodeConfiguration implements NodeConfiguration {
private String tmp; private boolean useSystemSmtpSettings;
private String smtpHost;
private int smtpPort;
private String username;
private String password;
private String smtpProtocol;
private int timeout;
private boolean enableTls;
@Override @Override
public TbSendEmailNodeConfiguration defaultConfiguration() { public TbSendEmailNodeConfiguration defaultConfiguration() {
TbSendEmailNodeConfiguration configuration = new TbSendEmailNodeConfiguration(); TbSendEmailNodeConfiguration configuration = new TbSendEmailNodeConfiguration();
configuration.tmp = ""; configuration.setUseSystemSmtpSettings(true);
configuration.setSmtpHost("localhost");
configuration.setSmtpProtocol("smtp");
configuration.setSmtpPort(25);
configuration.setTimeout(10000);
configuration.setEnableTls(false);
return configuration; return configuration;
} }
} }

28
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbEntityGetAttrNode.java

@ -18,8 +18,12 @@ package org.thingsboard.rule.engine.metadata;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*; 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.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
@ -30,8 +34,11 @@ import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@Slf4j
public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode { public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode {
private TbGetEntityAttrNodeConfiguration config; private TbGetEntityAttrNodeConfiguration config;
@ -46,17 +53,24 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode
try { try {
withCallback( withCallback(
findEntityAsync(ctx, msg.getOriginator()), findEntityAsync(ctx, msg.getOriginator()),
entityId -> withCallback( entityId -> safeGetAttributes(ctx, msg, entityId),
config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId),
attributes -> putAttributesAndTell(ctx, msg, attributes),
t -> ctx.tellError(msg, t)
),
t -> ctx.tellError(msg, t)); t -> ctx.tellError(msg, t));
} catch (Throwable th) { } catch (Throwable th) {
ctx.tellError(msg, th); ctx.tellError(msg, th);
} }
} }
private void safeGetAttributes(TbContext ctx, TbMsg msg, T entityId) {
if(entityId == null || entityId.isNullUid()) {
ctx.tellNext(msg, FAILURE);
return;
}
withCallback(config.isTelemetry() ? getLatestTelemetry(ctx, entityId) : getAttributesAsync(ctx, entityId),
attributes -> putAttributesAndTell(ctx, msg, attributes),
t -> ctx.tellError(msg, t));
}
private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) { private ListenableFuture<List<KvEntry>> getAttributesAsync(TbContext ctx, EntityId entityId) {
ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet()); ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(entityId, SERVER_SCOPE, config.getAttrMapping().keySet());
return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, ? extends List<KvEntry>>) l -> return Futures.transform(latest, (Function<? super List<AttributeKvEntry>, ? extends List<KvEntry>>) l ->
@ -75,7 +89,7 @@ public abstract class TbEntityGetAttrNode<T extends EntityId> implements TbNode
String attrName = config.getAttrMapping().get(r.getKey()); String attrName = config.getAttrMapping().get(r.getKey());
msg.getMetaData().putValue(attrName, r.getValueAsString()); msg.getMetaData().putValue(attrName, r.getValueAsString());
}); });
ctx.tellNext(msg); ctx.tellNext(msg, SUCCESS);
} }
@Override @Override

32
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNode.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import java.util.List; import java.util.List;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.server.common.data.DataConstants.*; import static org.thingsboard.server.common.data.DataConstants.*;
/** /**
@ -41,9 +42,9 @@ import static org.thingsboard.server.common.data.DataConstants.*;
configClazz = TbGetAttributesNodeConfiguration.class, configClazz = TbGetAttributesNodeConfiguration.class,
nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata", nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " + nodeDetails = "If Attributes enrichment configured, <b>CLIENT/SHARED/SERVER</b> attributes are added into Message metadata " +
"with specific prefix: <i>cs/shared/ss</i>. To access those attributes in other nodes this template can be used " + "with specific prefix: <i>cs/shared/ss</i>. Latest telemetry value added into metadata without prefix. " +
"<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> " + "To access those attributes in other nodes this template can be used " +
"If Latest Telemetry enrichment configured, latest telemetry added into metadata without prefix.", "<code>metadata.cs_temperature</code> or <code>metadata.shared_limit</code> ",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeOriginatorAttributesConfig") configDirective = "tbEnrichmentNodeOriginatorAttributesConfig")
public class TbGetAttributesNode implements TbNode { public class TbGetAttributesNode implements TbNode {
@ -57,22 +58,17 @@ public class TbGetAttributesNode implements TbNode {
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
if (CollectionUtils.isNotEmpty(config.getLatestTsKeyNames())) { ListenableFuture<List<Void>> allFutures = Futures.allAsList(
withCallback(getLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()), putLatestTelemetry(ctx, msg, config.getLatestTsKeyNames()),
i -> ctx.tellNext(msg), putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"),
t -> ctx.tellError(msg, t)); putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"),
} else { putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_")
ListenableFuture<List<Void>> future = Futures.allAsList( );
putAttrAsync(ctx, msg, CLIENT_SCOPE, config.getClientAttributeNames(), "cs_"), withCallback(allFutures, i -> ctx.tellNext(msg, SUCCESS), t -> ctx.tellError(msg, t));
putAttrAsync(ctx, msg, SHARED_SCOPE, config.getSharedAttributeNames(), "shared_"),
putAttrAsync(ctx, msg, SERVER_SCOPE, config.getServerAttributeNames(), "ss_"));
withCallback(future, i -> ctx.tellNext(msg), t -> ctx.tellError(msg, t));
}
} }
private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) { private ListenableFuture<Void> putAttrAsync(TbContext ctx, TbMsg msg, String scope, List<String> keys, String prefix) {
if (keys == null) { if (CollectionUtils.isEmpty(keys)) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys); ListenableFuture<List<AttributeKvEntry>> latest = ctx.getAttributesService().find(msg.getOriginator(), scope, keys);
@ -82,8 +78,8 @@ public class TbGetAttributesNode implements TbNode {
}); });
} }
private ListenableFuture<Void> getLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) { private ListenableFuture<Void> putLatestTelemetry(TbContext ctx, TbMsg msg, List<String> keys) {
if (keys == null) { if (CollectionUtils.isEmpty(keys)) {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys); ListenableFuture<List<TsKvEntry>> latest = ctx.getTimeseriesService().findLatest(msg.getOriginator(), keys);

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNode.java

@ -29,8 +29,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
configClazz = TbGetEntityAttrNodeConfiguration.class, configClazz = TbGetEntityAttrNodeConfiguration.class,
nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata", nodeDescription = "Add Originators Customer Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
"If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
"To access those attributes in other nodes this template can be used " + "To access those attributes in other nodes this template can be used " +
"<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", "<code>metadata.temperature</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbEnrichmentNodeCustomerAttributesConfig") configDirective = "tbEnrichmentNodeCustomerAttributesConfig")
public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> { public class TbGetCustomerAttributeNode extends TbEntityGetAttrNode<CustomerId> {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetRelatedAttributeNode.java

@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " + "If multiple Related Entities are found, only first Entity is used for attributes enrichment, other entities are discarded. " +
"If Attributes enrichment configured, server scope attributes are added into Message metadata. " + "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
"If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
"To access those attributes in other nodes this template can be used " + "To access those attributes in other nodes this template can be used " +
"<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", "<code>metadata.temperature</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbEnrichmentNodeRelatedAttributesConfig") configDirective = "tbEnrichmentNodeRelatedAttributesConfig")

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTenantAttributeNode.java

@ -31,8 +31,9 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
configClazz = TbGetEntityAttrNodeConfiguration.class, configClazz = TbGetEntityAttrNodeConfiguration.class,
nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata", nodeDescription = "Add Originators Tenant Attributes or Latest Telemetry into Message Metadata",
nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " + nodeDetails = "If Attributes enrichment configured, server scope attributes are added into Message metadata. " +
"If Latest Telemetry enrichment configured, latest telemetry added into metadata. " +
"To access those attributes in other nodes this template can be used " + "To access those attributes in other nodes this template can be used " +
"<code>metadata.temperature</code>. If Latest Telemetry enrichment configured, latest telemetry added into metadata", "<code>metadata.temperature</code>.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbEnrichmentNodeTenantAttributesConfig") configDirective = "tbEnrichmentNodeTenantAttributesConfig")
public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> { public class TbGetTenantAttributeNode extends TbEntityGetAttrNode<TenantId> {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java

@ -43,13 +43,14 @@ import java.util.concurrent.TimeoutException;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "mqtt", name = "mqtt",
configClazz = TbMqttNodeConfiguration.class, configClazz = TbMqttNodeConfiguration.class,
nodeDescription = "Publish messages to MQTT broker", nodeDescription = "Publish messages to MQTT broker",
nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.", nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeMqttConfig" configDirective = "tbActionNodeMqttConfig",
icon = "call_split"
) )
public class TbMqttNode implements TbNode { public class TbMqttNode implements TbNode {

5
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java

@ -33,13 +33,14 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "rabbitmq", name = "rabbitmq",
configClazz = TbRabbitMqNodeConfiguration.class, configClazz = TbRabbitMqNodeConfiguration.class,
nodeDescription = "Publish messages to RabbitMQ", nodeDescription = "Publish messages to RabbitMQ",
nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.", nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRabbitMqConfig" configDirective = "tbActionNodeRabbitMqConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZlcnNpb249IjEuMSIgeT0iMHB4IiB4PSIwcHgiIHZpZXdCb3g9IjAgMCAxMDAwIDEwMDAiPjxwYXRoIHN0cm9rZS13aWR0aD0iLjg0OTU2IiBkPSJtODYwLjQ3IDQxNi4zMmgtMjYyLjAxYy0xMi45MTMgMC0yMy42MTgtMTAuNzA0LTIzLjYxOC0yMy42MTh2LTI3Mi43MWMwLTIwLjMwNS0xNi4yMjctMzYuMjc2LTM2LjI3Ni0zNi4yNzZoLTkzLjc5MmMtMjAuMzA1IDAtMzYuMjc2IDE2LjIyNy0zNi4yNzYgMzYuMjc2djI3MC44NGMtMC4yNTQ4NyAxNC4xMDMtMTEuNDY5IDI1LjU3Mi0yNS43NDIgMjUuNTcybC04NS42MzYgMC42Nzk2NWMtMTQuMTAzIDAtMjUuNTcyLTExLjQ2OS0yNS41NzItMjUuNTcybDAuNjc5NjUtMjcxLjUyYzAtMjAuMzA1LTE2LjIyNy0zNi4yNzYtMzYuMjc2LTM2LjI3NmgtOTMuNTM3Yy0yMC4zMDUgMC0zNi4yNzYgMTYuMjI3LTM2LjI3NiAzNi4yNzZ2NzYzLjg0YzAgMTguMDk2IDE0Ljc4MiAzMi40NTMgMzIuNDUzIDMyLjQ1M2g3MjIuODFjMTguMDk2IDAgMzIuNDUzLTE0Ljc4MiAzMi40NTMtMzIuNDUzdi00MzUuMzFjLTEuMTg5NC0xOC4xODEtMTUuMjkyLTMyLjE5OC0zMy4zODgtMzIuMTk4em0tMTIyLjY4IDI4Ny4wN2MwIDIzLjYxOC0xOC44NiA0Mi40NzgtNDIuNDc4IDQyLjQ3OGgtNzMuOTk3Yy0yMy42MTggMC00Mi40NzgtMTguODYtNDIuNDc4LTQyLjQ3OHYtNzQuMjUyYzAtMjMuNjE4IDE4Ljg2LTQyLjQ3OCA0Mi40NzgtNDIuNDc4aDczLjk5N2MyMy42MTggMCA0Mi40NzggMTguODYgNDIuNDc4IDQyLjQ3OHoiLz48L3N2Zz4="
) )
public class TbRabbitMqNode implements TbNode { public class TbRabbitMqNode implements TbNode {

9
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java

@ -26,7 +26,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.http.client.Netty4ClientHttpRequestFactory; import org.springframework.http.client.Netty4ClientHttpRequestFactory;
import org.springframework.util.concurrent.ListenableFuture; import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback; import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.client.*; import org.springframework.web.client.AsyncRestTemplate;
import org.springframework.web.client.HttpClientErrorException;
import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
@ -34,19 +35,19 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import java.util.Map;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.EXTERNAL,
name = "rest api call", name = "rest api call",
configClazz = TbRestApiCallNodeConfiguration.class, configClazz = TbRestApiCallNodeConfiguration.class,
nodeDescription = "Invoke REST API calls to external REST server", nodeDescription = "Invoke REST API calls to external REST server",
nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.", nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRestApiCallConfig" configDirective = "tbActionNodeRestApiCallConfig",
iconUrl = "data:image/svg+xml;base64,PHN2ZyBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbDpzcGFjZT0icHJlc2VydmUiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiB2ZXJzaW9uPSIxLjEiIHk9IjBweCIgeD0iMHB4Ij48ZyB0cmFuc2Zvcm09Im1hdHJpeCguOTQ5NzUgMCAwIC45NDk3NSAxNy4xMiAyNi40OTIpIj48cGF0aCBkPSJtMTY5LjExIDEwOC41NGMtOS45MDY2IDAuMDczNC0xOS4wMTQgNi41NzI0LTIyLjAxNCAxNi40NjlsLTY5Ljk5MyAyMzEuMDhjLTMuNjkwNCAxMi4xODEgMy4yODkyIDI1LjIyIDE1LjQ2OSAyOC45MSAyLjIyNTkgMC42NzQ4MSA0LjQ5NjkgMSA2LjcyODUgMSA5Ljk3MjEgMCAxOS4xNjUtNi41MTUzIDIyLjE4Mi0xNi40NjdhNi41MjI0IDYuNTIyNCAwIDAgMCAwLjAwMiAtMC4wMDJsNjkuOTktMjMxLjA3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMCAtMC4wMDJjMy42ODU1LTEyLjE4MS0zLjI4Ny0yNS4yMjUtMTUuNDcxLTI4LjkxMi0yLjI4MjUtMC42OTE0NS00LjYxMTYtMS4wMTY5LTYuODk4NC0xem04NC45ODggMGMtOS45MDQ4IDAuMDczNC0xOS4wMTggNi41Njc1LTIyLjAxOCAxNi40NjlsLTY5Ljk4NiAyMzEuMDhjLTMuNjg5OCAxMi4xNzkgMy4yODUzIDI1LjIxNyAxNS40NjUgMjguOTA4IDIuMjI5NyAwLjY3NjQ3IDQuNTAwOCAxLjAwMiA2LjczMjQgMS4wMDIgOS45NzIxIDAgMTkuMTY1LTYuNTE1MyAyMi4xODItMTYuNDY3YTYuNTIyNCA2LjUyMjQgMCAwIDAgMC4wMDIgLTAuMDAybDY5Ljk4OC0yMzEuMDdjMy42OTA4LTEyLjE4MS0zLjI4NTItMjUuMjIzLTE1LjQ2Ny0yOC45MTItMi4yODE0LTAuNjkyMzEtNC42MTA4LTEuMDE4OS02Ljg5ODQtMS4wMDJ6bS0yMTcuMjkgNDIuMjNjLTEyLjcyOS0wLjAwMDg3LTIzLjE4OCAxMC40NTYtMjMuMTg4IDIzLjE4NiAwLjAwMSAxMi43MjggMTAuNDU5IDIzLjE4NiAyMy4xODggMjMuMTg2IDEyLjcyNy0wLjAwMSAyMy4xODMtMTAuNDU5IDIzLjE4NC0yMy4xODYgMC4wMDA4NzYtMTIuNzI4LTEwLjQ1Ni0yMy4xODUtMjMuMTg0LTIzLjE4NnptMCAxNDYuNjRjLTEyLjcyNy0wLjAwMDg3LTIzLjE4NiAxMC40NTUtMjMuMTg4IDIzLjE4NC0wLjAwMDg3MyAxMi43MjkgMTAuNDU4IDIzLjE4OCAyMy4xODggMjMuMTg4IDEyLjcyOC0wLjAwMSAyMy4xODQtMTAuNDYgMjMuMTg0LTIzLjE4OC0wLjAwMS0xMi43MjYtMTAuNDU3LTIzLjE4My0yMy4xODQtMjMuMTg0em0yNzAuNzkgNDIuMjExYy0xMi43MjcgMC0yMy4xODQgMTAuNDU3LTIzLjE4NCAyMy4xODRzMTAuNDU1IDIzLjE4OCAyMy4xODQgMjMuMTg4aDE1NC45OGMxMi43MjkgMCAyMy4xODYtMTAuNDYgMjMuMTg2LTIzLjE4OCAwLjAwMS0xMi43MjgtMTAuNDU4LTIzLjE4NC0yMy4xODYtMjMuMTg0eiIgdHJhbnNmb3JtPSJtYXRyaXgoMS4wMzc2IDAgMCAxLjAzNzYgLTcuNTY3NiAtMTQuOTI1KSIgc3Ryb2tlLXdpZHRoPSIxLjI2OTMiLz48L2c+PC9zdmc+"
) )
public class TbRestApiCallNode implements TbNode { public class TbRestApiCallNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java

@ -36,7 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg;
nodeDescription = "Sends reply to the RPC call from device", nodeDescription = "Sends reply to the RPC call from device",
nodeDetails = "Expects messages with any message type. Will forward message body to the device.", nodeDetails = "Expects messages with any message type. Will forward message body to the device.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRpcReplyConfig" configDirective = "tbActionNodeRpcReplyConfig",
icon = "call_merge"
) )
public class TbSendRPCReplyNode implements TbNode { public class TbSendRPCReplyNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java

@ -43,7 +43,8 @@ import java.util.concurrent.TimeUnit;
nodeDescription = "Sends one-way RPC call to device", nodeDescription = "Sends one-way RPC call to device",
nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.", nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeRpcRequestConfig" configDirective = "tbActionNodeRpcRequestConfig",
icon = "call_made"
) )
public class TbSendRPCRequestNode implements TbNode { public class TbSendRPCRequestNode implements TbNode {

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

@ -48,7 +48,8 @@ import java.util.Set;
nodeDescription = "Saves attributes data", nodeDescription = "Saves attributes data",
nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type", nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeAttributesConfig" configDirective = "tbActionNodeAttributesConfig",
icon = "file_upload"
) )
public class TbMsgAttributesNode implements TbNode { public class TbMsgAttributesNode implements TbNode {

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java

@ -45,7 +45,8 @@ import java.util.Map;
nodeDescription = "Saves timeseries data", nodeDescription = "Saves timeseries data",
nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type", nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbActionNodeTimeseriesConfig" configDirective = "tbActionNodeTimeseriesConfig",
icon = "file_upload"
) )
public class TbMsgTimeseriesNode implements TbNode { public class TbMsgTimeseriesNode implements TbNode {

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

@ -22,6 +22,8 @@ import org.thingsboard.server.common.msg.TbMsg;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
/** /**
* Created by ashvayka on 02.04.18. * Created by ashvayka on 02.04.18.
*/ */
@ -32,7 +34,7 @@ class TelemetryNodeCallback implements FutureCallback<Void> {
@Override @Override
public void onSuccess(@Nullable Void result) { public void onSuccess(@Nullable Void result) {
ctx.tellNext(msg); ctx.tellNext(msg, SUCCESS);
} }
@Override @Override

23
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbAbstractTransformNode.java

@ -18,10 +18,15 @@ package org.thingsboard.rule.engine.transform;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*; 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.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import static org.thingsboard.rule.engine.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.DonAsynchron.withCallback;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
/** /**
* Created by ashvayka on 19.01.18. * Created by ashvayka on 19.01.18.
@ -39,20 +44,18 @@ public abstract class TbAbstractTransformNode implements TbNode {
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
withCallback(transform(ctx, msg), withCallback(transform(ctx, msg),
m -> routeMsg(ctx, m), m -> {
if (m != null) {
ctx.tellNext(m, SUCCESS);
} else {
ctx.tellNext(msg, FAILURE);
}
},
t -> ctx.tellError(msg, t)); t -> ctx.tellError(msg, t));
} }
protected abstract ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg); protected abstract ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg);
private void routeMsg(TbContext ctx, TbMsg msg) {
if (config.isStartNewChain()) {
ctx.spawn(msg);
} else {
ctx.tellNext(msg);
}
}
public void setConfig(TbTransformNodeConfiguration config) { public void setConfig(TbTransformNodeConfiguration config) {
this.config = config; this.config = config;
} }

18
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java

@ -21,7 +21,10 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.TbNodeUtils;
import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader;
import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesRelatedEntityIdAsyncLoader;
import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader; import org.thingsboard.rule.engine.util.EntitiesTenantIdAsyncLoader;
@ -34,13 +37,15 @@ import java.util.HashSet;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.TRANSFORMATION, type = ComponentType.TRANSFORMATION,
name="change originator", name = "change originator",
configClazz = TbChangeOriginatorNodeConfiguration.class, configClazz = TbChangeOriginatorNodeConfiguration.class,
nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity", nodeDescription = "Change Message Originator To Tenant/Customer/Related Entity",
nodeDetails = "Related Entity found using configured relation direction and Relation Type. " + nodeDetails = "Related Entity found using configured relation direction and Relation Type. " +
"If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ", "If multiple Related Entities are found, only first Entity is used as new Originator, other entities are discarded. ",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbTransformationNodeChangeOriginatorConfig") configDirective = "tbTransformationNodeChangeOriginatorConfig",
icon = "find_replace"
)
public class TbChangeOriginatorNode extends TbAbstractTransformNode { public class TbChangeOriginatorNode extends TbAbstractTransformNode {
protected static final String CUSTOMER_SOURCE = "CUSTOMER"; protected static final String CUSTOMER_SOURCE = "CUSTOMER";
@ -59,7 +64,12 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode {
@Override @Override
protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) { protected ListenableFuture<TbMsg> transform(TbContext ctx, TbMsg msg) {
ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator()); ListenableFuture<? extends EntityId> newOriginator = getNewOriginator(ctx, msg.getOriginator());
return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData())); return Futures.transform(newOriginator, (Function<EntityId, TbMsg>) n -> {
if (n == null || n.isNullUid()) {
return null;
}
return ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData());
});
} }
private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) { private ListenableFuture<? extends EntityId> getNewOriginator(TbContext ctx, EntityId original) {

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeConfiguration.java

@ -43,7 +43,6 @@ public class TbChangeOriginatorNodeConfiguration extends TbTransformNodeConfigur
relationsQuery.setFilters(Collections.singletonList(entityTypeFilter)); relationsQuery.setFilters(Collections.singletonList(entityTypeFilter));
configuration.setRelationsQuery(relationsQuery); configuration.setRelationsQuery(relationsQuery);
configuration.setStartNewChain(false);
return configuration; return configuration;
} }
} }

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

@ -26,7 +26,7 @@ import org.thingsboard.server.common.msg.TbMsg;
name = "script", name = "script",
configClazz = TbTransformMsgNodeConfiguration.class, configClazz = TbTransformMsgNodeConfiguration.class,
nodeDescription = "Change Message payload, Metadata or Message type using JavaScript", nodeDescription = "Change Message payload, Metadata or Message type using JavaScript",
nodeDetails = "JavaScript function receive 3 input parameters.<br/> " + nodeDetails = "JavaScript function receive 3 input parameters <br/> " +
"<code>metadata</code> - is a Message metadata.<br/>" + "<code>metadata</code> - is a Message metadata.<br/>" +
"<code>msg</code> - is a Message payload.<br/>" + "<code>msg</code> - is a Message payload.<br/>" +
"<code>msgType</code> - is a Message type.<br/>" + "<code>msgType</code> - is a Message type.<br/>" +

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java

@ -26,7 +26,6 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio
@Override @Override
public TbTransformMsgNodeConfiguration defaultConfiguration() { public TbTransformMsgNodeConfiguration defaultConfiguration() {
TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration();
configuration.setStartNewChain(false);
configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};"); configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};");
return configuration; return configuration;
} }

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformNodeConfiguration.java

@ -20,5 +20,4 @@ import lombok.Data;
@Data @Data
public class TbTransformNodeConfiguration { public class TbTransformNodeConfiguration {
private boolean startNewChain = false;
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesCustomerIdAsyncLoader.java

@ -45,6 +45,6 @@ public class EntitiesCustomerIdAsyncLoader {
private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) { private static <T extends HasCustomerId> ListenableFuture<CustomerId> getCustomerAsync(ListenableFuture<T> future) {
return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> { return Futures.transform(future, (AsyncFunction<HasCustomerId, CustomerId>) in -> {
return in != null ? Futures.immediateFuture(in.getCustomerId()) return in != null ? Futures.immediateFuture(in.getCustomerId())
: Futures.immediateFailedFuture(new IllegalStateException("Customer not found"));}); : Futures.immediateFuture(null);});
} }
} }

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesRelatedEntityIdAsyncLoader.java

@ -40,11 +40,11 @@ public class EntitiesRelatedEntityIdAsyncLoader {
if (relationsQuery.getDirection() == EntitySearchDirection.FROM) { if (relationsQuery.getDirection() == EntitySearchDirection.FROM) {
return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo()) r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getTo())
: Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); : Futures.immediateFuture(null));
} else if (relationsQuery.getDirection() == EntitySearchDirection.TO) { } else if (relationsQuery.getDirection() == EntitySearchDirection.TO) {
return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>) return Futures.transform(asyncRelation, (AsyncFunction<? super List<EntityRelation>, EntityId>)
r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom()) r -> CollectionUtils.isNotEmpty(r) ? Futures.immediateFuture(r.get(0).getFrom())
: Futures.immediateFailedFuture(new IllegalStateException("Relation not found"))); : Futures.immediateFuture(null));
} }
return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction")); return Futures.immediateFailedFuture(new IllegalStateException("Unknown direction"));
} }

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java

@ -53,6 +53,6 @@ public class EntitiesTenantIdAsyncLoader {
private static <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) { private static <T extends HasTenantId> ListenableFuture<TenantId> getTenantAsync(ListenableFuture<T> future) {
return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> { return Futures.transform(future, (AsyncFunction<HasTenantId, TenantId>) in -> {
return in != null ? Futures.immediateFuture(in.getTenantId()) return in != null ? Futures.immediateFuture(in.getTenantId())
: Futures.immediateFailedFuture(new IllegalStateException("Tenant not found"));}); : Futures.immediateFuture(null);});
} }
} }

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

File diff suppressed because one or more lines are too long

9
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java

@ -59,6 +59,9 @@ public class TbMsgToEmailNodeTest {
initWithScript(); initWithScript();
metaData.putValue("username", "oreo"); metaData.putValue("username", "oreo");
metaData.putValue("userEmail", "user@email.io"); metaData.putValue("userEmail", "user@email.io");
metaData.putValue("name", "temp");
metaData.putValue("passed", "5");
metaData.putValue("count", "100");
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
emailNode.onMsg(ctx, msg); emailNode.onMsg(ctx, msg);
@ -91,9 +94,9 @@ public class TbMsgToEmailNodeTest {
try { try {
TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration(); TbMsgToEmailNodeConfiguration config = new TbMsgToEmailNodeConfiguration();
config.setFromTemplate("test@mail.org"); config.setFromTemplate("test@mail.org");
config.setToTemplate("$metadata.userEmail"); config.setToTemplate("${userEmail}");
config.setSubjectTemplate("Hi $metadata.username there"); config.setSubjectTemplate("Hi ${username} there");
config.setBodyTemplate("$msg.name is to high. Current $msg.passed and $msg.complex.count"); config.setBodyTemplate("${name} is to high. Current ${passed} and ${count}");
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));

23
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java

@ -31,12 +31,7 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -56,6 +51,8 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.same; import static org.mockito.Matchers.same;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@ -148,7 +145,7 @@ public class TbGetCustomerAttributeNodeTest {
} }
@Test @Test
public void errorThrownIfCustomerCannotBeFound() { public void failedChainUsedIfCustomerCannotBeFound() {
UserId userId = new UserId(UUIDs.timeBased()); UserId userId = new UserId(UUIDs.timeBased());
CustomerId customerId = new CustomerId(UUIDs.timeBased()); CustomerId customerId = new CustomerId(UUIDs.timeBased());
User user = new User(); User user = new User();
@ -159,13 +156,9 @@ public class TbGetCustomerAttributeNodeTest {
when(ctx.getUserService()).thenReturn(userService); when(ctx.getUserService()).thenReturn(userService);
when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null)); when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg);
final ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class);
verify(ctx).tellError(same(msg), captor.capture());
Throwable value = captor.getValue(); node.onMsg(ctx, msg);
assertEquals(IllegalStateException.class, value.getClass()); verify(ctx).tellNext(msg, FAILURE);
assertEquals("Customer not found", value.getMessage());
assertTrue(msg.getMetaData().getData().isEmpty()); assertTrue(msg.getMetaData().getData().isEmpty());
} }
@ -252,7 +245,7 @@ public class TbGetCustomerAttributeNodeTest {
.thenReturn(Futures.immediateFuture(timeseries)); .thenReturn(Futures.immediateFuture(timeseries));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
verify(ctx).tellNext(msg); verify(ctx).tellNext(msg, SUCCESS);
assertEquals(msg.getMetaData().getValue("tempo"), "highest"); assertEquals(msg.getMetaData().getValue("tempo"), "highest");
} }
@ -264,7 +257,7 @@ public class TbGetCustomerAttributeNodeTest {
.thenReturn(Futures.immediateFuture(attributes)); .thenReturn(Futures.immediateFuture(attributes));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
verify(ctx).tellNext(msg); verify(ctx).tellNext(msg, SUCCESS);
assertEquals(msg.getMetaData().getValue("tempo"), "high"); assertEquals(msg.getMetaData().getValue("tempo"), "high");
} }
} }

17
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java

@ -40,6 +40,7 @@ import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.same; import static org.mockito.Matchers.same;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class TbChangeOriginatorNodeTest { public class TbChangeOriginatorNodeTest {
@ -54,7 +55,7 @@ public class TbChangeOriginatorNodeTest {
@Test @Test
public void originatorCanBeChangedToCustomerId() throws TbNodeException { public void originatorCanBeChangedToCustomerId() throws TbNodeException {
init(false); init();
AssetId assetId = new AssetId(UUIDs.timeBased()); AssetId assetId = new AssetId(UUIDs.timeBased());
CustomerId customerId = new CustomerId(UUIDs.timeBased()); CustomerId customerId = new CustomerId(UUIDs.timeBased());
Asset asset = new Asset(); Asset asset = new Asset();
@ -82,7 +83,7 @@ public class TbChangeOriginatorNodeTest {
@Test @Test
public void newChainCanBeStarted() throws TbNodeException { public void newChainCanBeStarted() throws TbNodeException {
init(true); init();
AssetId assetId = new AssetId(UUIDs.timeBased()); AssetId assetId = new AssetId(UUIDs.timeBased());
CustomerId customerId = new CustomerId(UUIDs.timeBased()); CustomerId customerId = new CustomerId(UUIDs.timeBased());
Asset asset = new Asset(); Asset asset = new Asset();
@ -109,7 +110,7 @@ public class TbChangeOriginatorNodeTest {
@Test @Test
public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException { public void exceptionThrownIfCannotFindNewOriginator() throws TbNodeException {
init(true); init();
AssetId assetId = new AssetId(UUIDs.timeBased()); AssetId assetId = new AssetId(UUIDs.timeBased());
CustomerId customerId = new CustomerId(UUIDs.timeBased()); CustomerId customerId = new CustomerId(UUIDs.timeBased());
Asset asset = new Asset(); Asset asset = new Asset();
@ -121,19 +122,15 @@ public class TbChangeOriginatorNodeTest {
TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L);
when(ctx.getAssetService()).thenReturn(assetService); when(ctx.getAssetService()).thenReturn(assetService);
when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong"))); when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); verify(ctx).tellNext(same(msg), same(FAILURE));
verify(ctx).tellError(same(msg), captor.capture());
Throwable value = captor.getValue();
assertEquals("wrong", value.getMessage());
} }
public void init(boolean startNewChain) throws TbNodeException { public void init() throws TbNodeException {
TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration(); TbChangeOriginatorNodeConfiguration config = new TbChangeOriginatorNodeConfiguration();
config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE); config.setOriginatorSource(TbChangeOriginatorNode.CUSTOMER_SOURCE);
config.setStartNewChain(startNewChain);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));

33
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java

@ -38,6 +38,7 @@ import java.util.concurrent.Callable;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.mockito.Matchers.same; import static org.mockito.Matchers.same;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class TbTransformMsgNodeTest { public class TbTransformMsgNodeTest {
@ -53,7 +54,7 @@ public class TbTransformMsgNodeTest {
@Test @Test
public void metadataCanBeUpdated() throws TbNodeException, ScriptException { public void metadataCanBeUpdated() throws TbNodeException, ScriptException {
initWithScript(false); initWithScript();
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7"); metaData.putValue("temp", "7");
String rawJson = "{\"passed\": 5}"; String rawJson = "{\"passed\": 5}";
@ -68,37 +69,14 @@ public class TbTransformMsgNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
verify(ctx).getJsExecutor(); verify(ctx).getJsExecutor();
ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).tellNext(captor.capture()); verify(ctx).tellNext(captor.capture(), SUCCESS);
TbMsg actualMsg = captor.getValue();
assertEquals(transformedMsg, actualMsg);
}
@Test
public void newChainCanBeStarted() throws TbNodeException, ScriptException {
initWithScript(true);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
String rawJson = "{\"passed\": 5";
RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased());
RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L);
TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L);
mockJsExecutor();
when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg);
node.onMsg(ctx, msg);
verify(ctx).getJsExecutor();
ArgumentCaptor<TbMsg> captor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx).spawn(captor.capture());
TbMsg actualMsg = captor.getValue(); TbMsg actualMsg = captor.getValue();
assertEquals(transformedMsg, actualMsg); assertEquals(transformedMsg, actualMsg);
} }
@Test @Test
public void exceptionHandledCorrectly() throws TbNodeException, ScriptException { public void exceptionHandledCorrectly() throws TbNodeException, ScriptException {
initWithScript(false); initWithScript();
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7"); metaData.putValue("temp", "7");
String rawJson = "{\"passed\": 5"; String rawJson = "{\"passed\": 5";
@ -113,10 +91,9 @@ public class TbTransformMsgNodeTest {
verifyError(msg, "error", IllegalStateException.class); verifyError(msg, "error", IllegalStateException.class);
} }
private void initWithScript(boolean startChain) throws TbNodeException { private void initWithScript() throws TbNodeException {
TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration(); TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration();
config.setJsScript("scr"); config.setJsScript("scr");
config.setStartNewChain(startChain);
ObjectMapper mapper = new ObjectMapper(); ObjectMapper mapper = new ObjectMapper();
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config));

8
transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -35,7 +35,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy; import org.thingsboard.server.transport.coap.session.CoapExchangeObserverProxy;
import org.thingsboard.server.transport.coap.session.CoapSessionCtx; import org.thingsboard.server.transport.coap.session.CoapSessionCtx;
@ -54,17 +53,15 @@ public class CoapTransportResource extends CoapResource {
private final SessionMsgProcessor processor; private final SessionMsgProcessor processor;
private final DeviceAuthService authService; private final DeviceAuthService authService;
private final QuotaService quotaService; private final QuotaService quotaService;
private final DeviceOfflineService offlineService;
private final Field observerField; private final Field observerField;
private final long timeout; private final long timeout;
public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name, public CoapTransportResource(SessionMsgProcessor processor, DeviceAuthService authService, CoapTransportAdaptor adaptor, String name,
long timeout, QuotaService quotaService, DeviceOfflineService offlineService) { long timeout, QuotaService quotaService) {
super(name); super(name);
this.processor = processor; this.processor = processor;
this.authService = authService; this.authService = authService;
this.quotaService = quotaService; this.quotaService = quotaService;
this.offlineService = offlineService;
this.adaptor = adaptor; this.adaptor = adaptor;
this.timeout = timeout; this.timeout = timeout;
// This is important to turn off existing observable logic in // This is important to turn off existing observable logic in
@ -171,7 +168,6 @@ public class CoapTransportResource extends CoapResource {
case TO_SERVER_RPC_REQUEST: case TO_SERVER_RPC_REQUEST:
ctx.setSessionType(SessionType.SYNC); ctx.setSessionType(SessionType.SYNC);
msg = adaptor.convertToActorMsg(ctx, type, request); msg = adaptor.convertToActorMsg(ctx, type, request);
offlineService.online(ctx.getDevice(), true);
break; break;
case SUBSCRIBE_ATTRIBUTES_REQUEST: case SUBSCRIBE_ATTRIBUTES_REQUEST:
case SUBSCRIBE_RPC_COMMANDS_REQUEST: case SUBSCRIBE_RPC_COMMANDS_REQUEST:
@ -179,13 +175,11 @@ public class CoapTransportResource extends CoapResource {
advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx)); advanced.setObserver(new CoapExchangeObserverProxy(systemObserver, ctx));
ctx.setSessionType(SessionType.ASYNC); ctx.setSessionType(SessionType.ASYNC);
msg = adaptor.convertToActorMsg(ctx, type, request); msg = adaptor.convertToActorMsg(ctx, type, request);
offlineService.online(ctx.getDevice(), false);
break; break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
ctx.setSessionType(SessionType.ASYNC); ctx.setSessionType(SessionType.ASYNC);
msg = adaptor.convertToActorMsg(ctx, type, request); msg = adaptor.convertToActorMsg(ctx, type, request);
offlineService.online(ctx.getDevice(), false);
break; break;
default: default:
log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type); log.trace("[{}] Unsupported msg type: {}", ctx.getSessionId(), type);

6
transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java

@ -27,7 +27,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.SessionMsgProcessor; import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor; import org.thingsboard.server.transport.coap.adaptors.CoapTransportAdaptor;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -58,9 +57,6 @@ public class CoapTransportService {
@Autowired(required = false) @Autowired(required = false)
private QuotaService quotaService; private QuotaService quotaService;
@Autowired(required = false)
private DeviceOfflineService offlineService;
@Value("${coap.bind_address}") @Value("${coap.bind_address}")
private String host; private String host;
@ -90,7 +86,7 @@ public class CoapTransportService {
private void createResources() { private void createResources() {
CoapResource api = new CoapResource(API); CoapResource api = new CoapResource(API);
api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService, offlineService)); api.add(new CoapTransportResource(processor, authService, adaptor, V1, timeout, quotaService));
server.add(api); server.add(api);
} }

28
transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.transport.coap; package org.thingsboard.server.transport.coap;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapClient;
import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.CoapResponse;
@ -32,7 +31,6 @@ import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode; import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.DeviceStatusQuery;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -53,7 +51,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthResult; import org.thingsboard.server.common.transport.auth.DeviceAuthResult;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -140,31 +137,6 @@ public class CoapServerTest {
public static QuotaService quotaService() { public static QuotaService quotaService() {
return key -> false; return key -> false;
} }
@Bean
public static DeviceOfflineService offlineService() {
return new DeviceOfflineService() {
@Override
public void online(Device device, boolean isUpdate) {
}
@Override
public void offline(Device device) {
}
@Override
public ListenableFuture<List<Device>> findOfflineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
return null;
}
@Override
public ListenableFuture<List<Device>> findOnlineDevices(UUID tenantId, DeviceStatusQuery.ContactType contactType, long offlineThreshold) {
return null;
}
};
}
} }
@Autowired @Autowired

20
transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -26,7 +26,6 @@ import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.data.security.DeviceTokenCredentials;
import org.thingsboard.server.common.msg.core.*; import org.thingsboard.server.common.msg.core.*;
import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg;
@ -37,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.transport.http.session.HttpSessionCtx; import org.thingsboard.server.transport.http.session.HttpSessionCtx;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -65,9 +63,6 @@ public class DeviceApiController {
@Autowired(required = false) @Autowired(required = false)
private QuotaService quotaService; private QuotaService quotaService;
@Autowired(required = false)
private DeviceOfflineService offlineService;
@RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json") @RequestMapping(value = "/{deviceToken}/attributes", method = RequestMethod.GET, produces = "application/json")
public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken, public DeferredResult<ResponseEntity> getDeviceAttributes(@PathVariable("deviceToken") String deviceToken,
@RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys, @RequestParam(value = "clientKeys", required = false, defaultValue = "") String clientKeys,
@ -87,7 +82,7 @@ public class DeviceApiController {
Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null; Set<String> sharedKeySet = !StringUtils.isEmpty(sharedKeys) ? new HashSet<>(Arrays.asList(sharedKeys.split(","))) : null;
request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet); request = new BasicGetAttributesRequest(0, clientKeySet, sharedKeySet);
} }
process(ctx, request, false); process(ctx, request);
} else { } else {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.UNAUTHORIZED));
} }
@ -105,7 +100,7 @@ public class DeviceApiController {
HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
if (ctx.login(new DeviceTokenCredentials(deviceToken))) { if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try { try {
process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)), true); process(ctx, JsonConverter.convertToAttributes(new JsonParser().parse(json)));
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
@ -125,7 +120,7 @@ public class DeviceApiController {
HttpSessionCtx ctx = getHttpSessionCtx(responseWriter); HttpSessionCtx ctx = getHttpSessionCtx(responseWriter);
if (ctx.login(new DeviceTokenCredentials(deviceToken))) { if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try { try {
process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)), true); process(ctx, JsonConverter.convertToTelemetry(new JsonParser().parse(json)));
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
@ -155,7 +150,7 @@ public class DeviceApiController {
if (ctx.login(new DeviceTokenCredentials(deviceToken))) { if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try { try {
JsonObject response = new JsonParser().parse(json).getAsJsonObject(); JsonObject response = new JsonParser().parse(json).getAsJsonObject();
process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString()), true); process(ctx, new ToDeviceRpcResponseMsg(requestId, response.toString()));
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
@ -178,7 +173,7 @@ public class DeviceApiController {
JsonObject request = new JsonParser().parse(json).getAsJsonObject(); JsonObject request = new JsonParser().parse(json).getAsJsonObject();
process(ctx, new ToServerRpcRequestMsg(0, process(ctx, new ToServerRpcRequestMsg(0,
request.get("method").getAsString(), request.get("method").getAsString(),
request.get("params").toString()), true); request.get("params").toString()));
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
@ -204,7 +199,7 @@ public class DeviceApiController {
HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout); HttpSessionCtx ctx = getHttpSessionCtx(responseWriter, timeout);
if (ctx.login(new DeviceTokenCredentials(deviceToken))) { if (ctx.login(new DeviceTokenCredentials(deviceToken))) {
try { try {
process(ctx, msg, false); process(ctx, msg);
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST));
} }
@ -222,10 +217,9 @@ public class DeviceApiController {
return new HttpSessionCtx(processor, authService, responseWriter, timeout != 0 ? timeout : defaultTimeout); return new HttpSessionCtx(processor, authService, responseWriter, timeout != 0 ? timeout : defaultTimeout);
} }
private void process(HttpSessionCtx ctx, FromDeviceMsg request, boolean isUpdate) { private void process(HttpSessionCtx ctx, FromDeviceMsg request) {
AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request); AdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ctx, request);
processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg)); processor.process(new BasicToDeviceActorSessionMsg(ctx.getDevice(), msg));
offlineService.online(ctx.getDevice(), isUpdate);
} }
private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) { private boolean quotaExceeded(HttpServletRequest request, DeferredResult<ResponseEntity> responseWriter) {

25
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -37,7 +37,6 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.EncryptionUtil; import org.thingsboard.server.dao.EncryptionUtil;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@ -73,14 +72,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private final DeviceAuthService authService; private final DeviceAuthService authService;
private final RelationService relationService; private final RelationService relationService;
private final QuotaService quotaService; private final QuotaService quotaService;
private final DeviceOfflineService offlineService;
private final SslHandler sslHandler; private final SslHandler sslHandler;
private volatile boolean connected; private volatile boolean connected;
private volatile InetSocketAddress address; private volatile InetSocketAddress address;
private volatile GatewaySessionCtx gatewaySessionCtx; private volatile GatewaySessionCtx gatewaySessionCtx;
public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, public MqttTransportHandler(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService, DeviceOfflineService offlineService) { MqttTransportAdaptor adaptor, SslHandler sslHandler, QuotaService quotaService) {
this.processor = processor; this.processor = processor;
this.deviceService = deviceService; this.deviceService = deviceService;
this.relationService = relationService; this.relationService = relationService;
@ -90,7 +88,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
this.sessionId = deviceSessionCtx.getSessionId().toUidStr(); this.sessionId = deviceSessionCtx.getSessionId().toUidStr();
this.sslHandler = sslHandler; this.sslHandler = sslHandler;
this.quotaService = quotaService; this.quotaService = quotaService;
this.offlineService = offlineService;
} }
@Override @Override
@ -132,13 +129,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
case PINGREQ: case PINGREQ:
if (checkConnected(ctx)) { if (checkConnected(ctx)) {
ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
offlineService.online(deviceSessionCtx.getDevice(), false);
} }
break; break;
case DISCONNECT: case DISCONNECT:
if (checkConnected(ctx)) { if (checkConnected(ctx)) {
processDisconnect(ctx); processDisconnect(ctx);
offlineService.offline(deviceSessionCtx.getDevice());
} }
break; break;
default: default:
@ -190,28 +185,23 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
try { try {
if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) { if (topicName.equals(DEVICE_TELEMETRY_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg); msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_TELEMETRY_REQUEST, mqttMsg);
offlineService.online(deviceSessionCtx.getDevice(), true);
} else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) { } else if (topicName.equals(DEVICE_ATTRIBUTES_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg); msg = adaptor.convertToActorMsg(deviceSessionCtx, POST_ATTRIBUTES_REQUEST, mqttMsg);
offlineService.online(deviceSessionCtx.getDevice(), true);
} else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { } else if (topicName.startsWith(DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg); msg = adaptor.convertToActorMsg(deviceSessionCtx, GET_ATTRIBUTES_REQUEST, mqttMsg);
if (msgId >= 0) { if (msgId >= 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId)); ctx.writeAndFlush(createMqttPubAckMsg(msgId));
} }
offlineService.online(deviceSessionCtx.getDevice(), false);
} else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) { } else if (topicName.startsWith(DEVICE_RPC_RESPONSE_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg); msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_DEVICE_RPC_RESPONSE, mqttMsg);
if (msgId >= 0) { if (msgId >= 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId)); ctx.writeAndFlush(createMqttPubAckMsg(msgId));
} }
offlineService.online(deviceSessionCtx.getDevice(), true);
} else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) { } else if (topicName.startsWith(DEVICE_RPC_REQUESTS_TOPIC)) {
msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg); msg = adaptor.convertToActorMsg(deviceSessionCtx, TO_SERVER_RPC_REQUEST, mqttMsg);
if (msgId >= 0) { if (msgId >= 0) {
ctx.writeAndFlush(createMqttPubAckMsg(msgId)); ctx.writeAndFlush(createMqttPubAckMsg(msgId));
} }
offlineService.online(deviceSessionCtx.getDevice(), true);
} }
} catch (AdaptorException e) { } catch (AdaptorException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
@ -260,7 +250,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList)); ctx.writeAndFlush(createSubAckMessage(mqttMsg.variableHeader().messageId(), grantedQoSList));
offlineService.online(deviceSessionCtx.getDevice(), false);
} }
private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) {
@ -284,7 +273,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId())); ctx.writeAndFlush(createUnSubAckMessage(mqttMsg.variableHeader().messageId()));
offlineService.online(deviceSessionCtx.getDevice(), false);
} }
private MqttMessage createUnSubAckMessage(int msgId) { private MqttMessage createUnSubAckMessage(int msgId) {
@ -316,7 +304,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
connected = true; connected = true;
checkGatewaySession(); checkGatewaySession();
offlineService.online(deviceSessionCtx.getDevice(), false);
} }
} }
@ -328,7 +315,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
connected = true; connected = true;
checkGatewaySession(); checkGatewaySession();
offlineService.online(deviceSessionCtx.getDevice(), false);
} else { } else {
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED)); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED));
ctx.close(); ctx.close();
@ -379,9 +365,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error("[{}] Unexpected Exception", sessionId, cause); log.error("[{}] Unexpected Exception", sessionId, cause);
ctx.close(); ctx.close();
if(deviceSessionCtx.getDevice() != null) {
offlineService.offline(deviceSessionCtx.getDevice());
}
} }
private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) { private static MqttSubAckMessage createSubAckMessage(Integer msgId, List<Integer> grantedQoSList) {
@ -420,8 +403,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
if (infoNode != null) { if (infoNode != null) {
JsonNode gatewayNode = infoNode.get("gateway"); JsonNode gatewayNode = infoNode.get("gateway");
if (gatewayNode != null && gatewayNode.asBoolean()) { if (gatewayNode != null && gatewayNode.asBoolean()) {
gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService, gatewaySessionCtx = new GatewaySessionCtx(processor, deviceService, authService, relationService, deviceSessionCtx);
relationService, deviceSessionCtx, offlineService);
} }
} }
} }
@ -429,8 +411,5 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
@Override @Override
public void operationComplete(Future<? super Void> future) throws Exception { public void operationComplete(Future<? super Void> future) throws Exception {
processor.process(SessionCloseMsg.onError(deviceSessionCtx.getSessionId())); processor.process(SessionCloseMsg.onError(deviceSessionCtx.getSessionId()));
if(deviceSessionCtx.getDevice() != null) {
offlineService.offline(deviceSessionCtx.getDevice());
}
} }
} }

7
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java

@ -24,7 +24,6 @@ import io.netty.handler.ssl.SslHandler;
import org.thingsboard.server.common.transport.SessionMsgProcessor; import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@ -43,11 +42,10 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
private final MqttTransportAdaptor adaptor; private final MqttTransportAdaptor adaptor;
private final MqttSslHandlerProvider sslHandlerProvider; private final MqttSslHandlerProvider sslHandlerProvider;
private final QuotaService quotaService; private final QuotaService quotaService;
private final DeviceOfflineService offlineService;
public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, public MqttTransportServerInitializer(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService,
MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider, MqttTransportAdaptor adaptor, MqttSslHandlerProvider sslHandlerProvider,
QuotaService quotaService, DeviceOfflineService offlineService) { QuotaService quotaService) {
this.processor = processor; this.processor = processor;
this.deviceService = deviceService; this.deviceService = deviceService;
this.authService = authService; this.authService = authService;
@ -55,7 +53,6 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
this.adaptor = adaptor; this.adaptor = adaptor;
this.sslHandlerProvider = sslHandlerProvider; this.sslHandlerProvider = sslHandlerProvider;
this.quotaService = quotaService; this.quotaService = quotaService;
this.offlineService = offlineService;
} }
@Override @Override
@ -70,7 +67,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
pipeline.addLast("encoder", MqttEncoder.INSTANCE); pipeline.addLast("encoder", MqttEncoder.INSTANCE);
MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService, MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, relationService,
adaptor, sslHandler, quotaService, offlineService); adaptor, sslHandler, quotaService);
pipeline.addLast(handler); pipeline.addLast(handler);
ch.closeFuture().addListener(handler); ch.closeFuture().addListener(handler);

6
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java

@ -30,7 +30,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.SessionMsgProcessor; import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.common.transport.quota.QuotaService; import org.thingsboard.server.common.transport.quota.QuotaService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor;
@ -70,9 +69,6 @@ public class MqttTransportService {
@Autowired(required = false) @Autowired(required = false)
private QuotaService quotaService; private QuotaService quotaService;
@Autowired(required = false)
private DeviceOfflineService offlineService;
@Value("${mqtt.bind_address}") @Value("${mqtt.bind_address}")
private String host; private String host;
@Value("${mqtt.bind_port}") @Value("${mqtt.bind_port}")
@ -110,7 +106,7 @@ public class MqttTransportService {
b.group(bossGroup, workerGroup) b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) .channel(NioServerSocketChannel.class)
.childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService, .childHandler(new MqttTransportServerInitializer(processor, deviceService, authService, relationService,
adaptor, sslHandlerProvider, quotaService, offlineService)); adaptor, sslHandlerProvider, quotaService));
serverChannel = b.bind(host, port).sync().channel(); serverChannel = b.bind(host, port).sync().channel();
log.info("Mqtt transport started!"); log.info("Mqtt transport started!");

13
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java

@ -36,7 +36,6 @@ import org.thingsboard.server.common.transport.SessionMsgProcessor;
import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.auth.DeviceAuthService; import org.thingsboard.server.common.transport.auth.DeviceAuthService;
import org.thingsboard.server.dao.device.DeviceOfflineService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.transport.mqtt.MqttTransportHandler; import org.thingsboard.server.transport.mqtt.MqttTransportHandler;
@ -62,17 +61,14 @@ public class GatewaySessionCtx {
private final DeviceService deviceService; private final DeviceService deviceService;
private final DeviceAuthService authService; private final DeviceAuthService authService;
private final RelationService relationService; private final RelationService relationService;
private final DeviceOfflineService offlineService;
private final Map<String, GatewayDeviceSessionCtx> devices; private final Map<String, GatewayDeviceSessionCtx> devices;
private ChannelHandlerContext channel; private ChannelHandlerContext channel;
public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, public GatewaySessionCtx(SessionMsgProcessor processor, DeviceService deviceService, DeviceAuthService authService, RelationService relationService, DeviceSessionCtx gatewaySessionCtx) {
RelationService relationService, DeviceSessionCtx gatewaySessionCtx, DeviceOfflineService offlineService) {
this.processor = processor; this.processor = processor;
this.deviceService = deviceService; this.deviceService = deviceService;
this.authService = authService; this.authService = authService;
this.relationService = relationService; this.relationService = relationService;
this.offlineService = offlineService;
this.gateway = gatewaySessionCtx.getDevice(); this.gateway = gatewaySessionCtx.getDevice();
this.gatewaySessionId = gatewaySessionCtx.getSessionId(); this.gatewaySessionId = gatewaySessionCtx.getSessionId();
this.devices = new HashMap<>(); this.devices = new HashMap<>();
@ -102,7 +98,6 @@ public class GatewaySessionCtx {
log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName); log.debug("[{}] Added device [{}] to the gateway session", gatewaySessionId, deviceName);
processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg()))); processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new AttributesSubscribeMsg())));
processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg()))); processor.process(new BasicToDeviceActorSessionMsg(device, new BasicAdaptorToSessionActorMsg(ctx, new RpcSubscribeMsg())));
offlineService.online(device, false);
} }
} }
@ -112,7 +107,6 @@ public class GatewaySessionCtx {
if (deviceSessionCtx != null) { if (deviceSessionCtx != null) {
processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId())); processor.process(SessionCloseMsg.onDisconnect(deviceSessionCtx.getSessionId()));
deviceSessionCtx.setClosed(true); deviceSessionCtx.setClosed(true);
offlineService.offline(deviceSessionCtx.getDevice());
log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName); log.debug("[{}] Removed device [{}] from the gateway session", gatewaySessionId, deviceName);
} else { } else {
log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName); log.debug("[{}] Device [{}] was already removed from the gateway session", gatewaySessionId, deviceName);
@ -123,7 +117,6 @@ public class GatewaySessionCtx {
public void onGatewayDisconnect() { public void onGatewayDisconnect() {
devices.forEach((k, v) -> { devices.forEach((k, v) -> {
processor.process(SessionCloseMsg.onDisconnect(v.getSessionId())); processor.process(SessionCloseMsg.onDisconnect(v.getSessionId()));
offlineService.offline(v.getDevice());
}); });
} }
@ -145,7 +138,6 @@ public class GatewaySessionCtx {
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
offlineService.online(deviceSessionCtx.getDevice(), true);
} }
} else { } else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
@ -162,7 +154,6 @@ public class GatewaySessionCtx {
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data)))); new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data))));
offlineService.online(deviceSessionCtx.getDevice(), true);
} else { } else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
} }
@ -185,7 +176,6 @@ public class GatewaySessionCtx {
GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
offlineService.online(deviceSessionCtx.getDevice(), true);
} }
} else { } else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
@ -220,7 +210,6 @@ public class GatewaySessionCtx {
processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request))); new BasicAdaptorToSessionActorMsg(deviceSessionCtx, request)));
ack(msg); ack(msg);
offlineService.online(deviceSessionCtx.getDevice(), false);
} else { } else {
throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json);
} }

9
ui/src/app/common/types.constant.js

@ -485,7 +485,7 @@ export default angular.module('thingsboard.types', [])
clientSide: false clientSide: false
} }
}, },
ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION"], ruleNodeTypeComponentTypes: ["FILTER", "ENRICHMENT", "TRANSFORMATION", "ACTION", "EXTERNAL"],
ruleChainNodeComponent: { ruleChainNodeComponent: {
type: 'RULE_CHAIN', type: 'RULE_CHAIN',
name: 'rule chain', name: 'rule chain',
@ -536,6 +536,13 @@ export default angular.module('thingsboard.types', [])
nodeClass: "tb-action-type", nodeClass: "tb-action-type",
icon: "flash_on" icon: "flash_on"
}, },
EXTERNAL: {
value: "EXTERNAL",
name: "rulenode.type-external",
details: "rulenode.type-external-details",
nodeClass: "tb-external-type",
icon: "cloud_upload"
},
RULE_CHAIN: { RULE_CHAIN: {
value: "RULE_CHAIN", value: "RULE_CHAIN",
name: "rulenode.type-rule-chain", name: "rulenode.type-rule-chain",

2
ui/src/app/locale/locale.constant.js

@ -1228,6 +1228,8 @@ export default angular.module('thingsboard.locale', [])
"type-transformation-details": "Change Message payload and Metadata", "type-transformation-details": "Change Message payload and Metadata",
"type-action": "Action", "type-action": "Action",
"type-action-details": "Perform special action", "type-action-details": "Perform special action",
"type-external": "External",
"type-external-details": "Interacts with external system",
"type-rule-chain": "Rule Chain", "type-rule-chain": "Rule Chain",
"type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
"directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",

23
ui/src/app/rulechain/rulechain.controller.js

@ -246,6 +246,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var contextInfo = { var contextInfo = {
headerClass: node.nodeClass, headerClass: node.nodeClass,
icon: node.icon, icon: node.icon,
iconUrl: node.iconUrl,
title: node.name, title: node.name,
subtitle: node.component.name subtitle: node.component.name
}; };
@ -805,12 +806,21 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var ruleNodeComponent = ruleNodeComponents[i]; var ruleNodeComponent = ruleNodeComponents[i];
componentType = ruleNodeComponent.type; componentType = ruleNodeComponent.type;
var model = vm.ruleNodeTypesModel[componentType].model; var model = vm.ruleNodeTypesModel[componentType].model;
var icon = vm.types.ruleNodeType[componentType].icon;
var iconUrl = null;
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.icon) {
icon = ruleNodeComponent.configurationDescriptor.nodeDefinition.icon;
}
if (ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl) {
iconUrl = ruleNodeComponent.configurationDescriptor.nodeDefinition.iconUrl;
}
var node = { var node = {
id: 'node-lib-' + componentType + '-' + model.nodes.length, id: 'node-lib-' + componentType + '-' + model.nodes.length,
component: ruleNodeComponent, component: ruleNodeComponent,
name: '', name: '',
nodeClass: vm.types.ruleNodeType[componentType].nodeClass, nodeClass: vm.types.ruleNodeType[componentType].nodeClass,
icon: vm.types.ruleNodeType[componentType].icon, icon: icon,
iconUrl: iconUrl,
x: 30, x: 30,
y: 10+50*model.nodes.length, y: 10+50*model.nodes.length,
connectors: [] connectors: []
@ -904,6 +914,14 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var ruleNode = vm.ruleChainMetaData.nodes[i]; var ruleNode = vm.ruleChainMetaData.nodes[i];
var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type);
if (component) { if (component) {
var icon = vm.types.ruleNodeType[component.type].icon;
var iconUrl = null;
if (component.configurationDescriptor.nodeDefinition.icon) {
icon = component.configurationDescriptor.nodeDefinition.icon;
}
if (component.configurationDescriptor.nodeDefinition.iconUrl) {
iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl;
}
var node = { var node = {
id: 'rule-chain-node-' + vm.nextNodeID++, id: 'rule-chain-node-' + vm.nextNodeID++,
ruleNodeId: ruleNode.id, ruleNodeId: ruleNode.id,
@ -915,7 +933,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
component: component, component: component,
name: ruleNode.name, name: ruleNode.name,
nodeClass: vm.types.ruleNodeType[component.type].nodeClass, nodeClass: vm.types.ruleNodeType[component.type].nodeClass,
icon: vm.types.ruleNodeType[component.type].icon, icon: icon,
iconUrl: iconUrl,
connectors: [] connectors: []
}; };
if (component.configurationDescriptor.nodeDefinition.inEnabled) { if (component.configurationDescriptor.nodeDefinition.inEnabled) {

5
ui/src/app/rulechain/rulechain.scss

@ -76,7 +76,7 @@
-moz-user-select: none; -moz-user-select: none;
-ms-user-select: none; -ms-user-select: none;
user-select: none; user-select: none;
min-width: 180px; min-width: 150px;
} }
.fc-canvas { .fc-canvas {
background: #f9f9f9; background: #f9f9f9;
@ -161,6 +161,9 @@
&.tb-action-type { &.tb-action-type {
background-color: #f1928f; background-color: #f1928f;
} }
&.tb-external-type {
background-color: #fbc766;
}
&.tb-rule-chain-type { &.tb-rule-chain-type {
background-color: #d6c4f1; background-color: #d6c4f1;
} }

8
ui/src/app/rulechain/rulechain.tpl.html

@ -75,6 +75,8 @@
<md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel"> <md-expansion-panel md-component-id="{{typeId}}" id="{{typeId}}" ng-repeat="(typeId, typeModel) in vm.ruleNodeTypesModel">
<md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)" <md-expansion-panel-collapsed ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
ng-mouseleave="vm.destroyTooltips()"> ng-mouseleave="vm.destroyTooltips()">
<md-icon aria-label="node-type-icon"
class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
<div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
<md-expansion-panel-icon></md-expansion-panel-icon> <md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-collapsed> </md-expansion-panel-collapsed>
@ -82,6 +84,8 @@
<md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)" <md-expansion-panel-header ng-mouseenter="vm.typeHeaderMouseEnter($event, typeId)"
ng-mouseleave="vm.destroyTooltips()" ng-mouseleave="vm.destroyTooltips()"
ng-click="vm.$mdExpansionPanel(typeId).collapse()"> ng-click="vm.$mdExpansionPanel(typeId).collapse()">
<md-icon aria-label="node-type-icon"
class="material-icons" style="margin-right: 8px;">{{vm.types.ruleNodeType[typeId].icon}}</md-icon>
<div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div> <div class="tb-panel-title" translate>{{vm.types.ruleNodeType[typeId].name}}</div>
<md-expansion-panel-icon></md-expansion-panel-icon> <md-expansion-panel-icon></md-expansion-panel-icon>
</md-expansion-panel-header> </md-expansion-panel-header>
@ -114,8 +118,10 @@
</div> </div>
<md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()"> <md-menu-content id="tb-rule-chain-context-menu" width="4" ng-mouseleave="$mdCloseMousepointMenu()">
<div class="tb-context-menu-header {{vm.contextInfo.headerClass}}"> <div class="tb-context-menu-header {{vm.contextInfo.headerClass}}">
<md-icon aria-label="node-type-icon" <md-icon ng-if="!vm.contextInfo.iconUrl" aria-label="node-type-icon"
class="material-icons">{{vm.contextInfo.icon}}</md-icon> class="material-icons">{{vm.contextInfo.icon}}</md-icon>
<md-icon ng-if="vm.contextInfo.iconUrl" aria-label="node-type-icon"
md-svg-icon="{{vm.contextInfo.iconUrl}}"></md-icon>
<div flex> <div flex>
<div class="tb-context-menu-title">{{vm.contextInfo.title}}</div> <div class="tb-context-menu-title">{{vm.contextInfo.title}}</div>
<div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div> <div class="tb-context-menu-subtitle">{{vm.contextInfo.subtitle}}</div>

4
ui/src/app/rulechain/rulenode.tpl.html

@ -24,8 +24,10 @@
ng-mouseleave="callbacks.mouseLeave($event, node)"> ng-mouseleave="callbacks.mouseLeave($event, node)">
<div class="{{flowchartConstants.nodeOverlayClass}}"></div> <div class="{{flowchartConstants.nodeOverlayClass}}"></div>
<div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }"> <div class="tb-rule-node {{node.nodeClass}}" ng-class="{'tb-rule-node-highlighted' : node.highlighted, 'tb-rule-node-invalid': node.error }">
<md-icon aria-label="node-type-icon" flex="15" <md-icon ng-if="!node.iconUrl" aria-label="node-type-icon" flex="15"
class="material-icons">{{node.icon}}</md-icon> class="material-icons">{{node.icon}}</md-icon>
<md-icon ng-if="node.iconUrl" aria-label="node-type-icon" flex="15"
md-svg-icon="{{node.iconUrl}}"></md-icon>
<div layout="column" flex="85" layout-align="center"> <div layout="column" flex="85" layout-align="center">
<span class="tb-node-type">{{ node.component.name }}</span> <span class="tb-node-type">{{ node.component.name }}</span>
<span class="tb-node-title" ng-if="node.name">{{ node.name }}</span> <span class="tb-node-title" ng-if="node.name">{{ node.name }}</span>

23
ui/src/scss/main.scss

@ -267,6 +267,29 @@ div {
} }
} }
.tb-hint {
font-size: 12px;
line-height: 14px;
transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2);
color: grey;
padding-bottom: 15px;
&.ng-hide, &.ng-enter, &.ng-leave.ng-leave-active {
bottom: 26px;
opacity: 0;
}
&.ng-leave, &.ng-enter.ng-enter-active {
bottom: 7px;
opacity: 1;
}
}
md-input-container {
.tk-hint {
padding-top: 40px;
}
}
.md-caption { .md-caption {
&.tb-required:after { &.tb-required:after {
content: ' *'; content: ' *';

Loading…
Cancel
Save