Browse Source

Merge branch 'master' into aggregationTsDao_fix

# Conflicts:
#	dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java
pull/5905/head
van-vanich 5 years ago
parent
commit
b0529ff6c7
  1. 11
      application/pom.xml
  2. 20
      application/src/main/data/json/system/widget_bundles/cards.json
  3. 18
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  4. 2
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  5. 71
      application/src/main/data/upgrade/3.3.2/schema_update.sql
  6. 213
      application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql
  7. 41
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  8. 19
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  9. 53
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  10. 30
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  11. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  12. 95
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  13. 41
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java
  14. 49
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java
  15. 18
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  16. 14
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
  17. 4
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  18. 50
      application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java
  19. 4
      application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
  20. 12
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  21. 8
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  22. 2
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  23. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  24. 30
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  25. 2
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  26. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  27. 1
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  28. 2
      application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
  29. 34
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  30. 27
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  31. 20
      application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java
  32. 84
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  33. 71
      application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java
  34. 7
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  35. 6
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  36. 6
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  37. 2
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  38. 64
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  39. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  40. 2
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  41. 27
      application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
  42. 6
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  43. 2
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  44. 14
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  45. 187
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
  46. 32
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
  47. 14
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
  48. 5
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java
  49. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
  50. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java
  51. 11
      application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java
  52. 2
      application/src/main/java/org/thingsboard/server/service/install/DatabaseHelper.java
  53. 4
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  54. 54
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  55. 6
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  56. 84
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  57. 70
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java
  58. 24
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java
  59. 99
      application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java
  60. 2
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  61. 40
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  62. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  63. 21
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  64. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  65. 5
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java
  66. 11
      application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java
  67. 2
      application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByTenantIdTbRuleEngineSubmitStrategy.java
  68. 2
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategy.java
  69. 18
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java
  70. 191
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  71. 34
      application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java
  72. 2
      application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java
  73. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java
  74. 5
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java
  75. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java
  76. 2
      application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java
  77. 4
      application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java
  78. 2
      application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java
  79. 2
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  80. 2
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  81. 57
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  82. 24
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  83. 2
      application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java
  84. 11
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  85. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  86. 37
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  87. 4
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java
  88. 1
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  89. 36
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  90. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  91. 98
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  92. 8
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  93. 18
      application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java
  94. 103
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  95. 2
      application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java
  96. 3
      application/src/main/resources/logback.xml
  97. 125
      application/src/main/resources/thingsboard.yml
  98. 257
      application/src/test/java/org/thingsboard/server/actors/ActorSystemContextTest.java
  99. 4
      application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java
  100. 42
      application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java

11
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.3.2-SNAPSHOT</version> <version>3.3.3-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>
@ -311,8 +311,13 @@
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.hsqldb</groupId> <groupId>org.testcontainers</groupId>
<artifactId>hsqldb</artifactId> <artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

20
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

18
application/src/main/data/json/system/widget_bundles/control_widgets.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

71
application/src/main/data/upgrade/3.3.2/schema_update.sql

@ -0,0 +1,71 @@
--
-- Copyright © 2016-2021 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.
--
CREATE TABLE IF NOT EXISTS entity_alarm (
tenant_id uuid NOT NULL,
entity_type varchar(32),
entity_id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_type varchar(255) NOT NULL,
customer_id uuid,
alarm_id uuid,
CONSTRAINT entity_alarm_pkey PRIMARY KEY (entity_id, alarm_id),
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_alarm_tenant_status_created_time ON alarm(tenant_id, status, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_created_time ON entity_alarm(tenant_id, entity_id, created_time DESC);
CREATE INDEX IF NOT EXISTS idx_entity_alarm_alarm_id ON entity_alarm(alarm_id);
INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id)
SELECT tenant_id,
CASE
WHEN originator_type = 0 THEN 'TENANT'
WHEN originator_type = 1 THEN 'CUSTOMER'
WHEN originator_type = 2 THEN 'USER'
WHEN originator_type = 3 THEN 'DASHBOARD'
WHEN originator_type = 4 THEN 'ASSET'
WHEN originator_type = 5 THEN 'DEVICE'
WHEN originator_type = 6 THEN 'ALARM'
WHEN originator_type = 7 THEN 'RULE_CHAIN'
WHEN originator_type = 8 THEN 'RULE_NODE'
WHEN originator_type = 9 THEN 'ENTITY_VIEW'
WHEN originator_type = 10 THEN 'WIDGETS_BUNDLE'
WHEN originator_type = 11 THEN 'WIDGET_TYPE'
WHEN originator_type = 12 THEN 'TENANT_PROFILE'
WHEN originator_type = 13 THEN 'DEVICE_PROFILE'
WHEN originator_type = 14 THEN 'API_USAGE_STATE'
WHEN originator_type = 15 THEN 'TB_RESOURCE'
WHEN originator_type = 16 THEN 'OTA_PACKAGE'
WHEN originator_type = 17 THEN 'EDGE'
WHEN originator_type = 18 THEN 'RPC'
else 'UNKNOWN'
END,
originator_id,
created_time,
type,
customer_id,
id
FROM alarm
ON CONFLICT DO NOTHING;
INSERT INTO entity_alarm(tenant_id, entity_type, entity_id, created_time, alarm_type, customer_id, alarm_id)
SELECT a.tenant_id, r.from_type, r.from_id, created_time, type, customer_id, id
FROM alarm a
INNER JOIN relation r ON r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id
ON CONFLICT DO NOTHING;
DELETE FROM relation r WHERE r.relation_type_group = 'ALARM';

213
application/src/main/data/upgrade/3.3.2/schema_update_lwm2m_bootstrap.sql

@ -0,0 +1,213 @@
--
-- Copyright © 2016-2021 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.
--
CREATE OR REPLACE PROCEDURE update_profile_bootstrap()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE device_profile
SET profile_data = jsonb_set(
profile_data,
'{transportConfiguration}',
get_bootstrap(
profile_data::jsonb #> '{transportConfiguration}',
subquery.publickey_bs,
subquery.publickey_lw,
profile_data::json #>> '{transportConfiguration, bootstrap, bootstrapServer, securityMode}',
profile_data::json #>> '{transportConfiguration, bootstrap, lwm2mServer, securityMode}'),
true)
FROM (
SELECT id,
encode(
decode(profile_data::json #> '{transportConfiguration,bootstrap,bootstrapServer}' ->>
'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_bs,
encode(
decode(profile_data::json #> '{transportConfiguration,bootstrap,lwm2mServer}' ->>
'serverPublicKey', 'hex')::bytea, 'base64') AS publickey_lw
FROM device_profile
WHERE transport_type = 'LWM2M'
) AS subquery
WHERE device_profile.id = subquery.id
AND subquery.publickey_bs IS NOT NULL
AND subquery.publickey_lw IS NOT NULL;
END;
$$;
CREATE OR REPLACE FUNCTION get_bootstrap(transport_configuration_in jsonb, publickey_bs text,
publickey_lw text, security_mode_bs text,
security_mode_lw text) RETURNS jsonb AS
$$
DECLARE
bootstrap_new jsonb;
bootstrap_in jsonb;
BEGIN
IF security_mode_lw IS NULL THEN
security_mode_lw := 'NO_SEC';
END IF;
IF security_mode_bs IS NULL THEN
security_mode_bs := 'NO_SEC';
END IF;
bootstrap_in := transport_configuration_in::jsonb #> '{bootstrap}';
bootstrap_new := json_build_array(
json_build_object('shortServerId', bootstrap_in::json #> '{bootstrapServer}' -> 'serverId',
'securityMode', security_mode_bs,
'binding', bootstrap_in::json #> '{servers}' ->> 'binding',
'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime',
'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled',
'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod',
'host', bootstrap_in::json #> '{bootstrapServer}' ->> 'host',
'port', bootstrap_in::json #> '{bootstrapServer}' -> 'port',
'serverPublicKey', publickey_bs,
'bootstrapServerIs', true,
'clientHoldOffTime', bootstrap_in::json #> '{bootstrapServer}' -> 'clientHoldOffTime',
'bootstrapServerAccountTimeout',
bootstrap_in::json #> '{bootstrapServer}' -> 'bootstrapServerAccountTimeout'
),
json_build_object('shortServerId', bootstrap_in::json #> '{lwm2mServer}' -> 'serverId',
'securityMode', security_mode_lw,
'binding', bootstrap_in::json #> '{servers}' ->> 'binding',
'lifetime', bootstrap_in::json #> '{servers}' -> 'lifetime',
'notifIfDisabled', bootstrap_in::json #> '{servers}' -> 'notifIfDisabled',
'defaultMinPeriod', bootstrap_in::json #> '{servers}' -> 'defaultMinPeriod',
'host', bootstrap_in::json #> '{lwm2mServer}' ->> 'host',
'port', bootstrap_in::json #> '{lwm2mServer}' -> 'port',
'serverPublicKey', publickey_lw,
'bootstrapServerIs', false,
'clientHoldOffTime', bootstrap_in::json #> '{lwm2mServer}' -> 'clientHoldOffTime',
'bootstrapServerAccountTimeout',
bootstrap_in::json #> '{lwm2mServer}' -> 'bootstrapServerAccountTimeout'
)
);
RETURN jsonb_set(
transport_configuration_in,
'{bootstrap}',
bootstrap_new,
true) || '{"bootstrapServerUpdateEnable": true}';
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE update_device_credentials_to_base64_and_bootstrap()
LANGUAGE plpgsql AS
$$
BEGIN
UPDATE device_credentials
SET credentials_value = get_device_and_bootstrap(credentials_value::text)
WHERE credentials_type = 'LWM2M_CREDENTIALS';
END;
$$;
CREATE OR REPLACE FUNCTION get_device_and_bootstrap(IN credentials_value text, OUT credentials_value_new text)
LANGUAGE plpgsql AS
$$
DECLARE
client_secret_key text;
client_public_key_or_id text;
client_key_value_object jsonb;
client_bootstrap_server_value_object jsonb;
client_bootstrap_server_object jsonb;
client_bootstrap_object jsonb;
BEGIN
credentials_value_new := credentials_value;
IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'RPK' AND
NULLIF((credentials_value::jsonb #> '{client}' ->> 'key' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN
client_public_key_or_id := encode(decode(credentials_value::jsonb #> '{client}' ->> 'key', 'hex')::bytea, 'base64');
client_key_value_object := json_build_object(
'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint',
'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode',
'key', client_public_key_or_id);
credentials_value_new :=
credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb;
END IF;
IF credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode' = 'X509' AND
NULLIF((credentials_value::jsonb #> '{client}' ->> 'cert' ~ '^[0-9a-fA-F]+$')::text, 'false') = 'true' THEN
client_public_key_or_id :=
encode(decode(credentials_value::jsonb #> '{client}' ->> 'cert', 'hex')::bytea, 'base64');
client_key_value_object := json_build_object(
'endpoint', credentials_value::jsonb #> '{client}' ->> 'endpoint',
'securityConfigClientMode', credentials_value::jsonb #> '{client}' ->> 'securityConfigClientMode',
'cert', client_public_key_or_id);
credentials_value_new :=
credentials_value_new::jsonb || json_build_object('client', client_key_value_object)::jsonb;
END IF;
IF credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'RPK' OR
credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode' = 'X509' THEN
IF NULLIF((credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' AND
NULLIF(
(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' THEN
client_secret_key :=
encode(decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientSecretKey', 'hex')::bytea,
'base64');
client_public_key_or_id := encode(
decode(credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea,
'base64');
client_bootstrap_server_value_object := jsonb_build_object(
'securityMode', credentials_value::jsonb #> '{bootstrap,lwm2mServer}' ->> 'securityMode',
'clientPublicKeyOrId', client_public_key_or_id,
'clientSecretKey', client_secret_key
);
client_bootstrap_server_object := jsonb_build_object('lwm2mServer', client_bootstrap_server_value_object::jsonb);
client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb;
credentials_value_new :=
jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb;
END IF;
END IF;
IF credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'RPK' OR
credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode' = 'X509' THEN
IF NULLIF(
(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' AND
NULLIF(
(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId' ~ '^[0-9a-fA-F]+$')::text,
'false') = 'true' THEN
client_secret_key :=
encode(
decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientSecretKey', 'hex')::bytea,
'base64');
client_public_key_or_id := encode(
decode(credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'clientPublicKeyOrId', 'hex')::bytea,
'base64');
client_bootstrap_server_value_object := jsonb_build_object(
'securityMode', credentials_value::jsonb #> '{bootstrap,bootstrapServer}' ->> 'securityMode',
'clientPublicKeyOrId', client_public_key_or_id,
'clientSecretKey', client_secret_key
);
client_bootstrap_server_object :=
jsonb_build_object('bootstrapServer', client_bootstrap_server_value_object::jsonb);
client_bootstrap_object := credentials_value_new::jsonb #> '{bootstrap}' || client_bootstrap_server_object::jsonb;
credentials_value_new :=
jsonb_set(credentials_value_new::jsonb, '{bootstrap}', client_bootstrap_object::jsonb, false)::jsonb;
END IF;
END IF;
END;
$$;

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

@ -36,6 +36,7 @@ import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -82,7 +83,6 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRpcService; import org.thingsboard.server.service.rpc.TbRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
@ -95,6 +95,7 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService; import org.thingsboard.server.service.transport.TbCoreToTransportService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@ -333,30 +334,42 @@ public class ActorSystemContext {
@Getter @Getter
private long maxConcurrentSessionsPerDevice; private long maxConcurrentSessionsPerDevice;
@Value("${actors.session.sync.timeout}") @Value("${actors.session.sync.timeout:10000}")
@Getter @Getter
private long syncSessionTimeout; private long syncSessionTimeout;
@Value("${actors.rule.chain.error_persist_frequency}") @Value("${actors.rule.chain.error_persist_frequency:3000}")
@Getter @Getter
private long ruleChainErrorPersistFrequency; private long ruleChainErrorPersistFrequency;
@Value("${actors.rule.node.error_persist_frequency}") @Value("${actors.rule.node.error_persist_frequency:3000}")
@Getter @Getter
private long ruleNodeErrorPersistFrequency; private long ruleNodeErrorPersistFrequency;
@Value("${actors.statistics.enabled}") @Value("${actors.statistics.enabled:true}")
@Getter @Getter
private boolean statisticsEnabled; private boolean statisticsEnabled;
@Value("${actors.statistics.persist_frequency}") @Value("${actors.statistics.persist_frequency:3600000}")
@Getter @Getter
private long statisticsPersistFrequency; private long statisticsPersistFrequency;
@Value("${edges.enabled}") @Value("${edges.enabled:true}")
@Getter @Getter
private boolean edgesEnabled; private boolean edgesEnabled;
@Value("${cache.type:caffeine}")
@Getter
private String cacheType;
@Getter
private boolean localCacheType;
@PostConstruct
public void init() {
this.localCacheType = "caffeine".equals(cacheType);
}
@Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}") @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
public void printStats() { public void printStats() {
if (statisticsEnabled) { if (statisticsEnabled) {
@ -368,31 +381,31 @@ public class ActorSystemContext {
} }
} }
@Value("${actors.tenant.create_components_on_init}") @Value("${actors.tenant.create_components_on_init:true}")
@Getter @Getter
private boolean tenantComponentsInitEnabled; private boolean tenantComponentsInitEnabled;
@Value("${actors.rule.allow_system_mail_service}") @Value("${actors.rule.allow_system_mail_service:true}")
@Getter @Getter
private boolean allowSystemMailService; private boolean allowSystemMailService;
@Value("${actors.rule.allow_system_sms_service}") @Value("${actors.rule.allow_system_sms_service:true}")
@Getter @Getter
private boolean allowSystemSmsService; private boolean allowSystemSmsService;
@Value("${transport.sessions.inactivity_timeout}") @Value("${transport.sessions.inactivity_timeout:300000}")
@Getter @Getter
private long sessionInactivityTimeout; private long sessionInactivityTimeout;
@Value("${transport.sessions.report_timeout}") @Value("${transport.sessions.report_timeout:3000}")
@Getter @Getter
private long sessionReportTimeout; private long sessionReportTimeout;
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}") @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled:true}")
@Getter @Getter
private boolean debugPerTenantEnabled; private boolean debugPerTenantEnabled;
@Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration}") @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.configuration:50000:3600}")
@Getter @Getter
private String debugPerTenantLimitsConfiguration; private String debugPerTenantLimitsConfiguration;

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

@ -18,9 +18,12 @@ package org.thingsboard.server.actors.app;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.TbActor; import org.thingsboard.server.actors.TbActor;
import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.actors.TbActorException;
import org.thingsboard.server.actors.TbActorId; import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.device.SessionTimeoutCheckMsg;
import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.service.DefaultActorService;
@ -64,6 +67,15 @@ public class AppActor extends ContextAwareActor {
this.deletedTenants = new HashSet<>(); this.deletedTenants = new HashSet<>();
} }
@Override
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
if (systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE)) {
systemContext.schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(),
systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout());
}
}
@Override @Override
protected boolean doProcess(TbActorMsg msg) { protected boolean doProcess(TbActorMsg msg) {
if (!ruleChainsInitialized) { if (!ruleChainsInitialized) {
@ -101,6 +113,9 @@ public class AppActor extends ContextAwareActor {
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG: case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
onToTenantActorMsg((EdgeEventUpdateMsg) msg); onToTenantActorMsg((EdgeEventUpdateMsg) msg);
break; break;
case SESSION_TIMEOUT_MSG:
ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
break;
default: default:
return false; return false;
} }
@ -160,7 +175,7 @@ public class AppActor extends ContextAwareActor {
} }
} else { } else {
if (EntityType.TENANT.equals(msg.getEntityId().getEntityType())) { if (EntityType.TENANT.equals(msg.getEntityId().getEntityType())) {
TenantId tenantId = new TenantId(msg.getEntityId().getId()); TenantId tenantId = TenantId.fromUUID(msg.getEntityId().getId());
if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg);
deletedTenants.add(tenantId); deletedTenants.add(tenantId);
@ -222,7 +237,7 @@ public class AppActor extends ContextAwareActor {
@Override @Override
public TbActorId createActorId() { public TbActorId createActorId() {
return new TbEntityActorId(new TenantId(EntityId.NULL_UUID)); return new TbEntityActorId(TenantId.SYS_TENANT_ID);
} }
@Override @Override

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

@ -97,6 +97,7 @@ import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
@ -116,6 +117,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
static final String SESSION_TIMEOUT_MESSAGE = "session timeout!";
final TenantId tenantId; final TenantId tenantId;
final DeviceId deviceId; final DeviceId deviceId;
final LinkedHashMapRemoveEldest<UUID, SessionInfoMetaData> sessions; final LinkedHashMapRemoveEldest<UUID, SessionInfoMetaData> sessions;
@ -596,6 +598,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(responseMsg.getRequestId()); ToDeviceRpcRequestMetadata md = toDeviceRpcPendingMap.get(responseMsg.getRequestId());
if (md != null) { if (md != null) {
JsonNode response = null;
if (status.equals(RpcStatus.DELIVERED)) { if (status.equals(RpcStatus.DELIVERED)) {
if (md.getMsg().getMsg().isOneway()) { if (md.getMsg().getMsg().isOneway()) {
toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
@ -611,13 +614,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (maxRpcRetries <= md.getRetries()) { if (maxRpcRetries <= md.getRetries()) {
toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
status = RpcStatus.FAILED; status = RpcStatus.FAILED;
response = JacksonUtil.newObjectNode().put("error", "There was a Timeout and all retry attempts have been exhausted. Retry attempts set: " + maxRpcRetries);
} else { } else {
md.setRetries(md.getRetries() + 1); md.setRetries(md.getRetries() + 1);
} }
} }
if (md.getMsg().getMsg().isPersisted()) { if (md.getMsg().getMsg().isPersisted()) {
systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), status, null); systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), status, response);
} }
if (status != RpcStatus.SENT) { if (status != RpcStatus.SENT) {
sendNextPendingRequest(context); sendNextPendingRequest(context);
@ -871,6 +875,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
void restoreSessions() { void restoreSessions() {
if (systemContext.isLocalCacheType()) {
return;
}
log.debug("[{}] Restoring sessions from cache", deviceId); log.debug("[{}] Restoring sessions from cache", deviceId);
DeviceSessionsCacheEntry sessionsDump = null; DeviceSessionsCacheEntry sessionsDump = null;
try { try {
@ -905,6 +912,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
private void dumpSessions() { private void dumpSessions() {
if (systemContext.isLocalCacheType()) {
return;
}
log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
List<SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size()); List<SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size());
sessions.forEach((uuid, sessionMD) -> { sessions.forEach((uuid, sessionMD) -> {
@ -931,7 +941,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
void init(TbActorCtx ctx) { void init(TbActorCtx ctx) {
schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout());
PageLink pageLink = new PageLink(1024, 0, null, new SortOrder("createdTime")); PageLink pageLink = new PageLink(1024, 0, null, new SortOrder("createdTime"));
PageData<Rpc> pageData; PageData<Rpc> pageData;
do { do {
@ -953,19 +962,35 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
void checkSessionsTimeout() { void checkSessionsTimeout() {
log.debug("[{}] checkSessionsTimeout started. Size before check {}", deviceId, sessions.size()); final long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout();
long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout(); List<UUID> expiredIds = null;
Map<UUID, SessionInfoMetaData> sessionsToRemove = sessions.entrySet().stream().filter(kv -> kv.getValue().getLastActivityTime() < expTime).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
sessionsToRemove.forEach((sessionId, sessionMD) -> { for (Map.Entry<UUID, SessionInfoMetaData> kv : sessions.entrySet()) { //entry set are cached for stable sessions
sessions.remove(sessionId); if (kv.getValue().getLastActivityTime() < expTime) {
rpcSubscriptions.remove(sessionId); final UUID id = kv.getKey();
attributeSubscriptions.remove(sessionId); if (expiredIds == null) {
notifyTransportAboutClosedSession(sessionId, sessionMD, "session timeout!"); expiredIds = new ArrayList<>(1); //most of the expired sessions is a single event
}); }
if (!sessionsToRemove.isEmpty()) { expiredIds.add(id);
dumpSessions(); }
}
if (expiredIds != null) {
int removed = 0;
for (UUID id : expiredIds) {
final SessionInfoMetaData session = sessions.remove(id);
rpcSubscriptions.remove(id);
attributeSubscriptions.remove(id);
if (session != null) {
removed++;
notifyTransportAboutClosedSession(id, session, SESSION_TIMEOUT_MESSAGE);
}
}
if (removed != 0) {
dumpSessions();
}
} }
log.debug("[{}] checkSessionsTimeout finished. Size after check {}", deviceId, sessions.size());
} }
} }

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

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.rule.RuleNodeState;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.TbMsgProcessingStackItem;
import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -134,6 +135,25 @@ class DefaultTbContext implements TbContext {
scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor()); scheduleMsgWithDelay(new RuleNodeToSelfMsg(this, msg), delayMs, nodeCtx.getSelfActor());
} }
@Override
public void input(TbMsg msg, RuleChainId ruleChainId) {
msg.pushToStack(nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId());
nodeCtx.getChainActor().tell(new RuleChainInputMsg(ruleChainId, msg));
}
@Override
public void output(TbMsg msg, String relationType) {
TbMsgProcessingStackItem item = msg.popFormStack();
if (item == null) {
ack(msg);
} else {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType);
}
nodeCtx.getChainActor().tell(new RuleChainOutputMsg(item.getRuleChainId(), item.getRuleNodeId(), relationType, msg));
}
}
@Override @Override
public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer<Throwable> onFailure) { public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator());
@ -147,6 +167,11 @@ class DefaultTbContext implements TbContext {
} }
private void enqueue(TopicPartitionInfo tpi, TbMsg tbMsg, Consumer<Throwable> onFailure, Runnable onSuccess) { private void enqueue(TopicPartitionInfo tpi, TbMsg tbMsg, Consumer<Throwable> onFailure, Runnable onSuccess) {
if (!tbMsg.isValid()) {
log.trace("[{}] Skip invalid message: {}", getTenantId(), tbMsg);
onFailure.accept(new IllegalArgumentException("Source message is no longer valid!"));
return;
}
TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder() TransportProtos.ToRuleEngineMsg msg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) .setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
@ -215,6 +240,11 @@ class DefaultTbContext implements TbContext {
} }
private void enqueueForTellNext(TopicPartitionInfo tpi, String queueName, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) { private void enqueueForTellNext(TopicPartitionInfo tpi, String queueName, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
if (!source.isValid()) {
log.trace("[{}] Skip invalid message: {}", getTenantId(), source);
onFailure.accept(new IllegalArgumentException("Source message is no longer valid!"));
return;
}
RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId(); RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
RuleNodeId ruleNodeId = nodeCtx.getSelf().getId(); RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
TbMsg tbMsg = TbMsg.newMsg(source, queueName, ruleChainId, ruleNodeId); TbMsg tbMsg = TbMsg.newMsg(source, queueName, ruleChainId, ruleNodeId);

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

@ -60,6 +60,12 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
case RULE_CHAIN_TO_RULE_CHAIN_MSG: case RULE_CHAIN_TO_RULE_CHAIN_MSG:
processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg); processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
break; break;
case RULE_CHAIN_INPUT_MSG:
processor.onRuleChainInputMsg((RuleChainInputMsg) msg);
break;
case RULE_CHAIN_OUTPUT_MSG:
processor.onRuleChainOutputMsg((RuleChainOutputMsg) msg);
break;
case PARTITION_CHANGE_MSG: case PARTITION_CHANGE_MSG:
processor.onPartitionChangeMsg((PartitionChangeMsg) msg); processor.onPartitionChangeMsg((PartitionChangeMsg) msg);
break; break;

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

@ -66,6 +66,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> { public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
private static final String NA_RELATION_TYPE = "";
private final TbActorRef parent; private final TbActorRef parent;
private final TbActorRef self; private final TbActorRef self;
private final Map<RuleNodeId, RuleNodeCtx> nodeActors; private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
@ -199,38 +200,78 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
void onQueueToRuleEngineMsg(QueueToRuleEngineMsg envelope) { void onQueueToRuleEngineMsg(QueueToRuleEngineMsg envelope) {
TbMsg msg = envelope.getMsg(); TbMsg msg = envelope.getMsg();
if (!checkMsgValid(msg)) {
return;
}
log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg); log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg);
if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) { if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) {
try { onTellNext(msg, true);
checkActive(envelope.getMsg()); } else {
RuleNodeId targetId = msg.getRuleNodeId(); onTellNext(msg, envelope.getMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage());
RuleNodeCtx targetCtx; }
if (targetId == null) { }
targetCtx = firstNode;
msg = msg.copyWithRuleChainId(entityId); private void onTellNext(TbMsg msg, boolean useRuleNodeIdFromMsg) {
} else { try {
targetCtx = nodeActors.get(targetId); checkComponentStateActive(msg);
} RuleNodeId targetId = useRuleNodeIdFromMsg ? msg.getRuleNodeId() : null;
if (targetCtx != null) { RuleNodeCtx targetCtx;
log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId); if (targetId == null) {
pushMsgToNode(targetCtx, msg, ""); targetCtx = firstNode;
} else { msg = msg.copyWithRuleChainId(entityId);
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId); } else {
msg.getCallback().onSuccess(); targetCtx = nodeActors.get(targetId);
} }
} catch (RuleNodeException rne) { if (targetCtx != null) {
envelope.getMsg().getCallback().onFailure(rne); log.trace("[{}][{}] Pushing message to target rule node", entityId, targetId);
} catch (Exception e) { pushMsgToNode(targetCtx, msg, NA_RELATION_TYPE);
envelope.getMsg().getCallback().onFailure(new RuleEngineException(e.getMessage())); } else {
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess();
}
} catch (RuleNodeException rne) {
msg.getCallback().onFailure(rne);
} catch (Exception e) {
msg.getCallback().onFailure(new RuleEngineException(e.getMessage()));
}
}
public void onRuleChainInputMsg(RuleChainInputMsg envelope) {
var msg = envelope.getMsg();
if (!checkMsgValid(msg)) {
return;
}
if (entityId.equals(envelope.getRuleChainId())) {
onTellNext(envelope.getMsg(), false);
} else {
parent.tell(envelope);
}
}
public void onRuleChainOutputMsg(RuleChainOutputMsg envelope) {
var msg = envelope.getMsg();
if (!checkMsgValid(msg)) {
return;
}
if (entityId.equals(envelope.getRuleChainId())) {
var originatorNodeId = envelope.getTargetRuleNodeId();
RuleNodeCtx ruleNodeCtx = nodeActors.get(originatorNodeId);
if (ruleNodeCtx != null && ruleNodeCtx.getSelf().isDebugMode()) {
systemContext.persistDebugOutput(tenantId, originatorNodeId, envelope.getMsg(), envelope.getRelationType());
} }
onTellNext(envelope.getMsg(), originatorNodeId, Collections.singleton(envelope.getRelationType()), RuleNodeException.UNKNOWN);
} else { } else {
onTellNext(envelope.getMsg(), envelope.getMsg().getRuleNodeId(), envelope.getRelationTypes(), envelope.getFailureMessage()); parent.tell(envelope);
} }
} }
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) { void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
var msg = envelope.getMsg();
if (!checkMsgValid(msg)) {
return;
}
try { try {
checkActive(envelope.getMsg()); checkComponentStateActive(envelope.getMsg());
if (firstNode != null) { if (firstNode != null) {
pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType()); pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType());
} else { } else {
@ -242,12 +283,15 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
} }
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) { void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
onTellNext(envelope.getMsg(), envelope.getOriginator(), envelope.getRelationTypes(), envelope.getFailureMessage()); var msg = envelope.getMsg();
if (checkMsgValid(msg)) {
onTellNext(msg, envelope.getOriginator(), envelope.getRelationTypes(), envelope.getFailureMessage());
}
} }
private void onTellNext(TbMsg msg, RuleNodeId originatorNodeId, Set<String> relationTypes, String failureMessage) { private void onTellNext(TbMsg msg, RuleNodeId originatorNodeId, Set<String> relationTypes, String failureMessage) {
try { try {
checkActive(msg); checkComponentStateActive(msg);
EntityId entityId = msg.getOriginator(); EntityId entityId = msg.getOriginator();
TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, msg.getQueueName(), tenantId, entityId); TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, msg.getQueueName(), tenantId, entityId);
@ -356,4 +400,5 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null; RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;
return new RuleNodeException("Rule Chain is not active! Failed to initialize.", ruleChainName, firstRuleNode); return new RuleNodeException("Rule Chain is not active! Failed to initialize.", ruleChainName, firstRuleNode);
} }
} }

41
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainInputMsg.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainInputMsg extends TbToRuleChainActorMsg {
public RuleChainInputMsg(RuleChainId target, TbMsg tbMsg) {
super(tbMsg, target);
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_INPUT_MSG;
}
}

49
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainOutputMsg.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@EqualsAndHashCode(callSuper = true)
@ToString
public final class RuleChainOutputMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleNodeId targetRuleNodeId;
@Getter
private final String relationType;
public RuleChainOutputMsg(RuleChainId target, RuleNodeId targetRuleNodeId, String relationType, TbMsg tbMsg) {
super(tbMsg, target);
this.targetRuleNodeId = targetRuleNodeId;
this.relationType = relationType;
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_OUTPUT_MSG;
}
}

18
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java

@ -31,33 +31,19 @@ import org.thingsboard.server.common.msg.queue.RuleEngineException;
*/ */
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString @ToString
public final class RuleChainToRuleChainMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg { public final class RuleChainToRuleChainMsg extends TbToRuleChainActorMsg {
@Getter
private final RuleChainId target;
@Getter @Getter
private final RuleChainId source; private final RuleChainId source;
@Getter @Getter
private final String fromRelationType; private final String fromRelationType;
public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) { public RuleChainToRuleChainMsg(RuleChainId target, RuleChainId source, TbMsg tbMsg, String fromRelationType) {
super(tbMsg); super(tbMsg, target);
this.target = target;
this.source = source; this.source = source;
this.fromRelationType = fromRelationType; this.fromRelationType = fromRelationType;
} }
@Override
public void onTbActorStopped(TbActorStopReason reason) {
String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
msg.getCallback().onFailure(new RuleEngineException(message));
}
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override @Override
public MsgType getMsgType() { public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG; return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;

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

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
@ -86,12 +87,19 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
} }
} }
private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) { private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg envelope) {
TbMsg msg = envelope.getMsg();
if (!msg.isValid()) {
if (log.isTraceEnabled()) {
log.trace("Skip processing of message: {} because it is no longer valid!", msg);
}
return;
}
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {
log.debug("[{}][{}][{}] Going to process rule msg: {}", ruleChainId, id, processor.getComponentName(), msg.getMsg()); log.debug("[{}][{}][{}] Going to process rule engine msg: {}", ruleChainId, id, processor.getComponentName(), msg);
} }
try { try {
processor.onRuleChainToRuleNodeMsg(msg); processor.onRuleChainToRuleNodeMsg(envelope);
increaseMessagesProcessedCount(); increaseMessagesProcessedCount();
} catch (Exception e) { } catch (Exception e) {
logAndPersist("onRuleMsg", e); logAndPersist("onRuleMsg", e);

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

@ -101,7 +101,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
} }
public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception { public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
checkActive(msg.getMsg()); checkComponentStateActive(msg.getMsg());
TbMsg tbMsg = msg.getMsg(); TbMsg tbMsg = msg.getMsg();
int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter(); int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter();
int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage(); int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage();
@ -122,7 +122,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception { void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
msg.getMsg().getCallback().onProcessingStart(info); msg.getMsg().getCallback().onProcessingStart(info);
checkActive(msg.getMsg()); checkComponentStateActive(msg.getMsg());
TbMsg tbMsg = msg.getMsg(); TbMsg tbMsg = msg.getMsg();
int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter(); int ruleNodeCount = tbMsg.getAndIncrementRuleNodeCounter();
int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage(); int maxRuleNodeExecutionsPerMessage = getTenantProfileConfiguration().getMaxRuleNodeExecsPerMessage();

50
application/src/main/java/org/thingsboard/server/actors/ruleChain/TbToRuleChainActorMsg.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors.ruleChain;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbRuleEngineActorMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
@EqualsAndHashCode(callSuper = true)
@ToString
public abstract class TbToRuleChainActorMsg extends TbRuleEngineActorMsg implements RuleChainAwareMsg {
@Getter
private final RuleChainId target;
public TbToRuleChainActorMsg(TbMsg msg, RuleChainId target) {
super(msg);
this.target = target;
}
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override
public void onTbActorStopped(TbActorStopReason reason) {
String message = reason == TbActorStopReason.STOPPED ? String.format("Rule chain [%s] stopped", target.getId()) : String.format("Failed to initialize rule chain [%s]!", target.getId());
msg.getCallback().onFailure(new RuleEngineException(message));
}
}

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

@ -22,13 +22,13 @@ import org.thingsboard.server.actors.TbActorCtx;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public abstract class AbstractContextAwareMsgProcessor { public abstract class AbstractContextAwareMsgProcessor {
protected final static ObjectMapper mapper = new ObjectMapper();
protected final ActorSystemContext systemContext; protected final ActorSystemContext systemContext;
protected final ObjectMapper mapper = new ObjectMapper();
protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext) { protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext) {
super(); super();

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

@ -82,7 +82,17 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency); schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
} }
protected void checkActive(TbMsg tbMsg) throws RuleNodeException { protected boolean checkMsgValid(TbMsg tbMsg) {
var valid = tbMsg.isValid();
if (!valid) {
if (log.isTraceEnabled()) {
log.trace("Skip processing of message: {} because it is no longer valid!", tbMsg);
}
}
return valid;
}
protected void checkComponentStateActive(TbMsg tbMsg) throws RuleNodeException {
if (state != ComponentLifecycleState.ACTIVE) { if (state != ComponentLifecycleState.ACTIVE) {
log.debug("Component is not active. Current state [{}] for processor [{}][{}] tenant [{}]", state, entityId.getEntityType(), entityId, tenantId); log.debug("Component is not active. Current state [{}] for processor [{}][{}] tenant [{}]", state, entityId.getEntityType(), entityId, tenantId);
RuleNodeException ruleNodeException = getInactiveException(); RuleNodeException ruleNodeException = getInactiveException();

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

@ -26,7 +26,10 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate; import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator; import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.device.SessionTimeoutCheckMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainInputMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -168,6 +171,11 @@ public class TenantActor extends RuleChainManagerActor {
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG: case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((DeviceAwareMsg) msg, true); onToDeviceActorMsg((DeviceAwareMsg) msg, true);
break; break;
case SESSION_TIMEOUT_MSG:
ctx.broadcastToChildrenByType(msg, EntityType.DEVICE);
break;
case RULE_CHAIN_INPUT_MSG:
case RULE_CHAIN_OUTPUT_MSG:
case RULE_CHAIN_TO_RULE_CHAIN_MSG: case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg); onRuleChainMsg((RuleChainAwareMsg) msg);
break; break;

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

@ -80,7 +80,7 @@ public class AlarmController extends BaseController {
private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK"; private static final String ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES = "ANY, ACTIVE, CLEARED, ACK, UNACK";
private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value"; private static final String ALARM_QUERY_STATUS_DESCRIPTION = "A string value representing one of the AlarmStatus enumeration value";
private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK"; private static final String ALARM_QUERY_STATUS_ALLOWABLE_VALUES = "ACTIVE_UNACK, ACTIVE_ACK, CLEARED_UNACK, CLEARED_ACK";
private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on of next alarm fields: type, severity or status"; private static final String ALARM_QUERY_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on of next alarm fields: type, severity or status";
private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; private static final String ALARM_QUERY_START_TIME_DESCRIPTION = "The start timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'."; private static final String ALARM_QUERY_END_TIME_DESCRIPTION = "The end timestamp in milliseconds of the search time range over the Alarm class field: 'createdTime'.";
private static final String ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION = "A boolean value to specify if the alarm originator name will be " + private static final String ALARM_QUERY_FETCH_ORIGINATOR_DESCRIPTION = "A boolean value to specify if the alarm originator name will be " +

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

@ -126,7 +126,6 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeLicenseService; import org.thingsboard.server.service.edge.EdgeLicenseService;
import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository;
import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.resource.TbResourceService;
@ -261,9 +260,6 @@ public abstract class BaseController {
@Autowired @Autowired
protected TbDeviceProfileCache deviceProfileCache; protected TbDeviceProfileCache deviceProfileCache;
@Autowired
protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository;
@Autowired(required = false) @Autowired(required = false)
protected EdgeService edgeService; protected EdgeService edgeService;
@ -486,7 +482,7 @@ public abstract class BaseController {
checkCustomerId(new CustomerId(entityId.getId()), operation); checkCustomerId(new CustomerId(entityId.getId()), operation);
return; return;
case TENANT: case TENANT:
checkTenantId(new TenantId(entityId.getId()), operation); checkTenantId(TenantId.fromUUID(entityId.getId()), operation);
return; return;
case TENANT_PROFILE: case TENANT_PROFILE:
checkTenantProfileId(new TenantProfileId(entityId.getId()), operation); checkTenantProfileId(new TenantProfileId(entityId.getId()), operation);

30
application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java

@ -63,21 +63,21 @@ public class ControllerConstants {
protected static final String ASSET_TYPE_DESCRIPTION = "Asset type"; protected static final String ASSET_TYPE_DESCRIPTION = "Asset type";
protected static final String EDGE_TYPE_DESCRIPTION = "A string value representing the edge type. For example, 'default'"; protected static final String EDGE_TYPE_DESCRIPTION = "A string value representing the edge type. For example, 'default'";
protected static final String RULE_CHAIN_TYPE_DESCRIPTION = "Rule chain type (CORE or EDGE)"; protected static final String RULE_CHAIN_TYPE_DESCRIPTION = "Rule chain type (CORE or EDGE)";
protected static final String ASSET_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the asset name."; protected static final String ASSET_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the asset name.";
protected static final String DASHBOARD_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the dashboard title."; protected static final String DASHBOARD_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the dashboard title.";
protected static final String WIDGET_BUNDLE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the widget bundle title."; protected static final String WIDGET_BUNDLE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the widget bundle title.";
protected static final String RPC_TEXT_SEARCH_DESCRIPTION = "Not implemented. Leave empty."; protected static final String RPC_TEXT_SEARCH_DESCRIPTION = "Not implemented. Leave empty.";
protected static final String DEVICE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the device name."; protected static final String DEVICE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the device name.";
protected static final String ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the entity view name."; protected static final String ENTITY_VIEW_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the entity view name.";
protected static final String USER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the user email."; protected static final String USER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the user email.";
protected static final String TENANT_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the tenant name."; protected static final String TENANT_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the tenant name.";
protected static final String TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the tenant profile name."; protected static final String TENANT_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the tenant profile name.";
protected static final String RULE_CHAIN_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the rule chain name."; protected static final String RULE_CHAIN_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the rule chain name.";
protected static final String DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the device profile name."; protected static final String DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the device profile name.";
protected static final String CUSTOMER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the customer title."; protected static final String CUSTOMER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the customer title.";
protected static final String EDGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the edge name."; protected static final String EDGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the edge name.";
protected static final String EVENT_TEXT_SEARCH_DESCRIPTION = "The value is not used in searching."; protected static final String EVENT_TEXT_SEARCH_DESCRIPTION = "The value is not used in searching.";
protected static final String AUDIT_LOG_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on one of the next properties: entityType, entityName, userName, actionType, actionStatus."; protected static final String AUDIT_LOG_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on one of the next properties: entityType, entityName, userName, actionType, actionStatus.";
protected static final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by"; protected static final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by";
protected static final String DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title"; protected static final String DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title";
protected static final String CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, city"; protected static final String CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, city";
@ -114,12 +114,12 @@ public class ControllerConstants {
protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. "; protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. ";
protected static final String OTA_PACKAGE_DESCRIPTION = "OTA Package is a heavyweight object that includes main information about the OTA Package and also data. "; protected static final String OTA_PACKAGE_DESCRIPTION = "OTA Package is a heavyweight object that includes main information about the OTA Package and also data. ";
protected static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES = "MD5, SHA256, SHA384, SHA512, CRC32, MURMUR3_32, MURMUR3_128"; protected static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES = "MD5, SHA256, SHA384, SHA512, CRC32, MURMUR3_32, MURMUR3_128";
protected static final String OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the ota package title."; protected static final String OTA_PACKAGE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the ota package title.";
protected static final String OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, type, title, version, tag, url, fileName, dataSize, checksum"; protected static final String OTA_PACKAGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, type, title, version, tag, url, fileName, dataSize, checksum";
protected static final String RESOURCE_INFO_DESCRIPTION = "Resource Info is a lightweight object that includes main information about the Resource excluding the heavyweight data. "; protected static final String RESOURCE_INFO_DESCRIPTION = "Resource Info is a lightweight object that includes main information about the Resource excluding the heavyweight data. ";
protected static final String RESOURCE_DESCRIPTION = "Resource is a heavyweight object that includes main information about the Resource and also data. "; protected static final String RESOURCE_DESCRIPTION = "Resource is a heavyweight object that includes main information about the Resource and also data. ";
protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the resource title."; protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title.";
protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId"; protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId";
protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. "; protected static final String LWM2M_OBJECT_DESCRIPTION = "LwM2M Object is a object that includes information about the LwM2M model which can be used in transport configuration for the LwM2M device profile. ";
protected static final String LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name"; protected static final String LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES = "id, name";

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

@ -585,7 +585,7 @@ public class DashboardController extends BaseController {
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
checkTenantId(tenantId, Operation.READ); checkTenantId(tenantId, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));

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

@ -807,7 +807,7 @@ public class DeviceController extends BaseController {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT); Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT);
TenantId newTenantId = new TenantId(toUUID(strTenantId)); TenantId newTenantId = TenantId.fromUUID(toUUID(strTenantId));
Tenant newTenant = tenantService.findTenantById(newTenantId); Tenant newTenant = tenantService.findTenantById(newTenantId);
if (newTenant == null) { if (newTenant == null) {
throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);

1
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -219,7 +219,6 @@ public class DeviceProfileController extends BaseController {
isSoftwareChanged = true; isSoftwareChanged = true;
} }
} }
DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);

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

@ -70,7 +70,7 @@ public class EdgeEventController extends BaseController {
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = "The case insensitive 'startsWith' filter based on the edge event type name.") @ApiParam(value = "The case insensitive 'substring' filter based on the edge event type name.")
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES) @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = EDGE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,

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

@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.event.EventFilter;
@ -134,7 +136,7 @@ public class EventController extends BaseController {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ); checkEntityId(entityId, Operation.READ);
@ -175,7 +177,7 @@ public class EventController extends BaseController {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ); checkEntityId(entityId, Operation.READ);
@ -222,7 +224,7 @@ public class EventController extends BaseController {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId); EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ); checkEntityId(entityId, Operation.READ);
@ -238,4 +240,30 @@ public class EventController extends BaseController {
} }
} }
@ApiOperation(value = "Clear Events (clearEvents)", notes = "Clears events by filter for specified entity.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}/clear", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void clearEvents(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable(ENTITY_TYPE) String strEntityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(ENTITY_ID) String strEntityId,
@ApiParam(value = EVENT_START_TIME_DESCRIPTION)
@RequestParam(required = false) Long startTime,
@ApiParam(value = EVENT_END_TIME_DESCRIPTION)
@RequestParam(required = false) Long endTime,
@ApiParam(value = EVENT_FILTER_DEFINITION)
@RequestBody EventFilter eventFilter) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType);
try {
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.WRITE);
eventService.removeEvents(getTenantId(), entityId, eventFilter, startTime, endTime);
} catch (Exception e) {
throw handleException(e);
}
}
} }

27
application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java

@ -20,6 +20,7 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -29,10 +30,10 @@ import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.lwm2m.LwM2MService;
import java.util.Map; import java.util.Map;
@ -41,15 +42,17 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CU
@Slf4j @Slf4j
@RestController @RestController
@TbCoreComponent @ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'")
@RequestMapping("/api") @RequestMapping("/api")
public class Lwm2mController extends BaseController { public class Lwm2mController extends BaseController {
@Autowired @Autowired
private DeviceController deviceController; private DeviceController deviceController;
public static final String IS_BOOTSTRAP_SERVER = "isBootstrapServer"; @Autowired
private LwM2MService lwM2MService;
public static final String IS_BOOTSTRAP_SERVER = "isBootstrapServer";
@ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)", @ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)",
notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " + notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " +
@ -58,14 +61,14 @@ public class Lwm2mController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET) @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public ServerSecurityConfig getLwm2mBootstrapSecurityInfo( public LwM2MServerSecurityConfigDefault getLwm2mBootstrapSecurityInfo(
@ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION) @ApiParam(value = IS_BOOTSTRAP_SERVER_PARAM_DESCRIPTION)
@PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException { @PathVariable(IS_BOOTSTRAP_SERVER) boolean bootstrapServer) throws ThingsboardException {
try { try {
return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(bootstrapServer); return lwM2MService.getServerSecurityInfo(bootstrapServer);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
} }
@ApiOperation(hidden = true, value = "Save device with credentials (Deprecated)") @ApiOperation(hidden = true, value = "Save device with credentials (Deprecated)")

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

@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.server.ResponseStatusException;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -177,8 +178,8 @@ public class RpcV2Controller extends AbstractRpcController {
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = "Status of the RPC", required = true, allowableValues = RPC_STATUS_ALLOWABLE_VALUES) @ApiParam(value = "Status of the RPC", allowableValues = RPC_STATUS_ALLOWABLE_VALUES)
@RequestParam RpcStatus rpcStatus, @RequestParam(required = false) RpcStatus rpcStatus,
@ApiParam(value = RPC_TEXT_SEARCH_DESCRIPTION) @ApiParam(value = RPC_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RPC_SORT_PROPERTY_ALLOWABLE_VALUES) @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RPC_SORT_PROPERTY_ALLOWABLE_VALUES)
@ -187,14 +188,24 @@ public class RpcV2Controller extends AbstractRpcController {
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("DeviceId", strDeviceId); checkParameter("DeviceId", strDeviceId);
try { try {
if (rpcStatus != null && rpcStatus.equals(RpcStatus.DELETED)) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "RpcStatus: DELETED");
}
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId)); DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId));
final DeferredResult<ResponseEntity> response = new DeferredResult<>(); final DeferredResult<ResponseEntity> response = new DeferredResult<>();
accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() { accessValidator.validate(getCurrentUser(), Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) { public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
PageData<Rpc> rpcCalls = rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink); PageData<Rpc> rpcCalls;
if (rpcStatus != null) {
rpcCalls = rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink);
} else {
rpcCalls = rpcService.findAllByDeviceId(tenantId, deviceId, pageLink);
}
response.setResult(new ResponseEntity<>(rpcCalls, HttpStatus.OK)); response.setResult(new ResponseEntity<>(rpcCalls, HttpStatus.OK));
} }
@ -219,7 +230,7 @@ public class RpcV2Controller extends AbstractRpcController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE) @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public void deleteResource( public void deleteRpc(
@ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true) @ApiParam(value = RPC_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(RPC_ID) String strRpc) throws ThingsboardException { @PathVariable(RPC_ID) String strRpc) throws ThingsboardException {
checkParameter("RpcId", strRpc); checkParameter("RpcId", strRpc);
@ -235,6 +246,7 @@ public class RpcV2Controller extends AbstractRpcController {
} }
rpcService.deleteRpc(getTenantId(), rpcId); rpcService.deleteRpc(getTenantId(), rpcId);
rpc.setStatus(RpcStatus.DELETED);
TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc));
tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null);

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

@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
@ -43,6 +42,7 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -60,7 +60,9 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainData;
import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainImportResult;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
@ -68,15 +70,19 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
import org.thingsboard.server.service.rule.TbRuleChainService;
import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.permission.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -137,6 +143,9 @@ public class RuleChainController extends BaseController {
+ MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END
+ "\n\n Expected result JSON contains \"output\" and \"error\"."; + "\n\n Expected result JSON contains \"output\" and \"error\".";
@Autowired
protected TbRuleChainService tbRuleChainService;
@Autowired @Autowired
private InstallScripts installScripts; private InstallScripts installScripts;
@ -169,6 +178,44 @@ public class RuleChainController extends BaseController {
} }
} }
@ApiOperation(value = "Get Rule Chain output labels (getRuleChainOutputLabels)",
notes = "Fetch the unique labels for the \"output\" Rule Nodes that belong to the Rule Chain based on the provided Rule Chain Id. "
+ RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels", method = RequestMethod.GET)
@ResponseBody
public Set<String> getRuleChainOutputLabels(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
return tbRuleChainService.getRuleChainOutputLabels(getTenantId(), ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get output labels usage (getRuleChainOutputLabelsUsage)",
notes = "Fetch the list of rule chains and the relation types (labels) they use to process output of the current rule chain based on the provided Rule Chain Id. "
+ RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain/{ruleChainId}/output/labels/usage", method = RequestMethod.GET)
@ResponseBody
public List<RuleChainOutputLabelsUsage> getRuleChainOutputLabelsUsage(
@ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION)
@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
return tbRuleChainService.getOutputLabelUsage(getCurrentUser().getTenantId(), ruleChainId);
} catch (Exception e) {
throw handleException(e);
}
}
@ApiOperation(value = "Get Rule Chain (getRuleChainById)", @ApiOperation(value = "Get Rule Chain (getRuleChainById)",
notes = "Fetch the Rule Chain Metadata object based on the provided Rule Chain Id. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH) notes = "Fetch the Rule Chain Metadata object based on the provided Rule Chain Id. " + RULE_CHAIN_METADATA_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@ -310,7 +357,10 @@ public class RuleChainController extends BaseController {
@ResponseBody @ResponseBody
public RuleChainMetaData saveRuleChainMetaData( public RuleChainMetaData saveRuleChainMetaData(
@ApiParam(value = "A JSON value representing the rule chain metadata.") @ApiParam(value = "A JSON value representing the rule chain metadata.")
@RequestBody RuleChainMetaData ruleChainMetaData) throws ThingsboardException { @RequestBody RuleChainMetaData ruleChainMetaData,
@ApiParam(value = "Update related rule nodes.")
@RequestParam(value = "updateRelated", required = false, defaultValue = "true") boolean updateRelated
) throws ThingsboardException {
try { try {
TenantId tenantId = getTenantId(); TenantId tenantId = getTenantId();
if (debugPerTenantEnabled) { if (debugPerTenantEnabled) {
@ -322,20 +372,36 @@ public class RuleChainController extends BaseController {
} }
RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE); RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData) ? true : null); RuleChainUpdateResult result = ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData);
checkNotNull(result.isSuccess() ? true : null);
List<RuleChain> updatedRuleChains;
if (updateRelated && result.isSuccess()) {
updatedRuleChains = tbRuleChainService.updateRelatedRuleChains(tenantId, ruleChainMetaData.getRuleChainId(), result);
} else {
updatedRuleChains = Collections.emptyList();
}
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId())); RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()));
if (RuleChainType.CORE.equals(ruleChain.getType())) { if (RuleChainType.CORE.equals(ruleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
updatedRuleChains.forEach(updatedRuleChain -> {
tbClusterService.broadcastEntityStateChangeEvent(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), ComponentLifecycleEvent.UPDATED);
});
} }
logEntityAction(ruleChain.getId(), ruleChain, logEntityAction(ruleChain.getId(), ruleChain, null, ActionType.UPDATED, null, ruleChainMetaData);
null, for (RuleChain updatedRuleChain : updatedRuleChains) {
ActionType.UPDATED, null, ruleChainMetaData); RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId()));
logEntityAction(updatedRuleChain.getId(), updatedRuleChain, null, ActionType.UPDATED, null, updatedRuleChainMetaData);
}
if (RuleChainType.EDGE.equals(ruleChain.getType())) { if (RuleChainType.EDGE.equals(ruleChain.getType())) {
sendEntityNotificationMsg(ruleChain.getTenantId(), sendEntityNotificationMsg(ruleChain.getTenantId(), ruleChain.getId(), EdgeEventActionType.UPDATED);
ruleChain.getId(), EdgeEventActionType.UPDATED); updatedRuleChains.forEach(updatedRuleChain -> {
sendEntityNotificationMsg(updatedRuleChain.getTenantId(), updatedRuleChain.getId(), EdgeEventActionType.UPDATED);
});
} }
return savedRuleChainMetaData; return savedRuleChainMetaData;
@ -681,7 +747,7 @@ public class RuleChainController extends BaseController {
} }
@ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)", @ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)",
notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

71
application/src/main/java/org/thingsboard/server/controller/SystemInfoController.java

@ -0,0 +1,71 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.queue.util.TbCoreComponent;
import springfox.documentation.annotations.ApiIgnore;
import javax.annotation.PostConstruct;
@ApiIgnore
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class SystemInfoController {
@Autowired(required = false)
private BuildProperties buildProperties;
@PostConstruct
public void init() {
JsonNode info = buildInfoObject();
log.info("System build info: {}", info);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/system/info", method = RequestMethod.GET)
@ResponseBody
public JsonNode getSystemVersionInfo() {
return buildInfoObject();
}
private JsonNode buildInfoObject() {
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode infoObject = objectMapper.createObjectNode();
if (buildProperties != null) {
infoObject.put("version", buildProperties.getVersion());
infoObject.put("artifact", buildProperties.getArtifact());
infoObject.put("name", buildProperties.getName());
} else {
infoObject.put("version", "unknown");
}
return infoObject;
}
}

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

@ -573,10 +573,9 @@ public class TelemetryController extends BaseController {
for (String key : keys) { for (String key : keys) {
deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted)); deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
} }
ListenableFuture<List<Void>> future = tsService.remove(user.getTenantId(), entityId, deleteTsKvQueries); tsSubService.deleteTimeseriesAndNotify(tenantId, entityId, keys, deleteTsKvQueries, new FutureCallback<>() {
Futures.addCallback(future, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable List<Void> tmp) { public void onSuccess(@Nullable Void tmp) {
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null); logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
result.setResult(new ResponseEntity<>(HttpStatus.OK)); result.setResult(new ResponseEntity<>(HttpStatus.OK));
} }
@ -586,7 +585,7 @@ public class TelemetryController extends BaseController {
logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t); logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, t);
result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
} }
}, executor); });
}); });
} }

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

@ -82,7 +82,7 @@ public class TenantController extends BaseController {
@PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException {
checkParameter(TENANT_ID, strTenantId); checkParameter(TENANT_ID, strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
Tenant tenant = checkTenantId(tenantId, Operation.READ); Tenant tenant = checkTenantId(tenantId, Operation.READ);
if (!tenant.getAdditionalInfo().isNull()) { if (!tenant.getAdditionalInfo().isNull()) {
processDashboardIdFromAdditionalInfo((ObjectNode) tenant.getAdditionalInfo(), HOME_DASHBOARD); processDashboardIdFromAdditionalInfo((ObjectNode) tenant.getAdditionalInfo(), HOME_DASHBOARD);
@ -104,7 +104,7 @@ public class TenantController extends BaseController {
@PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException {
checkParameter(TENANT_ID, strTenantId); checkParameter(TENANT_ID, strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
return checkTenantInfoId(tenantId, Operation.READ); return checkTenantInfoId(tenantId, Operation.READ);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -154,7 +154,7 @@ public class TenantController extends BaseController {
@PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException {
checkParameter(TENANT_ID, strTenantId); checkParameter(TENANT_ID, strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
Tenant tenant = checkTenantId(tenantId, Operation.DELETE); Tenant tenant = checkTenantId(tenantId, Operation.DELETE);
tenantService.deleteTenant(tenantId); tenantService.deleteTenant(tenantId);
tenantProfileCache.evict(tenantId); tenantProfileCache.evict(tenantId);

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

@ -302,6 +302,10 @@ public class UserController extends BaseController {
UserId userId = new UserId(toUUID(strUserId)); UserId userId = new UserId(toUUID(strUserId));
User user = checkUserId(userId, Operation.DELETE); User user = checkUserId(userId, Operation.DELETE);
if (user.getAuthority() == Authority.SYS_ADMIN && getCurrentUser().getId().equals(userId)) {
throw new ThingsboardException("Sysadmin is not allowed to delete himself", ThingsboardErrorCode.PERMISSION_DENIED);
}
List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), userId); List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), userId);
userService.deleteUser(getCurrentUser().getTenantId(), userId); userService.deleteUser(getCurrentUser().getTenantId(), userId);
@ -371,7 +375,7 @@ public class UserController extends BaseController {
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("tenantId", strTenantId); checkParameter("tenantId", strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(userService.findTenantAdmins(tenantId, pageLink)); return checkNotNull(userService.findTenantAdmins(tenantId, pageLink));
} catch (Exception e) { } catch (Exception e) {

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

@ -216,7 +216,7 @@ public class WidgetTypeController extends BaseController {
try { try {
TenantId tenantId; TenantId tenantId;
if (isSystem) { if (isSystem) {
tenantId = new TenantId(ModelConstants.NULL_UUID); tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID);
} else { } else {
tenantId = getCurrentUser().getTenantId(); tenantId = getCurrentUser().getTenantId();
} }

64
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -23,6 +23,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.adapter.NativeWebSocketSession; import org.springframework.web.socket.adapter.NativeWebSocketSession;
@ -36,6 +37,7 @@ import org.thingsboard.server.config.WebSocketConfiguration;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService;
import org.thingsboard.server.service.telemetry.SessionEvent; import org.thingsboard.server.service.telemetry.SessionEvent;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint; import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint;
import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; import org.thingsboard.server.service.telemetry.TelemetryWebSocketService;
@ -56,6 +58,8 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import static org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService.NUMBER_OF_PING_ATTEMPTS;
@Service @Service
@TbCoreComponent @TbCoreComponent
@Slf4j @Slf4j
@ -111,6 +115,22 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
} }
} }
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
try {
SessionMetaData sessionMd = internalSessionMap.get(session.getId());
if (sessionMd != null) {
log.trace("[{}][{}] Processing pong response {}", sessionMd.sessionRef.getSecurityCtx().getTenantId(), session.getId(), message.getPayload());
sessionMd.processPongMessage(System.currentTimeMillis());
} else {
log.trace("[{}] Failed to find session", session.getId());
session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!"));
}
} catch (IOException e) {
log.warn("IO error", e);
}
}
@Override @Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception { public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session); super.afterConnectionEstablished(session);
@ -212,20 +232,31 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
synchronized void sendPing(long currentTime) { synchronized void sendPing(long currentTime) {
try { try {
if (currentTime - lastActivityTime >= pingTimeout) { long timeSinceLastActivity = currentTime - lastActivityTime;
if (timeSinceLastActivity >= pingTimeout) {
log.warn("[{}] Closing session due to ping timeout", session.getId());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else if (timeSinceLastActivity >= pingTimeout / NUMBER_OF_PING_ATTEMPTS) {
this.asyncRemote.sendPing(PING_MSG); this.asyncRemote.sendPing(PING_MSG);
lastActivityTime = currentTime;
} }
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to send ping msg", session.getId(), e); log.trace("[{}] Failed to send ping msg", session.getId(), e);
try { closeSession(CloseStatus.SESSION_NOT_RELIABLE);
close(this.sessionRef, CloseStatus.SESSION_NOT_RELIABLE);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
}
} }
} }
private void closeSession(CloseStatus reason) {
try {
close(this.sessionRef, reason);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
}
}
synchronized void processPongMessage(long currentTime) {
lastActivityTime = currentTime;
}
synchronized void sendMsg(String msg) { synchronized void sendMsg(String msg) {
if (isSending) { if (isSending) {
try { try {
@ -236,11 +267,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
} else { } else {
log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId()); log.info("[{}][{}] Session closed due to queue error", sessionRef.getSecurityCtx().getTenantId(), session.getId());
} }
try { closeSession(CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!"));
close(sessionRef, CloseStatus.POLICY_VIOLATION.withReason("Max pending updates limit reached!"));
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
}
} }
} else { } else {
isSending = true; isSending = true;
@ -253,11 +280,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
this.asyncRemote.sendText(msg, this); this.asyncRemote.sendText(msg, this);
} catch (Exception e) { } catch (Exception e) {
log.trace("[{}] Failed to send msg", session.getId(), e); log.trace("[{}] Failed to send msg", session.getId(), e);
try { closeSession(CloseStatus.SESSION_NOT_RELIABLE);
close(this.sessionRef, CloseStatus.SESSION_NOT_RELIABLE);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
}
} }
} }
@ -265,13 +288,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
public void onResult(SendResult result) { public void onResult(SendResult result) {
if (!result.isOK()) { if (!result.isOK()) {
log.trace("[{}] Failed to send msg", session.getId(), result.getException()); log.trace("[{}] Failed to send msg", session.getId(), result.getException());
try { closeSession(CloseStatus.SESSION_NOT_RELIABLE);
close(this.sessionRef, CloseStatus.SESSION_NOT_RELIABLE);
} catch (IOException ioe) {
log.trace("[{}] Session transport error", session.getId(), ioe);
}
} else { } else {
lastActivityTime = System.currentTimeMillis();
String msg = msgQueue.poll(); String msg = msgQueue.poll();
if (msg != null) { if (msg != null) {
sendMsgInternal(msg); sendMsgInternal(msg);

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

@ -210,6 +210,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ..."); log.info("Upgrading ThingsBoard from version 3.3.0 to 3.3.1 ...");
case "3.3.1": case "3.3.1":
log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ..."); log.info("Upgrading ThingsBoard from version 3.3.1 to 3.3.2 ...");
case "3.3.2":
log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.2");
dataUpdateService.updateData("3.3.2");
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;

2
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -164,7 +164,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) { public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
ToUsageStatsServiceMsg statsMsg = msg.getValue(); ToUsageStatsServiceMsg statsMsg = msg.getValue();
TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB()));
EntityId entityId; EntityId entityId;
if (statsMsg.getCustomerIdMSB() != 0 && statsMsg.getCustomerIdLSB() != 0) { if (statsMsg.getCustomerIdMSB() != 0 && statsMsg.getCustomerIdLSB() != 0) {
entityId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB())); entityId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB()));

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

@ -152,24 +152,14 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
try { try {
scannedComponent.setType(type); scannedComponent.setType(type);
Class<?> clazz = Class.forName(clazzName); Class<?> clazz = Class.forName(clazzName);
switch (type) { RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
case ENRICHMENT: scannedComponent.setName(ruleNodeAnnotation.name());
case FILTER: scannedComponent.setScope(ruleNodeAnnotation.scope());
case TRANSFORMATION: NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
case ACTION: ObjectNode configurationDescriptor = mapper.createObjectNode();
case EXTERNAL: JsonNode node = mapper.valueToTree(nodeDefinition);
RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class); configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setName(ruleNodeAnnotation.name()); scannedComponent.setConfigurationDescriptor(configurationDescriptor);
scannedComponent.setScope(ruleNodeAnnotation.scope());
NodeDefinition nodeDefinition = prepareNodeDefinition(ruleNodeAnnotation);
ObjectNode configurationDescriptor = mapper.createObjectNode();
JsonNode node = mapper.valueToTree(nodeDefinition);
configurationDescriptor.set("nodeDefinition", node);
scannedComponent.setConfigurationDescriptor(configurationDescriptor);
break;
default:
throw new RuntimeException(type + " is not supported yet!");
}
scannedComponent.setClazz(clazzName); scannedComponent.setClazz(clazzName);
log.info("Processing scanned component: {}", scannedComponent); log.info("Processing scanned component: {}", scannedComponent);
} catch (Exception e) { } catch (Exception e) {
@ -200,6 +190,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled()); nodeDefinition.setOutEnabled(nodeAnnotation.outEnabled());
nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation)); nodeDefinition.setRelationTypes(getRelationTypesWithFailureRelation(nodeAnnotation));
nodeDefinition.setCustomRelations(nodeAnnotation.customRelations()); nodeDefinition.setCustomRelations(nodeAnnotation.customRelations());
nodeDefinition.setRuleChainNode(nodeAnnotation.ruleChainNode());
Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz(); Class<? extends NodeConfiguration> configClazz = nodeAnnotation.configClazz();
NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance(); NodeConfiguration config = configClazz.getDeclaredConstructor().newInstance();
NodeConfiguration defaultConfiguration = config.defaultConfiguration(); NodeConfiguration defaultConfiguration = config.defaultConfiguration();

6
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -32,7 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
@ -185,9 +185,9 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
setValues(client, fields, Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE, setValues(client, fields, Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE,
BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_IDENTITY, BulkImportColumnType.LWM2M_CLIENT_ENDPOINT, BulkImportColumnType.LWM2M_CLIENT_IDENTITY,
BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT)); BulkImportColumnType.LWM2M_CLIENT_KEY, BulkImportColumnType.LWM2M_CLIENT_CERT));
LwM2MClientCredentials lwM2MClientCredentials = JacksonUtil.treeToValue(client, LwM2MClientCredentials.class); LwM2MClientCredential lwM2MClientCredential = JacksonUtil.treeToValue(client, LwM2MClientCredential.class);
// so that only fields needed for specific type of lwM2MClientCredentials were saved in json // so that only fields needed for specific type of lwM2MClientCredentials were saved in json
lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredentials)); lwm2mCredentials.set("client", JacksonUtil.valueToTree(lwM2MClientCredential));
ObjectNode bootstrapServer = JacksonUtil.newObjectNode(); ObjectNode bootstrapServer = JacksonUtil.newObjectNode();
setValues(bootstrapServer, fields, Set.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, setValues(bootstrapServer, fields, Set.of(BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE,

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

@ -124,7 +124,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
log.trace("Pushing notification to edge {}", edgeNotificationMsg); log.trace("Pushing notification to edge {}", edgeNotificationMsg);
try { try {
TenantId tenantId = new TenantId(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
switch (type) { switch (type) {
case EDGE: case EDGE:

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

@ -52,6 +52,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg; import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeUpdateMsg; import org.thingsboard.server.gen.edge.v1.EdgeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg; import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg;
import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
@ -73,14 +74,11 @@ import org.thingsboard.server.service.edge.rpc.fetch.GeneralEdgeEventFetcher;
import java.io.Closeable; import java.io.Closeable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -109,6 +107,8 @@ public final class EdgeGrpcSession implements Closeable {
private boolean connected; private boolean connected;
private boolean syncCompleted; private boolean syncCompleted;
private EdgeVersion edgeVersion;
private ScheduledExecutorService sendDownlinkExecutorService; private ScheduledExecutorService sendDownlinkExecutorService;
EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver<ResponseMsg> outputStream, BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener, EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver<ResponseMsg> outputStream, BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener,
@ -501,8 +501,9 @@ public final class EdgeGrpcSession implements Closeable {
private ListenableFuture<List<Void>> updateQueueStartTs(Long newStartTs) { private ListenableFuture<List<Void>> updateQueueStartTs(Long newStartTs) {
log.trace("[{}] updating QueueStartTs [{}][{}]", this.sessionId, edge.getId(), newStartTs); log.trace("[{}] updating QueueStartTs [{}][{}]", this.sessionId, edge.getId(), newStartTs);
newStartTs = ++newStartTs; // increments ts by 1 - next edge event search starts from current offset + 1 List<AttributeKvEntry> attributes = Collections.singletonList(
List<AttributeKvEntry> attributes = Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(QUEUE_START_TS_ATTR_KEY, newStartTs), System.currentTimeMillis())); new BaseAttributeKvEntry(
new LongDataEntry(QUEUE_START_TS_ATTR_KEY, newStartTs), System.currentTimeMillis()));
return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes); return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes);
} }
@ -525,7 +526,7 @@ public final class EdgeGrpcSession implements Closeable {
case RULE_CHAIN: case RULE_CHAIN:
return ctx.getRuleChainProcessor().processRuleChainToEdge(edge, edgeEvent, msgType, action); return ctx.getRuleChainProcessor().processRuleChainToEdge(edge, edgeEvent, msgType, action);
case RULE_CHAIN_METADATA: case RULE_CHAIN_METADATA:
return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType); return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType, this.edgeVersion);
case ALARM: case ALARM:
return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType, action); return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType, action);
case USER: case USER:
@ -655,6 +656,7 @@ public final class EdgeGrpcSession implements Closeable {
try { try {
if (edge.getSecret().equals(request.getEdgeSecret())) { if (edge.getSecret().equals(request.getEdgeSecret())) {
sessionOpenListener.accept(edge.getId(), this); sessionOpenListener.accept(edge.getId(), this);
this.edgeVersion = request.getEdgeVersion();
return ConnectResponseMsg.newBuilder() return ConnectResponseMsg.newBuilder()
.setResponseCode(ConnectResponseCode.ACCEPTED) .setResponseCode(ConnectResponseCode.ACCEPTED)
.setErrorMsg("") .setErrorMsg("")

187
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java

@ -17,15 +17,18 @@ package org.thingsboard.server.service.edge.rpc.constructor;
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 com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto;
import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
@ -36,6 +39,10 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.NavigableSet;
import java.util.TreeSet;
import java.util.UUID;
import java.util.stream.Collectors;
@Component @Component
@Slf4j @Slf4j
@ -43,6 +50,8 @@ import java.util.List;
public class RuleChainMsgConstructor { public class RuleChainMsgConstructor {
private static final ObjectMapper objectMapper = new ObjectMapper(); private static final ObjectMapper objectMapper = new ObjectMapper();
private static final String RULE_CHAIN_INPUT_NODE = "org.thingsboard.rule.engine.flow.TbRuleChainInputNode";
private static final String TB_RULE_CHAIN_OUTPUT_NODE = "org.thingsboard.rule.engine.flow.TbRuleChainOutputNode";
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) { public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) {
RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder() RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder()
@ -60,18 +69,19 @@ public class RuleChainMsgConstructor {
return builder.build(); return builder.build();
} }
public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) { public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType,
RuleChainMetaData ruleChainMetaData,
EdgeVersion edgeVersion) {
try { try {
RuleChainMetadataUpdateMsg.Builder builder = RuleChainMetadataUpdateMsg.newBuilder() RuleChainMetadataUpdateMsg.Builder builder = RuleChainMetadataUpdateMsg.newBuilder();
.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits()) switch (edgeVersion) {
.setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits()) case V_3_3_0:
.addAllNodes(constructNodes(ruleChainMetaData.getNodes())) constructRuleChainMetadataUpdatedMsg_V_3_3_0(builder, ruleChainMetaData);
.addAllConnections(constructConnections(ruleChainMetaData.getConnections())) break;
.addAllRuleChainConnections(constructRuleChainConnections(ruleChainMetaData.getRuleChainConnections())); case V_3_3_3:
if (ruleChainMetaData.getFirstNodeIndex() != null) { default:
builder.setFirstNodeIndex(ruleChainMetaData.getFirstNodeIndex()); constructRuleChainMetadataUpdatedMsg_V_3_3_3(builder, ruleChainMetaData);
} else { break;
builder.setFirstNodeIndex(-1);
} }
builder.setMsgType(msgType); builder.setMsgType(msgType);
return builder.build(); return builder.build();
@ -81,6 +91,104 @@ public class RuleChainMsgConstructor {
return null; return null;
} }
private void constructRuleChainMetadataUpdatedMsg_V_3_3_3(RuleChainMetadataUpdateMsg.Builder builder,
RuleChainMetaData ruleChainMetaData) throws JsonProcessingException {
builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits())
.setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits())
.addAllNodes(constructNodes(ruleChainMetaData.getNodes()))
.addAllConnections(constructConnections(ruleChainMetaData.getConnections()))
.addAllRuleChainConnections(constructRuleChainConnections(ruleChainMetaData.getRuleChainConnections(), new TreeSet<>()));
if (ruleChainMetaData.getFirstNodeIndex() != null) {
builder.setFirstNodeIndex(ruleChainMetaData.getFirstNodeIndex());
} else {
builder.setFirstNodeIndex(-1);
}
}
private void constructRuleChainMetadataUpdatedMsg_V_3_3_0(RuleChainMetadataUpdateMsg.Builder builder,
RuleChainMetaData ruleChainMetaData) throws JsonProcessingException {
List<RuleNode> supportedNodes = filterNodes_V_3_3_0(ruleChainMetaData.getNodes());
NavigableSet<Integer> removedNodeIndexes = getRemovedNodeIndexes(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections());
List<NodeConnectionInfo> connections = filterConnections_V_3_3_0(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections(), removedNodeIndexes);
List<RuleChainConnectionInfo> ruleChainConnections = new ArrayList<>();
if (ruleChainMetaData.getRuleChainConnections() != null) {
ruleChainConnections.addAll(ruleChainMetaData.getRuleChainConnections());
}
ruleChainConnections.addAll(addRuleChainConnections_V_3_3_0(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections()));
builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits())
.setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits())
.addAllNodes(constructNodes(supportedNodes))
.addAllConnections(constructConnections(connections))
.addAllRuleChainConnections(constructRuleChainConnections(ruleChainConnections, removedNodeIndexes));
if (ruleChainMetaData.getFirstNodeIndex() != null) {
Integer firstNodeIndex = ruleChainMetaData.getFirstNodeIndex();
// decrease index because of removed nodes
for (Integer removedIndex : removedNodeIndexes) {
if (firstNodeIndex > removedIndex) {
firstNodeIndex = firstNodeIndex - 1;
}
}
builder.setFirstNodeIndex(firstNodeIndex);
} else {
builder.setFirstNodeIndex(-1);
}
}
private List<NodeConnectionInfo> filterConnections_V_3_3_0(List<RuleNode> nodes, List<NodeConnectionInfo> connections, NavigableSet<Integer> removedNodeIndexes) {
List<NodeConnectionInfo> result = new ArrayList<>();
if (connections != null) {
result = connections.stream().filter(conn -> {
for (int i = 0; i < nodes.size(); i++) {
RuleNode node = nodes.get(i);
if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE)
|| node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) {
if (conn.getFromIndex() == i || conn.getToIndex() == i) {
return false;
}
}
}
return true;
}).map(conn -> {
NodeConnectionInfo newConn = new NodeConnectionInfo();
newConn.setFromIndex(conn.getFromIndex());
newConn.setToIndex(conn.getToIndex());
newConn.setType(conn.getType());
return newConn;
}).collect(Collectors.toList());
}
// decrease index because of removed nodes
for (Integer removedIndex : removedNodeIndexes) {
for (NodeConnectionInfo newConn : result) {
if (newConn.getToIndex() > removedIndex) {
newConn.setToIndex(newConn.getToIndex() - 1);
}
if (newConn.getFromIndex() > removedIndex) {
newConn.setFromIndex(newConn.getFromIndex() - 1);
}
}
}
return result;
}
private NavigableSet<Integer> getRemovedNodeIndexes(List<RuleNode> nodes, List<NodeConnectionInfo> connections) {
TreeSet<Integer> removedIndexes = new TreeSet<>();
for (NodeConnectionInfo connection : connections) {
for (int i = 0; i < nodes.size(); i++) {
RuleNode node = nodes.get(i);
if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE)
|| node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) {
if (connection.getFromIndex() == i || connection.getToIndex() == i) {
removedIndexes.add(i);
}
}
}
}
return removedIndexes.descendingSet();
}
private List<NodeConnectionInfoProto> constructConnections(List<NodeConnectionInfo> connections) { private List<NodeConnectionInfoProto> constructConnections(List<NodeConnectionInfo> connections) {
List<NodeConnectionInfoProto> result = new ArrayList<>(); List<NodeConnectionInfoProto> result = new ArrayList<>();
if (connections != null && !connections.isEmpty()) { if (connections != null && !connections.isEmpty()) {
@ -99,6 +207,21 @@ public class RuleChainMsgConstructor {
.build(); .build();
} }
private List<RuleNode> filterNodes_V_3_3_0(List<RuleNode> nodes) {
List<RuleNode> result = new ArrayList<>();
for (RuleNode node : nodes) {
switch (node.getType()) {
case RULE_CHAIN_INPUT_NODE:
case TB_RULE_CHAIN_OUTPUT_NODE:
log.trace("Skipping not supported rule node {}", node);
break;
default:
result.add(node);
}
}
return result;
}
private List<RuleNodeProto> constructNodes(List<RuleNode> nodes) throws JsonProcessingException { private List<RuleNodeProto> constructNodes(List<RuleNode> nodes) throws JsonProcessingException {
List<RuleNodeProto> result = new ArrayList<>(); List<RuleNodeProto> result = new ArrayList<>();
if (nodes != null && !nodes.isEmpty()) { if (nodes != null && !nodes.isEmpty()) {
@ -109,23 +232,55 @@ public class RuleChainMsgConstructor {
return result; return result;
} }
private List<RuleChainConnectionInfoProto> constructRuleChainConnections(List<RuleChainConnectionInfo> ruleChainConnections) throws JsonProcessingException { private List<RuleChainConnectionInfo> addRuleChainConnections_V_3_3_0(List<RuleNode> nodes, List<NodeConnectionInfo> connections) throws JsonProcessingException {
List<RuleChainConnectionInfo> result = new ArrayList<>();
for (int i = 0; i < nodes.size(); i++) {
RuleNode node = nodes.get(i);
if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE)) {
for (NodeConnectionInfo connection : connections) {
if (connection.getToIndex() == i) {
RuleChainConnectionInfo e = new RuleChainConnectionInfo();
e.setFromIndex(connection.getFromIndex());
TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class);
e.setTargetRuleChainId(new RuleChainId(UUID.fromString(configuration.getRuleChainId())));
e.setAdditionalInfo(node.getAdditionalInfo());
e.setType(connection.getType());
result.add(e);
}
}
}
}
return result;
}
private List<RuleChainConnectionInfoProto> constructRuleChainConnections(List<RuleChainConnectionInfo> ruleChainConnections, NavigableSet<Integer> removedNodeIndexes) throws JsonProcessingException {
List<RuleChainConnectionInfoProto> result = new ArrayList<>(); List<RuleChainConnectionInfoProto> result = new ArrayList<>();
if (ruleChainConnections != null && !ruleChainConnections.isEmpty()) { if (ruleChainConnections != null && !ruleChainConnections.isEmpty()) {
for (RuleChainConnectionInfo ruleChainConnectionInfo : ruleChainConnections) { for (RuleChainConnectionInfo ruleChainConnectionInfo : ruleChainConnections) {
result.add(constructRuleChainConnection(ruleChainConnectionInfo)); result.add(constructRuleChainConnection(ruleChainConnectionInfo, removedNodeIndexes));
} }
} }
return result; return result;
} }
private RuleChainConnectionInfoProto constructRuleChainConnection(RuleChainConnectionInfo ruleChainConnectionInfo) throws JsonProcessingException { private RuleChainConnectionInfoProto constructRuleChainConnection(RuleChainConnectionInfo ruleChainConnectionInfo, NavigableSet<Integer> removedNodeIndexes) throws JsonProcessingException {
int fromIndex = ruleChainConnectionInfo.getFromIndex();
// decrease index because of removed nodes
for (Integer removedIndex : removedNodeIndexes) {
if (fromIndex > removedIndex) {
fromIndex = fromIndex - 1;
}
}
ObjectNode additionalInfo = (ObjectNode) ruleChainConnectionInfo.getAdditionalInfo();
if (additionalInfo.get("ruleChainNodeId") == null) {
additionalInfo.put("ruleChainNodeId", "rule-chain-node-UNDEFINED");
}
return RuleChainConnectionInfoProto.newBuilder() return RuleChainConnectionInfoProto.newBuilder()
.setFromIndex(ruleChainConnectionInfo.getFromIndex()) .setFromIndex(fromIndex)
.setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits()) .setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits())
.setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits()) .setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits())
.setType(ruleChainConnectionInfo.getType()) .setType(ruleChainConnectionInfo.getType())
.setAdditionalInfo(objectMapper.writeValueAsString(ruleChainConnectionInfo.getAdditionalInfo())) .setAdditionalInfo(objectMapper.writeValueAsString(additionalInfo))
.build(); .build();
} }

32
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java

@ -19,7 +19,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -28,26 +28,32 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
@TbCoreComponent @TbCoreComponent
public class WidgetTypeMsgConstructor { public class WidgetTypeMsgConstructor {
public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetType widgetType) { public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails) {
WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder()
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(widgetType.getId().getId().getMostSignificantBits()) .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits())
.setIdLSB(widgetType.getId().getId().getLeastSignificantBits()); .setIdLSB(widgetTypeDetails.getId().getId().getLeastSignificantBits());
if (widgetType.getBundleAlias() != null) { if (widgetTypeDetails.getBundleAlias() != null) {
builder.setBundleAlias(widgetType.getBundleAlias()); builder.setBundleAlias(widgetTypeDetails.getBundleAlias());
} }
if (widgetType.getAlias() != null) { if (widgetTypeDetails.getAlias() != null) {
builder.setAlias(widgetType.getAlias()); builder.setAlias(widgetTypeDetails.getAlias());
} }
if (widgetType.getName() != null) { if (widgetTypeDetails.getName() != null) {
builder.setName(widgetType.getName()); builder.setName(widgetTypeDetails.getName());
} }
if (widgetType.getDescriptor() != null) { if (widgetTypeDetails.getDescriptor() != null) {
builder.setDescriptorJson(JacksonUtil.toString(widgetType.getDescriptor())); builder.setDescriptorJson(JacksonUtil.toString(widgetTypeDetails.getDescriptor()));
} }
if (widgetType.getTenantId().equals(TenantId.SYS_TENANT_ID)) { if (widgetTypeDetails.getTenantId().equals(TenantId.SYS_TENANT_ID)) {
builder.setIsSystem(true); builder.setIsSystem(true);
} }
if (widgetTypeDetails.getImage() != null) {
builder.setImage(widgetTypeDetails.getImage());
}
if (widgetTypeDetails.getDescription() != null) {
builder.setDescription(widgetTypeDetails.getDescription());
}
return builder.build(); return builder.build();
} }

14
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java

@ -15,25 +15,13 @@
*/ */
package org.thingsboard.server.service.edge.rpc.fetch; package org.thingsboard.server.service.edge.rpc.fetch;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.HasUUID;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.service.edge.rpc.EdgeEventUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

5
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
@ -63,14 +64,14 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor {
return downlinkMsg; return downlinkMsg;
} }
public DownlinkMsg processRuleChainMetadataToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType) { public DownlinkMsg processRuleChainMetadataToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeVersion edgeVersion) {
RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId());
RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId); RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId);
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
if (ruleChain != null) { if (ruleChain != null) {
RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId);
RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg =
ruleChainMsgConstructor.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData); ruleChainMsgConstructor.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData, edgeVersion);
if (ruleChainMetadataUpdateMsg != null) { if (ruleChainMetadataUpdateMsg != null) {
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())

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

@ -276,7 +276,7 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
case DASHBOARD: case DASHBOARD:
return new DashboardId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); return new DashboardId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
case TENANT: case TENANT:
return new TenantId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); return TenantId.fromUUID(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
case CUSTOMER: case CUSTOMER:
return new CustomerId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); return new CustomerId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB()));
case USER: case USER:
@ -303,7 +303,7 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor {
entityId = new DashboardId(edgeEvent.getEntityId()); entityId = new DashboardId(edgeEvent.getEntityId());
break; break;
case TENANT: case TENANT:
entityId = new TenantId(edgeEvent.getEntityId()); entityId = TenantId.fromUUID(edgeEvent.getEntityId());
break; break;
case CUSTOMER: case CUSTOMER:
entityId = new CustomerId(edgeEvent.getEntityId()); entityId = new CustomerId(edgeEvent.getEntityId());

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java

@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
@ -38,10 +38,10 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor {
switch (edgeEdgeEventActionType) { switch (edgeEdgeEventActionType) {
case ADDED: case ADDED:
case UPDATED: case UPDATED:
WidgetType widgetType = widgetTypeService.findWidgetTypeById(edgeEvent.getTenantId(), widgetTypeId); WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(edgeEvent.getTenantId(), widgetTypeId);
if (widgetType != null) { if (widgetTypeDetails != null) {
WidgetTypeUpdateMsg widgetTypeUpdateMsg = WidgetTypeUpdateMsg widgetTypeUpdateMsg =
widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetType); widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails);
downlinkMsg = DownlinkMsg.newBuilder() downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg)

11
application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster; import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.service.install.cql.CQLStatementsParser; import org.thingsboard.server.service.install.cql.CQLStatementsParser;
@ -29,6 +30,7 @@ import java.util.List;
public abstract class CassandraAbstractDatabaseSchemaService implements DatabaseSchemaService { public abstract class CassandraAbstractDatabaseSchemaService implements DatabaseSchemaService {
private static final String CASSANDRA_DIR = "cassandra"; private static final String CASSANDRA_DIR = "cassandra";
private static final String CASSANDRA_STANDARD_KEYSPACE = "thingsboard";
@Autowired @Autowired
@Qualifier("CassandraInstallCluster") @Qualifier("CassandraInstallCluster")
@ -37,6 +39,9 @@ public abstract class CassandraAbstractDatabaseSchemaService implements Database
@Autowired @Autowired
private InstallScripts installScripts; private InstallScripts installScripts;
@Value("${cassandra.keyspace_name}")
private String keyspaceName;
private final String schemaCql; private final String schemaCql;
protected CassandraAbstractDatabaseSchemaService(String schemaCql) { protected CassandraAbstractDatabaseSchemaService(String schemaCql) {
@ -61,6 +66,10 @@ public abstract class CassandraAbstractDatabaseSchemaService implements Database
private void loadCql(Path cql) throws Exception { private void loadCql(Path cql) throws Exception {
List<String> statements = new CQLStatementsParser(cql).getStatements(); List<String> statements = new CQLStatementsParser(cql).getStatements();
statements.forEach(statement -> cluster.getSession().execute(statement)); statements.forEach(statement -> cluster.getSession().execute(getCassandraKeyspaceName(statement)));
}
private String getCassandraKeyspaceName(String statement) {
return statement.replaceFirst(CASSANDRA_STANDARD_KEYSPACE, keyspaceName);
} }
} }

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

@ -99,7 +99,7 @@ public class DatabaseHelper {
} }
} }
for (CustomerId customerId : customerIds) { for (CustomerId customerId : customerIds) {
dashboardService.assignDashboardToCustomer(new TenantId(EntityId.NULL_UUID), dashboardId, customerId); dashboardService.assignDashboardToCustomer(TenantId.SYS_TENANT_ID, dashboardId, customerId);
} }
}); });
} }

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

@ -169,7 +169,7 @@ public class InstallScripts {
ruleChain = ruleChainService.saveRuleChain(ruleChain); ruleChain = ruleChainService.saveRuleChain(ruleChain);
ruleChainMetaData.setRuleChainId(ruleChain.getId()); ruleChainMetaData.setRuleChainId(ruleChain.getId());
ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData); ruleChainService.saveRuleChainMetaData(TenantId.SYS_TENANT_ID, ruleChainMetaData);
return ruleChain; return ruleChain;
} }
@ -217,7 +217,7 @@ public class InstallScripts {
dashboard.setTenantId(tenantId); dashboard.setTenantId(tenantId);
Dashboard savedDashboard = dashboardService.saveDashboard(dashboard); Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
if (customerId != null && !customerId.isNullUid()) { if (customerId != null && !customerId.isNullUid()) {
dashboardService.assignDashboardToCustomer(new TenantId(EntityId.NULL_UUID), savedDashboard.getId(), customerId); dashboardService.assignDashboardToCustomer(TenantId.SYS_TENANT_ID, savedDashboard.getId(), customerId);
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Unable to load dashboard from json: [{}]", path.toString()); log.error("Unable to load dashboard from json: [{}]", path.toString());

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

@ -43,6 +43,7 @@ import java.sql.SQLSyntaxErrorException;
import java.sql.SQLWarning; import java.sql.SQLWarning;
import java.sql.Statement; import java.sql.Statement;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS; import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
@ -469,6 +470,42 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e); log.error("Failed updating schema!!!", e);
} }
break; break;
case "3.3.2":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("insert into entity_alarm(tenant_id, entity_id, created_time, alarm_type, customer_id, alarm_id)" +
" select tenant_id, originator_id, created_time, type, customer_id, id from alarm ON CONFLICT DO NOTHING;");
conn.createStatement().execute("insert into entity_alarm(tenant_id, entity_id, created_time, alarm_type, customer_id, alarm_id)" +
" select a.tenant_id, r.from_id, created_time, type, customer_id, id" +
" from alarm a inner join relation r on r.relation_type_group = 'ALARM' and r.relation_type = 'ANY' and a.id = r.to_id ON CONFLICT DO NOTHING;");
conn.createStatement().execute("delete from relation r where r.relation_type_group = 'ALARM';");
} catch (Exception e) {
log.error("Failed to update alarm relations!!!", e);
}
log.info("Updating lwm2m device profiles ...");
try {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", "schema_update_lwm2m_bootstrap.sql");
loadSql(schemaUpdateFile, conn);
log.info("Updating server`s public key from HexDec to Base64 in profile for LWM2M...");
conn.createStatement().execute("call update_profile_bootstrap();");
log.info("Server`s public key from HexDec to Base64 in profile for LWM2M updated.");
log.info("Updating client`s public key and secret key from HexDec to Base64 for LWM2M...");
conn.createStatement().execute("call update_device_credentials_to_base64_and_bootstrap();");
log.info("Client`s public key and secret key from HexDec to Base64 for LWM2M updated.");
} catch (Exception e) {
log.error("Failed to update lwm2m profiles!!!", e);
}
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003003;");
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }
@ -476,10 +513,25 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
private void loadSql(Path sqlFile, Connection conn) throws Exception { private void loadSql(Path sqlFile, Connection conn) throws Exception {
String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8")); String sql = new String(Files.readAllBytes(sqlFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script Statement st = conn.createStatement();
st.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(3));
st.execute(sql);//NOSONAR, ignoring because method used to execute thingsboard database upgrade script
printWarnings(st);
Thread.sleep(5000); Thread.sleep(5000);
} }
protected void printWarnings(Statement statement) throws SQLException {
SQLWarning warnings = statement.getWarnings();
if (warnings != null) {
log.info("{}", warnings.getMessage());
SQLWarning nextWarning = warnings.getNextWarning();
while (nextWarning != null) {
log.info("{}", nextWarning.getMessage());
nextWarning = nextWarning.getNextWarning();
}
}
}
protected boolean isOldSchema(Connection conn, long fromVersion) { protected boolean isOldSchema(Connection conn, long fromVersion) {
boolean isOldSchema = true; boolean isOldSchema = true;
try { try {

6
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -57,6 +57,12 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
clearCacheByName("tenantProfiles"); clearCacheByName("tenantProfiles");
clearCacheByName("relations"); clearCacheByName("relations");
break; break;
case "3.3.2":
log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ...");
clearCacheByName("devices");
clearCacheByName("deviceProfiles");
clearCacheByName("tenantProfiles");
break;
default: default:
//Do nothing, since cache cleanup is optional. //Do nothing, since cache cleanup is optional.
} }

84
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -25,6 +25,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
@ -34,6 +36,8 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@ -43,8 +47,11 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
@ -52,7 +59,9 @@ import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
@ -76,6 +85,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private TenantService tenantService; private TenantService tenantService;
@Autowired
private RelationService relationService;
@Autowired @Autowired
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@ -125,6 +137,10 @@ public class DefaultDataUpdateService implements DataUpdateService {
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null); deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params(); updateOAuth2Params();
break; break;
case "3.3.2":
log.info("Updating data from version 3.3.2 to 3.3.3 ...");
updateNestedRuleChains();
break;
default: default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
} }
@ -209,6 +225,74 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
}; };
private void updateNestedRuleChains() {
try {
var packSize = 1024;
var updated = 0;
boolean hasNext = true;
while (hasNext) {
List<EntityRelation> relations = relationService.findRuleNodeToRuleChainRelations(TenantId.SYS_TENANT_ID, RuleChainType.CORE, packSize);
hasNext = relations.size() == packSize;
for (EntityRelation relation : relations) {
try {
RuleNodeId sourceNodeId = new RuleNodeId(relation.getFrom().getId());
RuleNode sourceNode = ruleChainService.findRuleNodeById(TenantId.SYS_TENANT_ID, sourceNodeId);
if (sourceNode == null) {
log.info("Skip processing of relation for non existing source rule node: [{}]", sourceNodeId);
relationService.deleteRelation(TenantId.SYS_TENANT_ID, relation);
continue;
}
RuleChainId sourceRuleChainId = sourceNode.getRuleChainId();
RuleChainId targetRuleChainId = new RuleChainId(relation.getTo().getId());
RuleChain targetRuleChain = ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, targetRuleChainId);
if (targetRuleChain == null) {
log.info("Skip processing of relation for non existing target rule chain: [{}]", targetRuleChainId);
relationService.deleteRelation(TenantId.SYS_TENANT_ID, relation);
continue;
}
TenantId tenantId = targetRuleChain.getTenantId();
RuleNode targetNode = new RuleNode();
targetNode.setName(targetRuleChain.getName());
targetNode.setRuleChainId(sourceRuleChainId);
targetNode.setType(TbRuleChainInputNode.class.getName());
TbRuleChainInputNodeConfiguration configuration = new TbRuleChainInputNodeConfiguration();
configuration.setRuleChainId(targetRuleChain.getId().toString());
targetNode.setConfiguration(JacksonUtil.valueToTree(configuration));
targetNode.setAdditionalInfo(relation.getAdditionalInfo());
targetNode.setDebugMode(false);
targetNode = ruleChainService.saveRuleNode(tenantId, targetNode);
EntityRelation sourceRuleChainToRuleNode = new EntityRelation();
sourceRuleChainToRuleNode.setFrom(sourceRuleChainId);
sourceRuleChainToRuleNode.setTo(targetNode.getId());
sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE);
sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN);
relationService.saveRelation(tenantId, sourceRuleChainToRuleNode);
EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation();
sourceRuleNodeToTargetRuleNode.setFrom(sourceNode.getId());
sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId());
sourceRuleNodeToTargetRuleNode.setType(relation.getType());
sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE);
sourceRuleNodeToTargetRuleNode.setAdditionalInfo(relation.getAdditionalInfo());
relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode);
//Delete old relation
relationService.deleteRelation(tenantId, relation);
updated++;
} catch (Exception e) {
log.info("Failed to update RuleNodeToRuleChainRelation: {}", relation, e);
}
}
if (updated > 0) {
log.info("RuleNodeToRuleChainRelations: {} entities updated so far...", updated);
}
}
} catch (Exception e) {
log.error("Unable to update Tenant", e);
}
}
private final PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsDefaultEdgeRuleChainUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {

70
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java

@ -1,70 +0,0 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.lwm2m;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.util.Hex;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'")
public class LwM2MServerSecurityInfoRepository {
private final LwM2MTransportServerConfig serverConfig;
private final LwM2MTransportBootstrapConfig bootstrapConfig;
public ServerSecurityConfig getServerSecurityInfo(boolean bootstrapServer) {
ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig);
result.setBootstrapServerIs(bootstrapServer);
return result;
}
private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig) {
ServerSecurityConfig bsServ = new ServerSecurityConfig();
bsServ.setServerId(serverConfig.getId());
bsServ.setHost(serverConfig.getHost());
bsServ.setPort(serverConfig.getPort());
bsServ.setSecurityHost(serverConfig.getSecureHost());
bsServ.setSecurityPort(serverConfig.getSecurePort());
bsServ.setServerPublicKey(getPublicKey(serverConfig));
return bsServ;
}
private String getPublicKey(LwM2MSecureServerConfig config) {
try {
SslCredentials sslCredentials = config.getSslCredentials();
if (sslCredentials != null) {
return Hex.encodeHexString(sslCredentials.getPublicKey().getEncoded());
}
} catch (Exception e) {
log.trace("Failed to fetch public key from key store!", e);
}
return "";
}
}

24
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MService.java

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

99
application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServiceImpl.java

@ -0,0 +1,99 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.lwm2m;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MServerSecurityConfigDefault;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import java.util.Optional;
@Slf4j
@Service
@RequiredArgsConstructor
@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${transport.lwm2m.enabled:false}'=='true'")
public class LwM2MServiceImpl implements LwM2MService {
private final LwM2MTransportServerConfig serverConfig;
private final Optional<LwM2MTransportBootstrapConfig> bootstrapConfig;
@Override
public LwM2MServerSecurityConfigDefault getServerSecurityInfo(boolean bootstrapServer) {
LwM2MSecureServerConfig bsServerConfig = bootstrapServer ? bootstrapConfig.orElse(null) : serverConfig;
if (bsServerConfig!= null) {
LwM2MServerSecurityConfigDefault result = getServerSecurityConfig(bsServerConfig);
result.setBootstrapServerIs(bootstrapServer);
return result;
}
else {
return null;
}
}
private LwM2MServerSecurityConfigDefault getServerSecurityConfig(LwM2MSecureServerConfig bsServerConfig) {
LwM2MServerSecurityConfigDefault bsServ = new LwM2MServerSecurityConfigDefault();
bsServ.setShortServerId(bsServerConfig.getId());
bsServ.setHost(bsServerConfig.getHost());
bsServ.setPort(bsServerConfig.getPort());
bsServ.setSecurityHost(bsServerConfig.getSecureHost());
bsServ.setSecurityPort(bsServerConfig.getSecurePort());
byte[] publicKeyBase64 = getPublicKey(bsServerConfig);
if (publicKeyBase64 == null) {
bsServ.setServerPublicKey("");
} else {
bsServ.setServerPublicKey(Base64.encodeBase64String(publicKeyBase64));
}
byte[] certificateBase64 = getCertificate(bsServerConfig);
if (certificateBase64 == null) {
bsServ.setServerCertificate("");
} else {
bsServ.setServerCertificate(Base64.encodeBase64String(certificateBase64));
}
return bsServ;
}
private byte[] getPublicKey(LwM2MSecureServerConfig config) {
try {
SslCredentials sslCredentials = config.getSslCredentials();
if (sslCredentials != null) {
return sslCredentials.getPublicKey().getEncoded();
}
} catch (Exception e) {
log.trace("Failed to fetch public key from key store!", e);
}
return null;
}
private byte[] getCertificate(LwM2MSecureServerConfig config) {
try {
SslCredentials sslCredentials = config.getSslCredentials();
if (sslCredentials != null) {
return sslCredentials.getCertificateChain()[0].getEncoded();
}
} catch (Exception e) {
log.trace("Failed to fetch certificate from key store!", e);
}
return null;
}
}

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

@ -95,7 +95,7 @@ public class DefaultMailService implements MailService {
@Override @Override
public void updateMailConfiguration() { public void updateMailConfiguration() {
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "mail"); AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
if (settings != null) { if (settings != null) {
JsonNode jsonConfig = settings.getJsonValue(); JsonNode jsonConfig = settings.getJsonValue();
mailSender = createMailSender(jsonConfig); mailSender = createMailSender(jsonConfig);

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

@ -17,11 +17,11 @@ package org.thingsboard.server.service.ota;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
@ -51,8 +51,6 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -122,17 +120,17 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
newFirmwareId = newDeviceProfile.getFirmwareId(); newFirmwareId = newDeviceProfile.getFirmwareId();
} }
if (oldDevice != null) { if (oldDevice != null) {
OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
if (oldFirmwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldFirmwareId = oldDeviceProfile.getFirmwareId();
}
if (newFirmwareId != null) { if (newFirmwareId != null) {
OtaPackageId oldFirmwareId = oldDevice.getFirmwareId();
if (oldFirmwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldFirmwareId = oldDeviceProfile.getFirmwareId();
}
if (!newFirmwareId.equals(oldFirmwareId)) { if (!newFirmwareId.equals(oldFirmwareId)) {
// Device was updated and new firmware is different from previous firmware. // Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE); send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE);
} }
} else { } else if (oldFirmwareId != null){
// Device was updated and new firmware is not set. // Device was updated and new firmware is not set.
remove(device, FIRMWARE); remove(device, FIRMWARE);
} }
@ -149,17 +147,17 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
newSoftwareId = newDeviceProfile.getSoftwareId(); newSoftwareId = newDeviceProfile.getSoftwareId();
} }
if (oldDevice != null) { if (oldDevice != null) {
OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
if (oldSoftwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldSoftwareId = oldDeviceProfile.getSoftwareId();
}
if (newSoftwareId != null) { if (newSoftwareId != null) {
OtaPackageId oldSoftwareId = oldDevice.getSoftwareId();
if (oldSoftwareId == null) {
DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId());
oldSoftwareId = oldDeviceProfile.getSoftwareId();
}
if (!newSoftwareId.equals(oldSoftwareId)) { if (!newSoftwareId.equals(oldSoftwareId)) {
// Device was updated and new firmware is different from previous firmware. // Device was updated and new firmware is different from previous firmware.
send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE); send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE);
} }
} else { } else if (oldSoftwareId != null){
// Device was updated and new firmware is not set. // Device was updated and new firmware is not set.
remove(device, SOFTWARE); remove(device, SOFTWARE);
} }
@ -208,7 +206,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
boolean isSuccess = false; boolean isSuccess = false;
OtaPackageId targetOtaPackageId = new OtaPackageId(new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB())); OtaPackageId targetOtaPackageId = new OtaPackageId(new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
OtaPackageType firmwareType = OtaPackageType.valueOf(msg.getType()); OtaPackageType firmwareType = OtaPackageType.valueOf(msg.getType());
long ts = msg.getTs(); long ts = msg.getTs();
@ -302,14 +300,16 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) { private void updateAttributes(Device device, OtaPackageInfo otaPackage, long ts, TenantId tenantId, DeviceId deviceId, OtaPackageType otaPackageType) {
List<AttributeKvEntry> attributes = new ArrayList<>(); List<AttributeKvEntry> attributes = new ArrayList<>();
List<String> attrToRemove = new ArrayList<>();
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
if (StringUtils.isNotEmpty(otaPackage.getTag())) { if (StringUtils.isNotEmpty(otaPackage.getTag())) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TAG), otaPackage.getTag()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TAG), otaPackage.getTag())));
} else {
attrToRemove.add(getAttributeKey(otaPackageType, TAG));
} }
if (otaPackage.hasUrl()) { if (otaPackage.hasUrl()) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
List<String> attrToRemove = new ArrayList<>();
if (otaPackage.getDataSize() == null) { if (otaPackage.getDataSize() == null) {
attrToRemove.add(getAttributeKey(otaPackageType, SIZE)); attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
@ -328,15 +328,15 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
} else { } else {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
} }
remove(device, otaPackageType, attrToRemove);
} else { } else {
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize()))); attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL))); attrToRemove.add(getAttributeKey(otaPackageType, URL));
} }
remove(device, otaPackageType, attrToRemove);
telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable Void tmp) { public void onSuccess(@Nullable Void tmp) {

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

@ -140,7 +140,7 @@ public class DefaultTbClusterService implements TbClusterService {
public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) {
if (tenantId.isNullUid()) { if (tenantId.isNullUid()) {
if (entityId.getEntityType().equals(EntityType.TENANT)) { if (entityId.getEntityType().equals(EntityType.TENANT)) {
tenantId = new TenantId(entityId.getId()); tenantId = TenantId.fromUUID(entityId.getId());
} else { } else {
log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg); log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg);
return; return;

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

@ -26,14 +26,15 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
@ -47,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
@ -64,7 +66,6 @@ import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair; import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@ -452,31 +453,37 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (msg.hasTsUpdate()) { } else if (msg.hasTsUpdate()) {
TbTimeSeriesUpdateProto proto = msg.getTsUpdate(); TbTimeSeriesUpdateProto proto = msg.getTsUpdate();
subscriptionManagerService.onTimeSeriesUpdate( subscriptionManagerService.onTimeSeriesUpdate(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback); TbSubscriptionUtils.toTsKvEntityList(proto.getDataList()), callback);
} else if (msg.hasAttrUpdate()) { } else if (msg.hasAttrUpdate()) {
TbAttributeUpdateProto proto = msg.getAttrUpdate(); TbAttributeUpdateProto proto = msg.getAttrUpdate();
subscriptionManagerService.onAttributesUpdate( subscriptionManagerService.onAttributesUpdate(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback); proto.getScope(), TbSubscriptionUtils.toAttributeKvList(proto.getDataList()), callback);
} else if (msg.hasAttrDelete()) { } else if (msg.hasAttrDelete()) {
TbAttributeDeleteProto proto = msg.getAttrDelete(); TbAttributeDeleteProto proto = msg.getAttrDelete();
subscriptionManagerService.onAttributesDelete( subscriptionManagerService.onAttributesDelete(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getScope(), proto.getKeysList(), callback); proto.getScope(), proto.getKeysList(), callback);
} else if (msg.hasTsDelete()) {
TbTimeSeriesDeleteProto proto = msg.getTsDelete();
subscriptionManagerService.onTimeSeriesDelete(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
proto.getKeysList(), callback);
} else if (msg.hasAlarmUpdate()) { } else if (msg.hasAlarmUpdate()) {
TbAlarmUpdateProto proto = msg.getAlarmUpdate(); TbAlarmUpdateProto proto = msg.getAlarmUpdate();
subscriptionManagerService.onAlarmUpdate( subscriptionManagerService.onAlarmUpdate(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback); JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
} else if (msg.hasAlarmDelete()) { } else if (msg.hasAlarmDelete()) {
TbAlarmDeleteProto proto = msg.getAlarmDelete(); TbAlarmDeleteProto proto = msg.getAlarmDelete();
subscriptionManagerService.onAlarmDeleted( subscriptionManagerService.onAlarmDeleted(
new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback); JacksonUtil.fromString(proto.getAlarm(), Alarm.class), callback);
} else { } else {

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

@ -257,7 +257,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
final TbRuleEngineProcessingStrategy ackStrategy = getAckStrategy(configuration); final TbRuleEngineProcessingStrategy ackStrategy = getAckStrategy(configuration);
submitStrategy.init(msgs); submitStrategy.init(msgs);
while (!stopped) { while (!stopped) {
TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(configuration.getName(), submitStrategy); TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(configuration.getName(), submitStrategy, ackStrategy.isSkipTimeoutMsgs());
submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> submitMessage(configuration, stats, ctx, id, msg))); submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> submitMessage(configuration, stats, ctx, id, msg)));
final boolean timeout = !ctx.await(configuration.getPackProcessingTimeout(), TimeUnit.MILLISECONDS); final boolean timeout = !ctx.await(configuration.getPackProcessingTimeout(), TimeUnit.MILLISECONDS);
@ -321,7 +321,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
void submitMessage(TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats, TbMsgPackProcessingContext ctx, UUID id, TbProtoQueueMsg<ToRuleEngineMsg> msg) { void submitMessage(TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats, TbMsgPackProcessingContext ctx, UUID id, TbProtoQueueMsg<ToRuleEngineMsg> msg) {
log.trace("[{}] Creating callback for topic {} message: {}", id, configuration.getName(), msg.getValue()); log.trace("[{}] Creating callback for topic {} message: {}", id, configuration.getName(), msg.getValue());
ToRuleEngineMsg toRuleEngineMsg = msg.getValue(); ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
TenantId tenantId = new TenantId(new UUID(toRuleEngineMsg.getTenantIdMSB(), toRuleEngineMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(toRuleEngineMsg.getTenantIdMSB(), toRuleEngineMsg.getTenantIdLSB()));
TbMsgCallback callback = prometheusStatsEnabled ? TbMsgCallback callback = prometheusStatsEnabled ?
new TbMsgPackCallback(id, tenantId, ctx, stats.getTimer(tenantId, SUCCESSFUL_STATUS), stats.getTimer(tenantId, FAILED_STATUS)) : new TbMsgPackCallback(id, tenantId, ctx, stats.getTimer(tenantId, SUCCESSFUL_STATUS), stats.getTimer(tenantId, FAILED_STATUS)) :
new TbMsgPackCallback(id, tenantId, ctx); new TbMsgPackCallback(id, tenantId, ctx);
@ -344,9 +344,9 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbMsg tmpMsg = TbMsg.fromBytes(configuration.getName(), tmp.getTbMsg().toByteArray(), TbMsgCallback.EMPTY); TbMsg tmpMsg = TbMsg.fromBytes(configuration.getName(), tmp.getTbMsg().toByteArray(), TbMsgCallback.EMPTY);
RuleNodeInfo ruleNodeInfo = ctx.getLastVisitedRuleNode(pending.getKey()); RuleNodeInfo ruleNodeInfo = ctx.getLastVisitedRuleNode(pending.getKey());
if (printAll) { if (printAll) {
log.trace("[{}] {} to process message: {}, Last Rule Node: {}", new TenantId(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo); log.trace("[{}] {} to process message: {}, Last Rule Node: {}", TenantId.fromUUID(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo);
} else { } else {
log.info("[{}] {} to process message: {}, Last Rule Node: {}", new TenantId(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo); log.info("[{}] {} to process message: {}, Last Rule Node: {}", TenantId.fromUUID(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo);
break; break;
} }
} }

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

@ -66,6 +66,11 @@ public class TbMsgPackCallback implements TbMsgCallback {
ctx.onFailure(tenantId, id, e); ctx.onFailure(tenantId, id, e);
} }
@Override
public boolean isMsgValid() {
return !ctx.isCanceled();
}
@Override @Override
public void onProcessingStart(RuleNodeInfo ruleNodeInfo) { public void onProcessingStart(RuleNodeInfo ruleNodeInfo) {
log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo); log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo);

11
application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java

@ -39,6 +39,7 @@ public class TbMsgPackProcessingContext {
private final String queueName; private final String queueName;
private final TbRuleEngineSubmitStrategy submitStrategy; private final TbRuleEngineSubmitStrategy submitStrategy;
private final boolean skipTimeoutMsgsPossible;
@Getter @Getter
private final boolean profilerEnabled; private final boolean profilerEnabled;
private final AtomicInteger pendingCount; private final AtomicInteger pendingCount;
@ -54,9 +55,12 @@ public class TbMsgPackProcessingContext {
private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>(); private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) { private volatile boolean canceled = false;
public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy, boolean skipTimeoutMsgsPossible) {
this.queueName = queueName; this.queueName = queueName;
this.submitStrategy = submitStrategy; this.submitStrategy = submitStrategy;
this.skipTimeoutMsgsPossible = skipTimeoutMsgsPossible;
this.profilerEnabled = log.isDebugEnabled(); this.profilerEnabled = log.isDebugEnabled();
this.pendingMap = submitStrategy.getPendingMap(); this.pendingMap = submitStrategy.getPendingMap();
this.pendingCount = new AtomicInteger(pendingMap.size()); this.pendingCount = new AtomicInteger(pendingMap.size());
@ -149,8 +153,13 @@ public class TbMsgPackProcessingContext {
} }
public void cleanup() { public void cleanup() {
canceled = true;
pendingMap.clear(); pendingMap.clear();
successMap.clear(); successMap.clear();
failedMap.clear(); failedMap.clear();
} }
public boolean isCanceled() {
return skipTimeoutMsgsPossible && canceled;
}
} }

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

@ -29,6 +29,6 @@ public class SequentialByTenantIdTbRuleEngineSubmitStrategy extends SequentialBy
@Override @Override
protected EntityId getEntityId(TransportProtos.ToRuleEngineMsg msg) { protected EntityId getEntityId(TransportProtos.ToRuleEngineMsg msg) {
return new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); return TenantId.fromUUID(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
} }
} }

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

@ -17,6 +17,8 @@ package org.thingsboard.server.service.queue.processing;
public interface TbRuleEngineProcessingStrategy { public interface TbRuleEngineProcessingStrategy {
boolean isSkipTimeoutMsgs();
TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result); TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result);
} }

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

@ -36,7 +36,9 @@ public class TbRuleEngineProcessingStrategyFactory {
public TbRuleEngineProcessingStrategy newInstance(String name, TbRuleEngineQueueAckStrategyConfiguration configuration) { public TbRuleEngineProcessingStrategy newInstance(String name, TbRuleEngineQueueAckStrategyConfiguration configuration) {
switch (configuration.getType()) { switch (configuration.getType()) {
case "SKIP_ALL_FAILURES": case "SKIP_ALL_FAILURES":
return new SkipStrategy(name); return new SkipStrategy(name, false);
case "SKIP_ALL_FAILURES_AND_TIMED_OUT":
return new SkipStrategy(name, true);
case "RETRY_ALL": case "RETRY_ALL":
return new RetryStrategy(name, true, true, true, configuration); return new RetryStrategy(name, true, true, true, configuration);
case "RETRY_FAILED": case "RETRY_FAILED":
@ -75,6 +77,11 @@ public class TbRuleEngineProcessingStrategyFactory {
this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries(); this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries();
} }
@Override
public boolean isSkipTimeoutMsgs() {
return true;
}
@Override @Override
public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) { public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) {
if (result.isSuccess()) { if (result.isSuccess()) {
@ -139,9 +146,16 @@ public class TbRuleEngineProcessingStrategyFactory {
private static class SkipStrategy implements TbRuleEngineProcessingStrategy { private static class SkipStrategy implements TbRuleEngineProcessingStrategy {
private final String queueName; private final String queueName;
private final boolean skipTimeoutMsgs;
public SkipStrategy(String name) { public SkipStrategy(String name, boolean skipTimeoutMsgs) {
this.queueName = name; this.queueName = name;
this.skipTimeoutMsgs = skipTimeoutMsgs;
}
@Override
public boolean isSkipTimeoutMsgs() {
return skipTimeoutMsgs;
} }
@Override @Override

191
application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java

@ -0,0 +1,191 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rule;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
@TbCoreComponent
@Slf4j
public class DefaultTbRuleChainService implements TbRuleChainService {
private final RuleChainService ruleChainService;
private final RelationService relationService;
@Override
public Set<String> getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId) {
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId);
Set<String> outputLabels = new TreeSet<>();
for (RuleNode ruleNode : metaData.getNodes()) {
if (isOutputRuleNode(ruleNode)) {
outputLabels.add(ruleNode.getName());
}
}
return outputLabels;
}
@Override
public List<RuleChainOutputLabelsUsage> getOutputLabelUsage(TenantId tenantId, RuleChainId ruleChainId) {
List<RuleNode> ruleNodes = ruleChainService.findRuleNodesByTenantIdAndType(tenantId, TbRuleChainInputNode.class.getName(), ruleChainId.getId().toString());
Map<RuleChainId, String> ruleChainNamesCache = new HashMap<>();
// Additional filter, "just in case" the structure of the JSON configuration will change.
var filteredRuleNodes = ruleNodes.stream().filter(node -> {
try {
TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class);
return ruleChainId.getId().toString().equals(configuration.getRuleChainId());
} catch (Exception e) {
log.warn("[{}][{}] Failed to decode rule node configuration", tenantId, ruleChainId, e);
return false;
}
}).collect(Collectors.toList());
return filteredRuleNodes.stream()
.map(ruleNode -> {
RuleChainOutputLabelsUsage usage = new RuleChainOutputLabelsUsage();
usage.setRuleNodeId(ruleNode.getId());
usage.setRuleNodeName(ruleNode.getName());
usage.setRuleChainId(ruleNode.getRuleChainId());
List<EntityRelation> relations = ruleChainService.getRuleNodeRelations(tenantId, ruleNode.getId());
if (relations != null && !relations.isEmpty()) {
usage.setLabels(relations.stream().map(EntityRelation::getType).collect(Collectors.toSet()));
}
return usage;
})
.filter(usage -> usage.getLabels() != null)
.peek(usage -> {
String ruleChainName = ruleChainNamesCache.computeIfAbsent(usage.getRuleChainId(),
id -> ruleChainService.findRuleChainById(tenantId, id).getName());
usage.setRuleChainName(ruleChainName);
})
.sorted(Comparator
.comparing(RuleChainOutputLabelsUsage::getRuleChainName)
.thenComparing(RuleChainOutputLabelsUsage::getRuleNodeName))
.collect(Collectors.toList());
}
@Override
public List<RuleChain> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result) {
Set<RuleChainId> ruleChainIds = new HashSet<>();
log.debug("[{}][{}] Going to update links in related rule chains", tenantId, ruleChainId);
if (result.getUpdatedRuleNodes() == null || result.getUpdatedRuleNodes().isEmpty()) {
return Collections.emptyList();
}
Set<String> oldLabels = new HashSet<>();
Set<String> newLabels = new HashSet<>();
Set<String> confusedLabels = new HashSet<>();
Map<String, String> updatedLabels = new HashMap<>();
for (RuleNodeUpdateResult update : result.getUpdatedRuleNodes()) {
var oldNode = update.getOldRuleNode();
var newNode = update.getNewRuleNode();
if (isOutputRuleNode(newNode)) {
try {
oldLabels.add(oldNode.getName());
newLabels.add(newNode.getName());
if (!oldNode.getName().equals(newNode.getName())) {
String oldLabel = oldNode.getName();
String newLabel = newNode.getName();
if (updatedLabels.containsKey(oldLabel) && !updatedLabels.get(oldLabel).equals(newLabel)) {
confusedLabels.add(oldLabel);
log.warn("[{}][{}] Can't automatically rename the label from [{}] to [{}] due to conflict [{}]", tenantId, ruleChainId, oldLabel, newLabel, updatedLabels.get(oldLabel));
} else {
updatedLabels.put(oldLabel, newLabel);
}
}
} catch (Exception e) {
log.warn("[{}][{}][{}] Failed to decode rule node configuration", tenantId, ruleChainId, newNode.getId(), e);
}
}
}
// Remove all output labels that are renamed to two or more different labels, since we don't which new label to use;
confusedLabels.forEach(updatedLabels::remove);
// Remove all output labels that are renamed but still present in the rule chain;
newLabels.forEach(updatedLabels::remove);
if (!oldLabels.equals(newLabels)) {
ruleChainIds.addAll(updateRelatedRuleChains(tenantId, ruleChainId, updatedLabels));
}
return ruleChainIds.stream().map(id -> ruleChainService.findRuleChainById(tenantId, id)).collect(Collectors.toList());
}
public Set<RuleChainId> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, Map<String, String> labelsMap) {
Set<RuleChainId> updatedRuleChains = new HashSet<>();
List<RuleChainOutputLabelsUsage> usageList = getOutputLabelUsage(tenantId, ruleChainId);
for (RuleChainOutputLabelsUsage usage : usageList) {
labelsMap.forEach((oldLabel, newLabel) -> {
if (usage.getLabels().contains(oldLabel)) {
updatedRuleChains.add(usage.getRuleChainId());
renameOutgoingLinks(tenantId, usage.getRuleNodeId(), oldLabel, newLabel);
}
});
}
return updatedRuleChains;
}
private void renameOutgoingLinks(TenantId tenantId, RuleNodeId ruleNodeId, String oldLabel, String newLabel) {
List<EntityRelation> relations = ruleChainService.getRuleNodeRelations(tenantId, ruleNodeId);
for (EntityRelation relation : relations) {
if (relation.getType().equals(oldLabel)) {
relationService.deleteRelation(tenantId, relation);
relation.setType(newLabel);
relationService.saveRelation(tenantId, relation);
}
}
}
private boolean isOutputRuleNode(RuleNode ruleNode) {
return isRuleNode(ruleNode, TbRuleChainOutputNode.class);
}
private boolean isInputRuleNode(RuleNode ruleNode) {
return isRuleNode(ruleNode, TbRuleChainInputNode.class);
}
private boolean isRuleNode(RuleNode ruleNode, Class<?> clazz) {
return ruleNode != null && ruleNode.getType().equals(clazz.getName());
}
}

34
application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.rule;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import java.util.List;
import java.util.Set;
public interface TbRuleChainService {
Set<String> getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId);
List<RuleChainOutputLabelsUsage> getOutputLabelUsage(TenantId tenantId, RuleChainId ruleChainId);
List<RuleChain> updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result);
}

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

@ -450,7 +450,7 @@ public class AccessValidator {
} else if (currentUser.isSystemAdmin()) { } else if (currentUser.isSystemAdmin()) {
callback.onSuccess(ValidationResult.ok(null)); callback.onSuccess(ValidationResult.ok(null));
} else { } else {
ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(currentUser.getTenantId(), new TenantId(entityId.getId())); ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(currentUser.getTenantId(), TenantId.fromUUID(entityId.getId()));
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
if (tenant == null) { if (tenant == null) {
return ValidationResult.entityNotFound("Tenant with requested id wasn't found!"); return ValidationResult.entityNotFound("Tenant with requested id wasn't found!");

4
application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java

@ -75,7 +75,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
} }
private SecurityUser authenticateByUserId(UserId userId) { private SecurityUser authenticateByUserId(UserId userId) {
TenantId systemId = new TenantId(EntityId.NULL_UUID); TenantId systemId = TenantId.SYS_TENANT_ID;
User user = userService.findUserById(systemId, userId); User user = userService.findUserById(systemId, userId);
if (user == null) { if (user == null) {
throw new UsernameNotFoundException("User not found by refresh token"); throw new UsernameNotFoundException("User not found by refresh token");
@ -99,7 +99,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
} }
private SecurityUser authenticateByPublicId(String publicId) { private SecurityUser authenticateByPublicId(String publicId) {
TenantId systemId = new TenantId(EntityId.NULL_UUID); TenantId systemId = TenantId.SYS_TENANT_ID;
CustomerId customerId; CustomerId customerId;
try { try {
customerId = new CustomerId(UUID.fromString(publicId)); customerId = new CustomerId(UUID.fromString(publicId));

5
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java

@ -54,8 +54,11 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF
throws IOException, ServletException { throws IOException, ServletException {
String baseUrl; String baseUrl;
String errorPrefix; String errorPrefix;
String callbackUrlScheme = null;
OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request); OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request);
String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME); if (authorizationRequest != null) {
callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME);
}
if (!StringUtils.isEmpty(callbackUrlScheme)) { if (!StringUtils.isEmpty(callbackUrlScheme)) {
baseUrl = callbackUrlScheme + ":"; baseUrl = callbackUrlScheme + ":";
errorPrefix = "/?error="; errorPrefix = "/?error=";

2
application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestLoginProcessingFilter.java

@ -73,7 +73,7 @@ public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingF
throw new AuthenticationServiceException("Invalid login request payload"); throw new AuthenticationServiceException("Invalid login request payload");
} }
if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isBlank(loginRequest.getPassword())) { if (StringUtils.isBlank(loginRequest.getUsername()) || StringUtils.isEmpty(loginRequest.getPassword())) {
throw new AuthenticationServiceException("Username or Password not provided"); throw new AuthenticationServiceException("Username or Password not provided");
} }

2
application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java

@ -126,7 +126,7 @@ public class JwtTokenFactory {
securityUser.setUserPrincipal(principal); securityUser.setUserPrincipal(principal);
String tenantId = claims.get(TENANT_ID, String.class); String tenantId = claims.get(TENANT_ID, String.class);
if (tenantId != null) { if (tenantId != null) {
securityUser.setTenantId(new TenantId(UUID.fromString(tenantId))); securityUser.setTenantId(TenantId.fromUUID(UUID.fromString(tenantId)));
} }
String customerId = claims.get(CUSTOMER_ID, String.class); String customerId = claims.get(CUSTOMER_ID, String.class);
if (customerId != null) { if (customerId != null) {

4
application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java

@ -27,6 +27,7 @@ import org.passay.PasswordData;
import org.passay.PasswordValidator; import org.passay.PasswordValidator;
import org.passay.Rule; import org.passay.Rule;
import org.passay.RuleResult; import org.passay.RuleResult;
import org.passay.WhitespaceRule;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
@ -174,6 +175,9 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
if (isPositiveInteger(passwordPolicy.getMinimumSpecialCharacters())) { if (isPositiveInteger(passwordPolicy.getMinimumSpecialCharacters())) {
passwordRules.add(new CharacterRule(EnglishCharacterData.Special, passwordPolicy.getMinimumSpecialCharacters())); passwordRules.add(new CharacterRule(EnglishCharacterData.Special, passwordPolicy.getMinimumSpecialCharacters()));
} }
if (passwordPolicy.getAllowWhitespaces() != null && !passwordPolicy.getAllowWhitespaces()) {
passwordRules.add(new WhitespaceRule());
}
PasswordValidator validator = new PasswordValidator(passwordRules); PasswordValidator validator = new PasswordValidator(passwordRules);
PasswordData passwordData = new PasswordData(password); PasswordData passwordData = new PasswordData(password);
RuleResult result = validator.validate(passwordData); RuleResult result = validator.validate(passwordData);

2
application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java

@ -71,7 +71,7 @@ public class DefaultSmsService implements SmsService {
@Override @Override
public void updateSmsConfiguration() { public void updateSmsConfiguration() {
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(new TenantId(EntityId.NULL_UUID), "sms"); AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "sms");
if (settings != null) { if (settings != null) {
try { try {
JsonNode jsonConfig = settings.getJsonValue(); JsonNode jsonConfig = settings.getJsonValue();

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

@ -237,7 +237,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
@Override @Override
public void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback callback) { public void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback callback) {
try { try {
TenantId tenantId = new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB()));
if (proto.getDeleted()) { if (proto.getDeleted()) {
onDeviceDeleted(tenantId, deviceId); onDeviceDeleted(tenantId, deviceId);

2
application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java

@ -77,7 +77,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
public void reportQueueStats(long ts, TbRuleEngineConsumerStats ruleEngineStats) { public void reportQueueStats(long ts, TbRuleEngineConsumerStats ruleEngineStats) {
String queueName = ruleEngineStats.getQueueName(); String queueName = ruleEngineStats.getQueueName();
ruleEngineStats.getTenantStats().forEach((id, stats) -> { ruleEngineStats.getTenantStats().forEach((id, stats) -> {
TenantId tenantId = new TenantId(id); TenantId tenantId = TenantId.fromUUID(id);
try { try {
AssetId serviceAssetId = getServiceAssetId(tenantId, queueName); AssetId serviceAssetId = getServiceAssetId(tenantId, queueName);
if (stats.getTotalMsgCounter().get() > 0) { if (stats.getTotalMsgCounter().get() > 0) {

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

@ -19,8 +19,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
@ -40,20 +42,20 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos.*;
import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateTsValue;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
@ -222,7 +224,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
} }
return subscriptionUpdate; return subscriptionUpdate;
}); }, true);
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
updateDeviceInactivityTimeout(tenantId, entityId, ts); updateDeviceInactivityTimeout(tenantId, entityId, ts);
} }
@ -256,7 +258,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
} }
return subscriptionUpdate; return subscriptionUpdate;
}); }, true);
if (entityId.getEntityType() == EntityType.DEVICE) { if (entityId.getEntityType() == EntityType.DEVICE) {
if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) { if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) {
updateDeviceInactivityTimeout(tenantId, entityId, attributes); updateDeviceInactivityTimeout(tenantId, entityId, attributes);
@ -333,14 +335,39 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
} }
return subscriptionUpdate; return subscriptionUpdate;
}); }, false);
callback.onSuccess();
}
@Override
public void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback) {
onLocalTelemetrySubUpdate(entityId,
s -> {
if (TbSubscriptionType.TIMESERIES.equals(s.getType())) {
return (TbTimeseriesSubscription) s;
} else {
return null;
}
}, s -> true, s -> {
List<TsKvEntry> subscriptionUpdate = null;
for (String key : keys) {
if (s.isAllKeys() || s.getKeyStates().containsKey(key)) {
if (subscriptionUpdate == null) {
subscriptionUpdate = new ArrayList<>();
}
subscriptionUpdate.add(new BasicTsKvEntry(0, new StringDataEntry(key, null)));
}
}
return subscriptionUpdate;
}, false);
callback.onSuccess(); callback.onSuccess();
} }
private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId, private <T extends TbSubscription> void onLocalTelemetrySubUpdate(EntityId entityId,
Function<TbSubscription, T> castFunction, Function<TbSubscription, T> castFunction,
Predicate<T> filterFunction, Predicate<T> filterFunction,
Function<T, List<TsKvEntry>> processFunction) { Function<T, List<TsKvEntry>> processFunction,
boolean ignoreEmptyUpdates) {
Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId); Set<TbSubscription> entitySubscriptions = subscriptionsByEntityId.get(entityId);
if (entitySubscriptions != null) { if (entitySubscriptions != null) {
entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> { entitySubscriptions.stream().map(castFunction).filter(Objects::nonNull).filter(filterFunction).forEach(s -> {
@ -351,7 +378,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY); localSubscriptionService.onSubscriptionUpdate(s.getSessionId(), update, TbCallback.EMPTY);
} else { } else {
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId()); TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_CORE, s.getServiceId());
toCoreNotificationsProducer.send(tpi, toProto(s, subscriptionUpdate), null); toCoreNotificationsProducer.send(tpi, toProto(s, subscriptionUpdate, ignoreEmptyUpdates), null);
} }
} }
}); });
@ -467,6 +494,10 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates) { private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates) {
return toProto(subscription, updates, true);
}
private TbProtoQueueMsg<ToCoreNotificationMsg> toProto(TbSubscription subscription, List<TsKvEntry> updates, boolean ignoreEmptyUpdates) {
TbSubscriptionUpdateProto.Builder builder = TbSubscriptionUpdateProto.newBuilder(); TbSubscriptionUpdateProto.Builder builder = TbSubscriptionUpdateProto.newBuilder();
builder.setSessionId(subscription.getSessionId()); builder.setSessionId(subscription.getSessionId());
@ -487,14 +518,16 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
boolean hasData = false; boolean hasData = false;
for (Object v : value) { for (Object v : value) {
Object[] array = (Object[]) v; Object[] array = (Object[]) v;
dataBuilder.addTs((long) array[0]); TbSubscriptionUpdateTsValue.Builder tsValueBuilder = TbSubscriptionUpdateTsValue.newBuilder();
tsValueBuilder.setTs((long) array[0]);
String strVal = (String) array[1]; String strVal = (String) array[1];
if (strVal != null) { if (strVal != null) {
hasData = true; hasData = true;
dataBuilder.addValue(strVal); tsValueBuilder.setValue(strVal);
} }
dataBuilder.addTsValue(tsValueBuilder.build());
} }
if (hasData) { if (!ignoreEmptyUpdates || hasData) {
builder.addData(dataBuilder.build()); builder.addData(dataBuilder.build());
} }
}); });

24
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -36,16 +36,13 @@ import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -68,7 +65,6 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -83,8 +79,6 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ -133,6 +127,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private int maxEntitiesPerDataSubscription; private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") @Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription; private int maxEntitiesPerAlarmSubscription;
@Value("${server.ws.max_alarm_queries_per_refresh_interval:3}")
private int maxAlarmQueriesPerRefreshInterval;
private ExecutorService wsCallBackExecutor; private ExecutorService wsCallBackExecutor;
private boolean tsInSqlDB; private boolean tsInSqlDB;
@ -182,8 +178,6 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
log.debug("[{}][{}] Updating data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery()); log.debug("[{}][{}] Updating data using query: {}", session.getSessionId(), cmd.getCmdId(), cmd.getQuery());
} }
ctx.setAndResolveQuery(cmd.getQuery()); ctx.setAndResolveQuery(cmd.getQuery());
TenantId tenantId = ctx.getTenantId();
CustomerId customerId = ctx.getCustomerId();
EntityDataQuery query = ctx.getQuery(); EntityDataQuery query = ctx.getQuery();
//Step 1. Update existing query with the contents of LatestValueCmd //Step 1. Update existing query with the contents of LatestValueCmd
if (cmd.getLatestCmd() != null) { if (cmd.getLatestCmd() != null) {
@ -282,7 +276,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
if (adq.getPageLink().getTimeWindow() > 0) { if (adq.getPageLink().getTimeWindow() > 0) {
TbAlarmDataSubCtx finalCtx = ctx; TbAlarmDataSubCtx finalCtx = ctx;
ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay( ScheduledFuture<?> task = scheduler.scheduleWithFixedDelay(
finalCtx::cleanupOldAlarms, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); finalCtx::checkAndResetInvocationCounter, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task); finalCtx.setRefreshTask(task);
} }
} }
@ -293,6 +287,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
finalCtx.update(); finalCtx.update();
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
log.trace("[{}][{}] Executing query: {}", finalCtx.getSessionId(), finalCtx.getCmdId(), finalCtx.getQuery());
stats.getDynamicQueryInvocationCnt().incrementAndGet(); stats.getDynamicQueryInvocationCnt().incrementAndGet();
stats.getDynamicQueryTimeSpent().addAndGet(end - start); stats.getDynamicQueryTimeSpent().addAndGet(end - start);
} catch (Exception e) { } catch (Exception e) {
@ -308,7 +303,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
long regularQueryInvocationTimeValue = stats.getRegularQueryTimeSpent().getAndSet(0); long regularQueryInvocationTimeValue = stats.getRegularQueryTimeSpent().getAndSet(0);
int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0); int dynamicQueryInvocationCntValue = stats.getDynamicQueryInvocationCnt().getAndSet(0);
long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0); long dynamicQueryInvocationTimeValue = stats.getDynamicQueryTimeSpent().getAndSet(0);
long dynamicQueryCnt = subscriptionsBySessionId.values().stream().map(Map::values).count(); long dynamicQueryCnt = subscriptionsBySessionId.values().stream().mapToLong(m -> m.values().stream().filter(TbAbstractSubCtx::isDynamic).count()).sum();
if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) { if (regularQueryInvocationCntValue > 0 || dynamicQueryInvocationCntValue > 0 || dynamicQueryCnt > 0 || alarmQueryInvocationCntValue > 0) {
log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " + log.info("Stats: regularQueryInvocationCnt = [{}], regularQueryInvocationTime = [{}], " +
"dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " + "dynamicQueryCnt = [{}] dynamicQueryInvocationCnt = [{}], dynamicQueryInvocationTime = [{}], " +
@ -345,7 +340,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>());
TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService,
attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription,
maxAlarmQueriesPerRefreshInterval);
ctx.setAndResolveQuery(cmd.getQuery()); ctx.setAndResolveQuery(cmd.getQuery());
sessionSubs.put(cmd.getCmdId(), ctx); sessionSubs.put(cmd.getCmdId(), ctx);
return ctx; return ctx;
@ -501,6 +497,12 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
if (ctx != null) { if (ctx != null) {
ctx.cancelTasks(); ctx.cancelTasks();
ctx.clearSubscriptions(); ctx.clearSubscriptions();
if (ctx.getSessionId() != null) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(ctx.getSessionId());
if (sessionSubs != null) {
sessionSubs.remove(ctx.getCmdId());
}
}
} }
} }

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

@ -40,6 +40,8 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty); void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, TbCallback callback);
void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback); void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);

11
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -57,6 +57,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
this.subToEntityIdMap = new ConcurrentHashMap<>(); this.subToEntityIdMap = new ConcurrentHashMap<>();
} }
@Override
public void fetchData() { public void fetchData() {
this.data = findEntityData(); this.data = findEntityData();
} }
@ -71,12 +72,14 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
return result; return result;
} }
@Override
public boolean isDynamic() {
return query != null && query.getPageLink().isDynamic();
}
@Override
protected synchronized void update() { protected synchronized void update() {
long start = System.currentTimeMillis();
PageData<EntityData> newData = findEntityData(); PageData<EntityData> newData = findEntityData();
long end = System.currentTimeMillis();
stats.getRegularQueryInvocationCnt().incrementAndGet();
stats.getRegularQueryTimeSpent().addAndGet(end - start);
Map<EntityId, EntityData> oldDataMap; Map<EntityId, EntityData> oldDataMap;
if (data != null && !data.getData().isEmpty()) { if (data != null && !data.getData().isEmpty()) {
oldDataMap = data.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a, b) -> a)); oldDataMap = data.getData().stream().collect(Collectors.toMap(EntityData::getEntityId, Function.identity(), (a, b) -> a));

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

@ -174,6 +174,8 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
} }
} }
public abstract boolean isDynamic();
public abstract void fetchData(); public abstract void fetchData();
protected abstract void update(); protected abstract void update();

37
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.subscription;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
@ -56,6 +57,7 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@ToString(callSuper = true)
public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> { public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
private final AlarmService alarmService; private final AlarmService alarmService;
@ -68,6 +70,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
private final int maxEntitiesPerAlarmSubscription; private final int maxEntitiesPerAlarmSubscription;
private final int maxAlarmQueriesPerRefreshInterval;
@Getter @Getter
@Setter @Setter
private PageData<AlarmData> alarms; private PageData<AlarmData> alarms;
@ -75,18 +79,30 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
@Setter @Setter
private boolean tooManyEntities; private boolean tooManyEntities;
private int alarmInvocationAttempts;
public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService, public TbAlarmDataSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService, AttributesService attributesService, SubscriptionServiceStatistics stats, AlarmService alarmService,
TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerAlarmSubscription) { TelemetryWebSocketSessionRef sessionRef, int cmdId,
int maxEntitiesPerAlarmSubscription, int maxAlarmQueriesPerRefreshInterval) {
super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId);
this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription; this.maxEntitiesPerAlarmSubscription = maxEntitiesPerAlarmSubscription;
this.maxAlarmQueriesPerRefreshInterval = maxAlarmQueriesPerRefreshInterval;
this.alarmService = alarmService; this.alarmService = alarmService;
this.entitiesMap = new LinkedHashMap<>(); this.entitiesMap = new LinkedHashMap<>();
this.alarmsMap = new HashMap<>(); this.alarmsMap = new HashMap<>();
} }
public void fetchAlarms() { public void fetchAlarms() {
alarmInvocationAttempts++;
log.trace("[{}] Fetching alarms: {}", cmdId, alarmInvocationAttempts);
if (alarmInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) {
doFetchAlarms();
}
}
private void doFetchAlarms() {
AlarmDataUpdate update; AlarmDataUpdate update;
if (!entitiesMap.isEmpty()) { if (!entitiesMap.isEmpty()) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
@ -103,6 +119,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
} }
public void fetchData() { public void fetchData() {
resetInvocationCounter();
log.trace("[{}] Fetching data: {}", cmdId, alarmInvocationAttempts);
super.fetchData(); super.fetchData();
entitiesMap.clear(); entitiesMap.clear();
tooManyEntities = data.hasNext(); tooManyEntities = data.hasNext();
@ -222,7 +240,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
} }
} }
if (shouldRefresh) { if (shouldRefresh) {
fetchAlarms(); doFetchAlarms();
} }
} }
@ -256,8 +274,19 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
return true; return true;
} }
public synchronized void checkAndResetInvocationCounter() {
boolean fetchNeeded = this.alarmInvocationAttempts > maxAlarmQueriesPerRefreshInterval;
resetInvocationCounter();
if (fetchNeeded) {
fetchAlarms();
} else {
cleanupOldAlarms();
}
}
@Override @Override
protected synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) { protected synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
resetInvocationCounter();
entitiesMap.clear(); entitiesMap.clear();
tooManyEntities = data.hasNext(); tooManyEntities = data.hasNext();
for (EntityData entityData : data.getData()) { for (EntityData entityData : data.getData()) {
@ -295,6 +324,10 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
subsToAdd.forEach(localSubscriptionService::addSubscription); subsToAdd.forEach(localSubscriptionService::addSubscription);
} }
private void resetInvocationCounter() {
alarmInvocationAttempts = 0;
}
@Override @Override
protected EntityDataQuery buildEntityDataQuery() { protected EntityDataQuery buildEntityDataQuery() {
EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder(); EntityDataSortOrder sortOrder = query.getPageLink().getSortOrder();

4
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityCountSubCtx.java

@ -52,4 +52,8 @@ public class TbEntityCountSubCtx extends TbAbstractSubCtx<EntityCountQuery> {
} }
} }
@Override
public boolean isDynamic() {
return true;
}
} }

1
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -165,6 +165,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null); return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null);
} }
@Override
public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) { public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
List<Integer> subIdsToCancel = new ArrayList<>(); List<Integer> subIdsToCancel = new ArrayList<>();
List<TbSubscription> subsToAdd = new ArrayList<>(); List<TbSubscription> subsToAdd = new ArrayList<>();

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

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.subscription; package org.thingsboard.server.service.subscription;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
@ -30,24 +31,25 @@ import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionKetStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateTsValue;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesSubscriptionProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode;
import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate;
@ -123,7 +125,7 @@ public class TbSubscriptionUtils {
.sessionId(subProto.getSessionId()) .sessionId(subProto.getSessionId())
.subscriptionId(subProto.getSubscriptionId()) .subscriptionId(subProto.getSubscriptionId())
.entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB()))) .entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB())))
.tenantId(new TenantId(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB()))); .tenantId(TenantId.fromUUID(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB())));
builder.scope(TbAttributeSubscriptionScope.valueOf(attributeSub.getScope())); builder.scope(TbAttributeSubscriptionScope.valueOf(attributeSub.getScope()));
builder.allKeys(attributeSub.getAllKeys()); builder.allKeys(attributeSub.getAllKeys());
@ -140,7 +142,7 @@ public class TbSubscriptionUtils {
.sessionId(subProto.getSessionId()) .sessionId(subProto.getSessionId())
.subscriptionId(subProto.getSubscriptionId()) .subscriptionId(subProto.getSubscriptionId())
.entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB()))) .entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB())))
.tenantId(new TenantId(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB()))); .tenantId(TenantId.fromUUID(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB())));
builder.allKeys(telemetrySub.getAllKeys()); builder.allKeys(telemetrySub.getAllKeys());
Map<String, Long> keyStates = new HashMap<>(); Map<String, Long> keyStates = new HashMap<>();
@ -159,7 +161,7 @@ public class TbSubscriptionUtils {
.sessionId(subProto.getSessionId()) .sessionId(subProto.getSessionId())
.subscriptionId(subProto.getSubscriptionId()) .subscriptionId(subProto.getSubscriptionId())
.entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB()))) .entityId(EntityIdFactory.getByTypeAndUuid(subProto.getEntityType(), new UUID(subProto.getEntityIdMSB(), subProto.getEntityIdLSB())))
.tenantId(new TenantId(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB()))); .tenantId(TenantId.fromUUID(new UUID(subProto.getTenantIdMSB(), subProto.getTenantIdLSB())));
builder.ts(alarmSub.getTs()); builder.ts(alarmSub.getTs());
return builder.build(); return builder.build();
} }
@ -171,10 +173,11 @@ public class TbSubscriptionUtils {
Map<String, List<Object>> data = new TreeMap<>(); Map<String, List<Object>> data = new TreeMap<>();
proto.getDataList().forEach(v -> { proto.getDataList().forEach(v -> {
List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>()); List<Object> values = data.computeIfAbsent(v.getKey(), k -> new ArrayList<>());
for (int i = 0; i < v.getTsCount(); i++) { for (int i = 0; i < v.getTsValueCount(); i++) {
Object[] value = new Object[2]; Object[] value = new Object[2];
value[0] = v.getTs(i); TbSubscriptionUpdateTsValue tsValue = v.getTsValue(i);
value[1] = v.getValue(i); value[0] = tsValue.getTs();
value[1] = tsValue.hasValue() ? tsValue.getValue() : null;
values.add(value); values.add(value);
} }
}); });
@ -205,6 +208,19 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
} }
public static ToCoreMsg toTimeseriesDeleteProto(TenantId tenantId, EntityId entityId, List<String> keys) {
TbTimeSeriesDeleteProto.Builder builder = TbTimeSeriesDeleteProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name());
builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.addAllKeys(keys);
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setTsDelete(builder);
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
}
public static ToCoreMsg toAttributesUpdateProto(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) { public static ToCoreMsg toAttributesUpdateProto(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
TbAttributeUpdateProto.Builder builder = TbAttributeUpdateProto.newBuilder(); TbAttributeUpdateProto.Builder builder = TbAttributeUpdateProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name()); builder.setEntityType(entityId.getEntityType().name());

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

@ -90,7 +90,7 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
Futures.addCallback(saveFuture, new FutureCallback<T>() { Futures.addCallback(saveFuture, new FutureCallback<T>() {
@Override @Override
public void onSuccess(@Nullable T result) { public void onSuccess(@Nullable T result) {
callback.accept(null); callback.accept(result);
} }
@Override @Override

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

@ -20,8 +20,10 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
@ -31,10 +33,12 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
@ -45,7 +49,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.subscription.TbSubscriptionUtils;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -58,6 +61,7 @@ import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -118,28 +122,46 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override @Override
public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) { public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, true);
}
@Override
public void saveWithoutLatestAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback) {
doSaveAndNotify(tenantId, customerId, entityId, ts, ttl, callback, false);
}
private void doSaveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Void> callback, boolean saveLatest) {
checkInternalEntity(entityId); checkInternalEntity(entityId);
boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null;
if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) {
saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback<Integer>() { if (saveLatest) {
@Override saveAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
public void onSuccess(Integer result) { } else {
if (!sysTenant && result != null && result > 0) { saveWithoutLatestAndNotifyInternal(tenantId, entityId, ts, ttl, getCallback(tenantId, customerId, sysTenant, callback));
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); }
}
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
});
} else { } else {
callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!")); callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!"));
} }
} }
@NotNull
private FutureCallback<Integer> getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) {
return new FutureCallback<>() {
@Override
public void onSuccess(Integer result) {
if (!sysTenant && result != null && result > 0) {
apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result);
}
callback.onSuccess(null);
}
@Override
public void onFailure(Throwable t) {
callback.onFailure(t);
}
};
}
@Override @Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback) { public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback) {
saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback); saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback);
@ -148,6 +170,15 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override @Override
public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) { public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl); ListenableFuture<Integer> saveFuture = tsService.save(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
}
private void saveWithoutLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, long ttl, FutureCallback<Integer> callback) {
ListenableFuture<Integer> saveFuture = tsService.saveWithoutLatest(tenantId, entityId, ts, ttl);
addCallbacks(tenantId, entityId, ts, callback, saveFuture);
}
private void addCallbacks(TenantId tenantId, EntityId entityId, List<TsKvEntry> ts, FutureCallback<Integer> callback, ListenableFuture<Integer> saveFuture) {
addMainCallback(saveFuture, callback); addMainCallback(saveFuture, callback);
addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts));
if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) {
@ -251,7 +282,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
@Override @Override
public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) { public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List<String> keys, FutureCallback<Void> callback) {
ListenableFuture<List<Void>> deleteFuture = tsService.removeLatest(tenantId, entityId, keys); ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.removeLatest(tenantId, entityId, keys);
addVoidCallback(deleteFuture, callback); addVoidCallback(deleteFuture, callback);
} }
@ -271,6 +302,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
}, tsCallBackExecutor); }, tsCallBackExecutor);
} }
@Override
public void deleteTimeseriesAndNotify(TenantId tenantId, EntityId entityId, List<String> keys, List<DeleteTsKvQuery> deleteTsKvQueries, FutureCallback<Void> callback) {
ListenableFuture<List<TsKvLatestRemovingResult>> deleteFuture = tsService.remove(tenantId, entityId, deleteTsKvQueries);
addVoidCallback(deleteFuture, callback);
addWsCallback(deleteFuture, list -> onTimeSeriesDelete(tenantId, entityId, keys, list));
}
@Override @Override
public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) { public void saveAttrAndNotify(TenantId tenantId, EntityId entityId, String scope, String key, long value, FutureCallback<Void> callback) {
saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value) saveAndNotify(tenantId, entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value)
@ -337,6 +375,34 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
private void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List<String> keys, List<TsKvLatestRemovingResult> ts) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (currentPartitions.contains(tpi)) {
if (subscriptionManagerService.isPresent()) {
List<TsKvEntry> updated = new ArrayList<>();
List<String> deleted = new ArrayList<>();
ts.stream().filter(Objects::nonNull).forEach(res -> {
if (res.isRemoved()) {
if (res.getData() != null) {
updated.add(res.getData());
} else {
deleted.add(res.getKey());
}
}
});
subscriptionManagerService.get().onTimeSeriesUpdate(tenantId, entityId, updated, TbCallback.EMPTY);
subscriptionManagerService.get().onTimeSeriesDelete(tenantId, entityId, deleted, TbCallback.EMPTY);
} else {
log.warn("Possible misconfiguration because subscriptionManagerService is null!");
}
} else {
TransportProtos.ToCoreMsg toCoreMsg = TbSubscriptionUtils.toTimeseriesDeleteProto(tenantId, entityId, keys);
clusterService.pushMsgToCore(tpi, entityId.getId(), toCoreMsg, null);
}
}
private <S> void addVoidCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) { private <S> void addVoidCallback(ListenableFuture<S> saveFuture, final FutureCallback<Void> callback) {
Futures.addCallback(saveFuture, new FutureCallback<S>() { Futures.addCallback(saveFuture, new FutureCallback<S>() {
@Override @Override

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

@ -103,6 +103,8 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService { public class DefaultTelemetryWebSocketService implements TelemetryWebSocketService {
public static final int NUMBER_OF_PING_ATTEMPTS = 3;
private static final int DEFAULT_LIMIT = 100; private static final int DEFAULT_LIMIT = 100;
private static final Aggregation DEFAULT_AGGREGATION = Aggregation.NONE; private static final Aggregation DEFAULT_AGGREGATION = Aggregation.NONE;
private static final int UNKNOWN_SUBSCRIPTION_ID = 0; private static final int UNKNOWN_SUBSCRIPTION_ID = 0;
@ -136,7 +138,6 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
@Autowired @Autowired
private TbServiceInfoProvider serviceInfoProvider; private TbServiceInfoProvider serviceInfoProvider;
@Value("${server.ws.limits.max_subscriptions_per_tenant:0}") @Value("${server.ws.limits.max_subscriptions_per_tenant:0}")
private int maxSubscriptionsPerTenant; private int maxSubscriptionsPerTenant;
@Value("${server.ws.limits.max_subscriptions_per_customer:0}") @Value("${server.ws.limits.max_subscriptions_per_customer:0}")
@ -146,6 +147,9 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
@Value("${server.ws.limits.max_subscriptions_per_public_user:0}") @Value("${server.ws.limits.max_subscriptions_per_public_user:0}")
private int maxSubscriptionsPerPublicUser; private int maxSubscriptionsPerPublicUser;
@Value("${server.ws.ping_timeout:30000}")
private long pingTimeout;
private ConcurrentMap<TenantId, Set<String>> tenantSubscriptionsMap = new ConcurrentHashMap<>(); private ConcurrentMap<TenantId, Set<String>> tenantSubscriptionsMap = new ConcurrentHashMap<>();
private ConcurrentMap<CustomerId, Set<String>> customerSubscriptionsMap = new ConcurrentHashMap<>(); private ConcurrentMap<CustomerId, Set<String>> customerSubscriptionsMap = new ConcurrentHashMap<>();
private ConcurrentMap<UserId, Set<String>> regularUserSubscriptionsMap = new ConcurrentHashMap<>(); private ConcurrentMap<UserId, Set<String>> regularUserSubscriptionsMap = new ConcurrentHashMap<>();
@ -162,7 +166,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass()); executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass());
pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping")); pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping"));
pingExecutor.scheduleWithFixedDelay(this::sendPing, 10000, 10000, TimeUnit.MILLISECONDS); pingExecutor.scheduleWithFixedDelay(this::sendPing, pingTimeout / NUMBER_OF_PING_ATTEMPTS, pingTimeout / NUMBER_OF_PING_ATTEMPTS, TimeUnit.MILLISECONDS);
} }
@PreDestroy @PreDestroy

18
application/src/main/java/org/thingsboard/server/service/transport/BasicCredentialsValidationResult.java

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

103
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -25,7 +25,6 @@ import com.google.protobuf.ByteString;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
@ -107,6 +107,9 @@ import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.PASSWORD_MISMATCH;
import static org.thingsboard.server.service.transport.BasicCredentialsValidationResult.VALID;
/** /**
* Created by ashvayka on 05.10.18. * Created by ashvayka on 05.10.18.
*/ */
@ -181,71 +184,89 @@ public class DefaultTransportApiService implements TransportApiService {
//TODO: Make async and enable caching //TODO: Make async and enable caching
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId); DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId);
if (credentials != null && credentials.getCredentialsType() == credentialsType) { if (credentials != null && credentials.getCredentialsType() == credentialsType) {
return getDeviceInfo(credentials.getDeviceId(), credentials); return getDeviceInfo(credentials);
} else { } else {
return getEmptyTransportApiResponseFuture(); return getEmptyTransportApiResponseFuture();
} }
} }
private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = null; DeviceCredentials credentials;
if (!StringUtils.isEmpty(mqtt.getUserName())) { if (StringUtils.isEmpty(mqtt.getUserName())) {
credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
if (credentials != null) { if (credentials != null) {
if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { return getDeviceInfo(credentials);
return getDeviceInfo(credentials.getDeviceId(), credentials); } else {
} else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { return getEmptyTransportApiResponseFuture();
if (!checkMqttCredentials(mqtt, credentials)) { }
credentials = null; } else {
} credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(
} else { EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
if (checkIsMqttCredentials(credentials)) {
var validationResult = validateMqttCredentials(mqtt, credentials);
if (VALID.equals(validationResult)) {
return getDeviceInfo(credentials);
} else if (PASSWORD_MISMATCH.equals(validationResult)) {
return getEmptyTransportApiResponseFuture(); return getEmptyTransportApiResponseFuture();
} else {
return validateUserNameCredentials(mqtt);
} }
} else {
return validateUserNameCredentials(mqtt);
} }
if (credentials == null) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
}
}
if (credentials == null) {
credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
} }
}
private ListenableFuture<TransportApiResponseMsg> validateUserNameCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
if (credentials != null) { if (credentials != null) {
return getDeviceInfo(credentials.getDeviceId(), credentials); switch (credentials.getCredentialsType()) {
} else { case ACCESS_TOKEN:
return getEmptyTransportApiResponseFuture(); return getDeviceInfo(credentials);
case MQTT_BASIC:
if (VALID.equals(validateMqttCredentials(mqtt, credentials))) {
return getDeviceInfo(credentials);
} else {
return getEmptyTransportApiResponseFuture();
}
}
} }
return getEmptyTransportApiResponseFuture();
}
private static boolean checkIsMqttCredentials(DeviceCredentials credentials) {
return credentials != null && DeviceCredentialsType.MQTT_BASIC.equals(credentials.getCredentialsType());
} }
private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) { private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) {
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId); return checkMqttCredentials(clientCred, deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId));
}
private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) {
if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
if (!checkMqttCredentials(clientCred, deviceCredentials)) { if (VALID.equals(validateMqttCredentials(clientCred, deviceCredentials))) {
return null;
} else {
return deviceCredentials; return deviceCredentials;
} }
} }
return null; return null;
} }
private boolean checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) { private BasicCredentialsValidationResult validateMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) {
BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class); BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class);
if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) { if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) {
return false; return BasicCredentialsValidationResult.HASH_MISMATCH;
} }
if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) { if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) {
return false; return BasicCredentialsValidationResult.HASH_MISMATCH;
} }
if (!StringUtils.isEmpty(dbCred.getPassword())) { if (!StringUtils.isEmpty(dbCred.getPassword())) {
if (StringUtils.isEmpty(clientCred.getPassword())) { if (StringUtils.isEmpty(clientCred.getPassword())) {
return false; return BasicCredentialsValidationResult.PASSWORD_MISMATCH;
} else { } else {
if (!dbCred.getPassword().equals(clientCred.getPassword())) { return dbCred.getPassword().equals(clientCred.getPassword()) ? VALID : BasicCredentialsValidationResult.PASSWORD_MISMATCH;
return false;
}
} }
} }
return true; return VALID;
} }
private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) { private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) {
@ -359,7 +380,7 @@ public class DefaultTransportApiService implements TransportApiService {
DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId);
builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
} else if (entityType.equals(EntityType.TENANT)) { } else if (entityType.equals(EntityType.TENANT)) {
TenantId tenantId = new TenantId(entityUuid); TenantId tenantId = TenantId.fromUUID(entityUuid);
TenantProfile tenantProfile = tenantProfileCache.get(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
ApiUsageState state = apiUsageStateService.getApiUsageState(tenantId); ApiUsageState state = apiUsageStateService.getApiUsageState(tenantId);
builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile))); builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)));
@ -404,7 +425,7 @@ public class DefaultTransportApiService implements TransportApiService {
private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) { private ListenableFuture<TransportApiResponseMsg> handle(GetResourceRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType()); ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
String resourceKey = requestMsg.getResourceKey(); String resourceKey = requestMsg.getResourceKey();
TransportProtos.GetResourceResponseMsg.Builder builder = TransportProtos.GetResourceResponseMsg.newBuilder(); TransportProtos.GetResourceResponseMsg.Builder builder = TransportProtos.GetResourceResponseMsg.newBuilder();
@ -437,10 +458,10 @@ public class DefaultTransportApiService implements TransportApiService {
.build()); .build());
} }
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> { return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, credentials.getDeviceId()), device -> {
if (device == null) { if (device == null) {
log.trace("[{}] Failed to lookup device by id", deviceId); log.trace("[{}] Failed to lookup device by id", credentials.getDeviceId());
return getEmptyTransportApiResponse(); return getEmptyTransportApiResponse();
} }
try { try {
@ -458,7 +479,7 @@ public class DefaultTransportApiService implements TransportApiService {
return TransportApiResponseMsg.newBuilder() return TransportApiResponseMsg.newBuilder()
.setValidateCredResponseMsg(builder.build()).build(); .setValidateCredResponseMsg(builder.build()).build();
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by id", deviceId, e); log.warn("[{}] Failed to lookup device by id", credentials.getDeviceId(), e);
return getEmptyTransportApiResponse(); return getEmptyTransportApiResponse();
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
@ -521,7 +542,7 @@ public class DefaultTransportApiService implements TransportApiService {
} }
private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetOtaPackageRequestMsg requestMsg) { private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetOtaPackageRequestMsg requestMsg) {
TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB()));
OtaPackageType otaPackageType = OtaPackageType.valueOf(requestMsg.getType()); OtaPackageType otaPackageType = OtaPackageType.valueOf(requestMsg.getType());
Device device = deviceService.findDeviceById(tenantId, deviceId); Device device = deviceService.findDeviceById(tenantId, deviceId);
@ -571,7 +592,7 @@ public class DefaultTransportApiService implements TransportApiService {
} }
private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) { private ListenableFuture<TransportApiResponseMsg> handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) {
TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId())); TenantId tenantId = TenantId.fromUUID(UUID.fromString(msg.getTenantId()));
String deviceName = msg.getEndpoint(); String deviceName = msg.getEndpoint();
Lock deviceCreationLock = deviceCreationLocks.computeIfAbsent(deviceName, id -> new ReentrantLock()); Lock deviceCreationLock = deviceCreationLocks.computeIfAbsent(deviceName, id -> new ReentrantLock());
deviceCreationLock.lock(); deviceCreationLock.lock();

2
application/src/main/java/org/thingsboard/server/service/transport/msg/TransportToDeviceActorMsgWrapper.java

@ -44,7 +44,7 @@ public class TransportToDeviceActorMsgWrapper implements TbActorMsg, DeviceAware
public TransportToDeviceActorMsgWrapper(TransportToDeviceActorMsg msg, TbCallback callback) { public TransportToDeviceActorMsgWrapper(TransportToDeviceActorMsg msg, TbCallback callback) {
this.msg = msg; this.msg = msg;
this.callback = callback; this.callback = callback;
this.tenantId = new TenantId(new UUID(msg.getSessionInfo().getTenantIdMSB(), msg.getSessionInfo().getTenantIdLSB())); this.tenantId = TenantId.fromUUID(new UUID(msg.getSessionInfo().getTenantIdMSB(), msg.getSessionInfo().getTenantIdLSB()));
this.deviceId = new DeviceId(new UUID(msg.getSessionInfo().getDeviceIdMSB(), msg.getSessionInfo().getDeviceIdLSB())); this.deviceId = new DeviceId(new UUID(msg.getSessionInfo().getDeviceIdMSB(), msg.getSessionInfo().getDeviceIdLSB()));
} }

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

@ -40,6 +40,9 @@
<!-- Top Rule Nodes by max execution time --> <!-- Top Rule Nodes by max execution time -->
<!-- <logger name="org.thingsboard.server.service.queue.TbMsgPackProcessingContext" level="DEBUG" /> --> <!-- <logger name="org.thingsboard.server.service.queue.TbMsgPackProcessingContext" level="DEBUG" /> -->
<!-- MQTT transport debug -->
<!-- <logger name="org.thingsboard.server.transport.mqtt.MqttTransportHandler" level="DEBUG" /> -->
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" /> <logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
<root level="INFO"> <root level="INFO">

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

@ -54,7 +54,8 @@ server:
log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:false}" log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:false}"
ws: ws:
send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}" send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}"
ping_timeout: "${TB_SERVER_WS_PING_TIMEOUT:15000}" # recommended timeout >= 30 seconds. Platform will attempt to send 'ping' request 3 times within the timeout
ping_timeout: "${TB_SERVER_WS_PING_TIMEOUT:30000}"
limits: limits:
# Limit the amount of sessions and subscriptions available on each server. Put values to zero to disable particular limitation # Limit the amount of sessions and subscriptions available on each server. Put values to zero to disable particular limitation
max_sessions_per_tenant: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT:0}" max_sessions_per_tenant: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT:0}"
@ -70,6 +71,7 @@ server:
dynamic_page_link: dynamic_page_link:
refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}" refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}"
refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}" refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}"
max_alarm_queries_per_refresh_interval: "${TB_SERVER_WS_MAX_ALARM_QUERIES_PER_REFRESH_INTERVAL:3}"
max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}" max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}"
max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}" max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}"
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}" max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}"
@ -163,7 +165,7 @@ ui:
# Help parameters # Help parameters
help: help:
# Base url for UI help assets # Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.2}" base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}"
database: database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -211,7 +213,7 @@ cassandra:
init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}" init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}"
max_requests_per_connection_local: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_LOCAL:32768}" max_requests_per_connection_local: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_LOCAL:32768}"
max_requests_per_connection_remote: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_REMOTE:32768}" max_requests_per_connection_remote: "${CASSANDRA_MAX_REQUESTS_PER_CONNECTION_REMOTE:32768}"
# Credential parameters # # Credential parameters
credentials: "${CASSANDRA_USE_CREDENTIALS:false}" credentials: "${CASSANDRA_USE_CREDENTIALS:false}"
# Specify your username # Specify your username
username: "${CASSANDRA_USERNAME:}" username: "${CASSANDRA_USERNAME:}"
@ -264,17 +266,17 @@ sql:
batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}" batch_size: "${SQL_ATTRIBUTES_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_ATTRIBUTES_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_ATTRIBUTES_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:4}" batch_threads: "${SQL_ATTRIBUTES_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
ts: ts:
batch_size: "${SQL_TS_BATCH_SIZE:10000}" batch_size: "${SQL_TS_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_TS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_TS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_BATCH_THREADS:4}" batch_threads: "${SQL_TS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
ts_latest: ts_latest:
batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}" batch_size: "${SQL_TS_LATEST_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}" batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}" update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}" batch_sort: "${SQL_BATCH_SORT:false}"
@ -289,7 +291,7 @@ sql:
timescale: timescale:
# Specify Interval size for new data chunks storage. # Specify Interval size for new data chunks storage.
chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}" chunk_time_interval: "${SQL_TIMESCALE_CHUNK_TIME_INTERVAL:604800000}"
batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:4}" batch_threads: "${SQL_TIMESCALE_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
ttl: ttl:
ts: ts:
enabled: "${SQL_TTL_TS_ENABLED:true}" enabled: "${SQL_TTL_TS_ENABLED:true}"
@ -379,50 +381,50 @@ cache:
caffeine: caffeine:
specs: specs:
relations: relations:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_RELATIONS_TTL:1440}"
maxSize: 10000 # maxSize: 0 means the cache is disabled maxSize: "${CACHE_SPECS_RELATIONS_MAX_SIZE:10000}" # maxSize: 0 means the cache is disabled
deviceCredentials: deviceCredentials:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_CREDENTIALS_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_DEVICE_CREDENTIALS_MAX_SIZE:10000}"
devices: devices:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_DEVICES_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_DEVICES_MAX_SIZE:10000}"
sessions: sessions:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_SESSIONS_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_SESSIONS_MAX_SIZE:10000}"
assets: assets:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_ASSETS_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_ASSETS_MAX_SIZE:10000}"
entityViews: entityViews:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_VIEWS_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_ENTITY_VIEWS_MAX_SIZE:10000}"
claimDevices: claimDevices:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_CLAIM_DEVICES_TTL:1440}"
maxSize: 1000 maxSize: "${CACHE_SPECS_CLAIM_DEVICES_MAX_SIZE:1000}"
securitySettings: securitySettings:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_SECURITY_SETTINGS_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_SECURITY_SETTINGS_MAX_SIZE:10000}"
tenantProfiles: tenantProfiles:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_TENANT_PROFILES_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_TENANT_PROFILES_MAX_SIZE:10000}"
deviceProfiles: deviceProfiles:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}"
attributes: attributes:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}"
maxSize: 100000 maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}"
tokensOutdatageTime: tokensOutdatageTime:
timeToLiveInMinutes: 20000 timeToLiveInMinutes: "${CACHE_SPECS_TOKENS_OUTDATAGE_TIME_TTL:20000}"
maxSize: 10000 maxSize: "${CACHE_SPECS_TOKENS_OUTDATAGE_TIME_MAX_SIZE:10000}"
otaPackages: otaPackages:
timeToLiveInMinutes: 60 timeToLiveInMinutes: "${CACHE_SPECS_OTA_PACKAGES_TTL:60}"
maxSize: 10 maxSize: "${CACHE_SPECS_OTA_PACKAGES_MAX_SIZE:10}"
otaPackagesData: otaPackagesData:
timeToLiveInMinutes: 60 timeToLiveInMinutes: "${CACHE_SPECS_OTA_PACKAGES_DATA_TTL:60}"
maxSize: 10 maxSize: "${CACHE_SPECS_OTA_PACKAGES_DATA_MAX_SIZE:10}"
edges: edges:
timeToLiveInMinutes: 1440 timeToLiveInMinutes: "${CACHE_SPECS_EDGES_TTL:1440}"
maxSize: 10000 maxSize: "${CACHE_SPECS_EDGES_MAX_SIZE:10000}"
redis: redis:
# standalone or cluster # standalone or cluster
@ -502,7 +504,7 @@ spring.servlet.multipart.max-file-size: "50MB"
spring.servlet.multipart.max-request-size: "50MB" spring.servlet.multipart.max-request-size: "50MB"
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
spring.jpa.properties.hibernate.order_by.default_null_ordering: "last" spring.jpa.properties.hibernate.order_by.default_null_ordering: "${SPRING_JPA_PROPERTIES_HIBERNATE_ORDER_BY_DEFAULT_NULL_ORDERING:last}"
# SQL DAO Configuration # SQL DAO Configuration
spring: spring:
@ -595,7 +597,7 @@ js:
remote: remote:
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}"
# Maximum time in seconds for black listed function to stay in 1:the list. # Maximum time in seconds for black listed function to stay in the list.
max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}" max_black_list_duration_sec: "${REMOTE_JS_SANDBOX_MAX_BLACKLIST_DURATION_SEC:60}"
stats: stats:
enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}" enabled: "${TB_JS_REMOTE_STATS_ENABLED:false}"
@ -617,6 +619,13 @@ transport:
log: log:
enabled: "${TB_TRANSPORT_LOG_ENABLED:true}" enabled: "${TB_TRANSPORT_LOG_ENABLED:true}"
max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}" max_length: "${TB_TRANSPORT_LOG_MAX_LENGTH:1024}"
rate_limits:
# Enable or disable generic rate limits. Device and Tenant specific rate limits are controlled in Tenant Profile.
ip_limits_enabled: "${TB_TRANSPORT_IP_RATE_LIMITS_ENABLED:false}"
# Maximum number of connect attempts with invalid credentials
max_wrong_credentials_per_ip: "${TB_TRANSPORT_MAX_WRONG_CREDENTIALS_PER_IP:10}"
# Timeout to expire block IP addresses
ip_block_timeout: "${TB_TRANSPORT_IP_BLOCK_TIMEOUT:60000}"
# Local HTTP transport parameters # Local HTTP transport parameters
http: http:
enabled: "${HTTP_ENABLED:true}" enabled: "${HTTP_ENABLED:true}"
@ -628,6 +637,9 @@ transport:
enabled: "${MQTT_ENABLED:true}" enabled: "${MQTT_ENABLED:true}"
bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}"
bind_port: "${MQTT_BIND_PORT:1883}" bind_port: "${MQTT_BIND_PORT:1883}"
# Enable proxy protocol support. Disabled by default. If enabled, supports both v1 and v2.
# Useful to get the real IP address of the client in the logs and for rate limits.
proxy_enabled: "${MQTT_PROXY_PROTOCOL_ENABLED:false}"
timeout: "${MQTT_TIMEOUT:10000}" timeout: "${MQTT_TIMEOUT:10000}"
msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism
netty: netty:
@ -731,7 +743,7 @@ transport:
# Server X509 Certificates support # Server X509 Certificates support
credentials: credentials:
# Whether to enable LWM2M server X509 Certificate/RPK support # Whether to enable LWM2M server X509 Certificate/RPK support
enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:true}" enabled: "${LWM2M_SERVER_CREDENTIALS_ENABLED:false}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}" type: "${LWM2M_SERVER_CREDENTIALS_TYPE:PEM}"
# PEM server credentials # PEM server credentials
@ -757,7 +769,7 @@ transport:
# Only Certificate_x509: # Only Certificate_x509:
skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}"
bootstrap: bootstrap:
enable: "${LWM2M_ENABLED_BS:true}" enabled: "${LWM2M_ENABLED_BS:true}"
id: "${LWM2M_SERVER_ID_BS:111}" id: "${LWM2M_SERVER_ID_BS:111}"
bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}" bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}"
bind_port: "${LWM2M_BS_BIND_PORT:5687}" bind_port: "${LWM2M_BS_BIND_PORT:5687}"
@ -767,7 +779,7 @@ transport:
# Bootstrap server X509 Certificates support # Bootstrap server X509 Certificates support
credentials: credentials:
# Whether to enable LWM2M bootstrap server X509 Certificate/RPK support # Whether to enable LWM2M bootstrap server X509 Certificate/RPK support
enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:true}" enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:false}"
# Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore)
type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}" type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}"
# PEM server credentials # PEM server credentials
@ -794,19 +806,19 @@ transport:
# X509 trust certificates # X509 trust certificates
trust-credentials: trust-credentials:
# Whether to load X509 trust certificates # Whether to load X509 trust certificates
enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:true}" enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:false}"
# Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore) # Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore)
type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}" type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}"
# PEM certificates # PEM certificates
pem: pem:
# Path to the certificates file (holds trust certificates) # Path to the certificates file (holds trust certificates)
cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mserver.pem}" cert_file: "${LWM2M_TRUST_PEM_CERT:lwm2mtruststorechain.pem}"
# Keystore with trust certificates # Keystore with trust certificates
keystore: keystore:
# Type of the key store # Type of the key store
type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}" type: "${LWM2M_TRUST_KEY_STORE_TYPE:JKS}"
# Path to the key store that holds the X509 certificates # Path to the key store that holds the X509 certificates
store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mserver.jks}" store_file: "${LWM2M_TRUST_KEY_STORE:lwm2mtruststorechain.jks}"
# Password used to access the key store # Password used to access the key store
store_password: "${LWM2M_TRUST_KEY_STORE_PASSWORD:server_ks_password}" store_password: "${LWM2M_TRUST_KEY_STORE_PASSWORD:server_ks_password}"
recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}"
@ -817,8 +829,13 @@ transport:
ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}" ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}"
clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}" clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}"
log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}" log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}"
psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}"
# Use redis for Security and Registration stores # Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}" redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside
# - key: "PROTOCOL_STAGE_THREAD_COUNT"
# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}"
snmp: snmp:
enabled: "${SNMP_ENABLED:true}" enabled: "${SNMP_ENABLED:true}"
response_processing: response_processing:
@ -1026,12 +1043,12 @@ queue:
# For BATCH only # For BATCH only
batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch
processing-strategy: processing-strategy:
type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
# For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries; pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries. max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}" # Max allowed time in seconds for pause between retries.
- name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
@ -1043,12 +1060,12 @@ queue:
# For BATCH only # For BATCH only
batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
processing-strategy: processing-strategy:
type: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT type: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
# For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries. max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries.
- name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
@ -1060,12 +1077,12 @@ queue:
# For BATCH only # For BATCH only
batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch
processing-strategy: processing-strategy:
type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, SKIP_ALL_FAILURES_AND_TIMED_OUT, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
# For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT
retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}" # Time in seconds to wait in consumer thread before retries;
max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries. max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}" # Max allowed time in seconds for pause between retries.
transport: transport:
# For high priority notifications that require minimum latency and processing time # For high priority notifications that require minimum latency and processing time
notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"

257
application/src/test/java/org/thingsboard/server/actors/ActorSystemContextTest.java

@ -0,0 +1,257 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.actors;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.SmsService;
import org.thingsboard.rule.engine.api.sms.SmsSenderFactory;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeStateService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.executors.ExternalCallExecutorService;
import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.script.JsInvokeService;
import org.thingsboard.server.service.session.DeviceSessionCacheService;
import org.thingsboard.server.service.sms.SmsExecutorService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.TbCoreToTransportService;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = ActorSystemContext.class)
@EnableConfigurationProperties
@TestPropertySource(properties = {
"cache.type=caffeine",
})
public class ActorSystemContextTest {
@Autowired
ActorSystemContext ctx;
@MockBean
private TbApiUsageStateService apiUsageStateService;
@MockBean
private TbApiUsageClient apiUsageClient;
@MockBean
private TbServiceInfoProvider serviceInfoProvider;
@MockBean
private ActorService actorService;
@MockBean
private ComponentDiscoveryService componentService;
@MockBean
private DataDecodingEncodingService encodingService;
@MockBean
private DeviceService deviceService;
@MockBean
private TbTenantProfileCache tenantProfileCache;
@MockBean
private TbDeviceProfileCache deviceProfileCache;
@MockBean
private AssetService assetService;
@MockBean
private DashboardService dashboardService;
@MockBean
private TenantService tenantService;
@MockBean
private TenantProfileService tenantProfileService;
@MockBean
private CustomerService customerService;
@MockBean
private UserService userService;
@MockBean
private RuleChainService ruleChainService;
@MockBean
private RuleNodeStateService ruleNodeStateService;
@MockBean
private PartitionService partitionService;
@MockBean
private TbClusterService clusterService;
@MockBean
private TimeseriesService tsService;
@MockBean
private AttributesService attributesService;
@MockBean
private EventService eventService;
@MockBean
private RelationService relationService;
@MockBean
private AuditLogService auditLogService;
@MockBean
private EntityViewService entityViewService;
@MockBean
private TelemetrySubscriptionService tsSubService;
@MockBean
private AlarmSubscriptionService alarmService;
@MockBean
private JsInvokeService jsSandbox;
@MockBean
private MailExecutorService mailExecutor;
@MockBean
private SmsExecutorService smsExecutor;
@MockBean
private DbCallbackExecutorService dbCallbackExecutor;
@MockBean
private ExternalCallExecutorService externalCallExecutorService;
@MockBean
private SharedEventLoopGroupService sharedEventLoopGroupService;
@MockBean
private MailService mailService;
@MockBean
private SmsService smsService;
@MockBean
private SmsSenderFactory smsSenderFactory;
@MockBean
private ClaimDevicesService claimDevicesService;
@MockBean
private JsInvokeStats jsInvokeStats;
@MockBean
private DeviceStateService deviceStateService;
@MockBean
private DeviceSessionCacheService deviceSessionCacheService;
@MockBean
private TbCoreToTransportService tbCoreToTransportService;
@MockBean
private TbRuleEngineDeviceRpcService tbRuleEngineDeviceRpcService;
@MockBean
private TbCoreDeviceRpcService tbCoreDeviceRpcService;
@MockBean
private EdgeService edgeService;
@MockBean
private EdgeEventService edgeEventService;
@MockBean
private EdgeRpcService edgeRpcService;
@MockBean
private ResourceService resourceService;
@MockBean
private OtaPackageService otaPackageService;
@MockBean
private TbRpcService tbRpcService;
@MockBean
private CassandraCluster cassandraCluster;
@MockBean
private CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor;
@MockBean
private CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor;
@MockBean
private RedisTemplate<String, Object> redisTemplate;
@Test
void givenCaffeineCache_whenInit_thenIsLocalCacheTrue() {
assertThat(ctx.getCacheType()).isEqualTo("caffeine");
assertThat(ctx.isLocalCacheType()).as("caffeine is the local cache type").isTrue();
}
}

4
application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTestSuite.java → application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java

@ -29,11 +29,11 @@ import org.springframework.test.context.junit.jupiter.SpringExtension;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(SpringExtension.class) @ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = CaffeineCacheDefaultConfigurationTestSuite.class, loader = SpringBootContextLoader.class) @ContextConfiguration(classes = CaffeineCacheDefaultConfigurationTest.class, loader = SpringBootContextLoader.class)
@ComponentScan({"org.thingsboard.server.cache"}) @ComponentScan({"org.thingsboard.server.cache"})
@EnableConfigurationProperties @EnableConfigurationProperties
@Slf4j @Slf4j
public class CaffeineCacheDefaultConfigurationTestSuite { public class CaffeineCacheDefaultConfigurationTest {
@Autowired @Autowired
CaffeineCacheConfiguration caffeineCacheConfiguration; CaffeineCacheConfiguration caffeineCacheConfiguration;

42
application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.thingsboard.server.queue.memory.InMemoryStorage;
@Slf4j
public abstract class AbstractInMemoryStorageTest {
@Before
public void setUpInMemoryStorage() {
log.info("set up InMemoryStorage");
cleanupInMemStorage();
}
@After
public void tearDownInMemoryStorage() {
log.info("tear down InMemoryStorage");
cleanupInMemStorage();
}
public static void cleanupInMemStorage() {
InMemoryStorage.getInstance().cleanup();
}
}

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

Loading…
Cancel
Save