Browse Source

Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/6371

merge with master
pull/6374/head
Dmitriymush 4 years ago
parent
commit
eb70aa43b3
  1. 2
      application/pom.xml
  2. 83
      application/src/main/data/upgrade/3.3.4/schema_update.sql
  3. 230
      application/src/main/data/upgrade/3.4.0/schema_update.sql
  4. 159
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 18
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  6. 5
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  7. 4
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  8. 6
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  9. 4
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  10. 1
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  11. 6
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  12. 1
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  13. 5
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  14. 4
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  15. 68
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  16. 55
      application/src/main/java/org/thingsboard/server/controller/QueueController.java
  17. 13
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  18. 4
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  19. 1
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  20. 1
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  21. 4
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  22. 5
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  23. 5
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  24. 6
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  25. 2
      application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java
  26. 8
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java
  27. 8
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  28. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  29. 11
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  30. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java
  31. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java
  32. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java
  33. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  34. 14
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  35. 15
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  36. 57
      application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java
  37. 5
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java
  38. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java
  39. 304
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  40. 39
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  41. 48
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  42. 13
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  43. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
  44. 23
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java
  45. 6
      application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  47. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java
  48. 29
      application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
  49. 15
      application/src/main/resources/thingsboard.yml
  50. 3
      application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
  51. 17
      application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java
  52. 56
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  53. 19
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  54. 19
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  55. 13
      application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java
  56. 2
      application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
  57. 11
      application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
  58. 32
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  59. 72
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  60. 55
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  61. 1
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  62. 76
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  63. 66
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  64. 2
      common/actor/pom.xml
  65. 2
      common/cache/pom.xml
  66. 2
      common/cluster-api/pom.xml
  67. 8
      common/cluster-api/src/main/proto/queue.proto
  68. 2
      common/coap-server/pom.xml
  69. 2
      common/dao-api/pom.xml
  70. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
  71. 18
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  72. 12
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/DbTypeInfoComponent.java
  73. 33
      common/dao-api/src/main/java/org/thingsboard/server/dao/util/DefaultDbTypeInfoComponent.java
  74. 2
      common/data/pom.xml
  75. 4
      common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
  76. 4
      common/data/src/main/java/org/thingsboard/server/common/data/Customer.java
  77. 14
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  78. 6
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  79. 6
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  80. 42
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceIdInfo.java
  81. 6
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java
  82. 4
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  83. 6
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  84. 4
      common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java
  85. 14
      common/data/src/main/java/org/thingsboard/server/common/data/EventInfo.java
  86. 4
      common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java
  87. 2
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java
  88. 28
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
  89. 2
      common/data/src/main/java/org/thingsboard/server/common/data/SaveOtaPackageInfoRequest.java
  90. 4
      common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java
  91. 10
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  92. 4
      common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java
  93. 2
      common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java
  94. 8
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  95. 6
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  96. 5
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  97. 4
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
  98. 22
      common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java
  99. 24
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java
  100. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java

2
application/pom.xml

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

83
application/src/main/data/upgrade/3.3.4/schema_update.sql

@ -33,16 +33,7 @@ ALTER TABLE widgets_bundle
ALTER TABLE entity_view ALTER TABLE entity_view
ADD COLUMN IF NOT EXISTS external_id UUID; ADD COLUMN IF NOT EXISTS external_id UUID;
CREATE INDEX IF NOT EXISTS idx_device_external_id ON device(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_device_profile_external_id ON device_profile(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_asset_external_id ON asset(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_rule_chain_external_id ON rule_chain(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id, external_id); CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id, external_id);
CREATE INDEX IF NOT EXISTS idx_dashboard_external_id ON dashboard(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_customer_external_id ON customer(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_widgets_bundle_external_id ON widgets_bundle(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_entity_view_external_id ON entity_view(tenant_id, external_id);
CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type); CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type);
ALTER TABLE admin_settings ALTER TABLE admin_settings
@ -73,3 +64,77 @@ CREATE TABLE IF NOT EXISTS user_auth_settings (
CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id); CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id);
ALTER TABLE tenant_profile DROP COLUMN IF EXISTS isolated_tb_core; ALTER TABLE tenant_profile DROP COLUMN IF EXISTS isolated_tb_core;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'device_external_id_unq_key') THEN
ALTER TABLE device ADD CONSTRAINT device_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'device_profile_external_id_unq_key') THEN
ALTER TABLE device_profile ADD CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'asset_external_id_unq_key') THEN
ALTER TABLE asset ADD CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'rule_chain_external_id_unq_key') THEN
ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'dashboard_external_id_unq_key') THEN
ALTER TABLE dashboard ADD CONSTRAINT dashboard_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'customer_external_id_unq_key') THEN
ALTER TABLE customer ADD CONSTRAINT customer_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'widgets_bundle_external_id_unq_key') THEN
ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'entity_view_external_id_unq_key') THEN
ALTER TABLE entity_view ADD CONSTRAINT entity_view_external_id_unq_key UNIQUE (tenant_id, external_id);
END IF;
END;
$$;

230
application/src/main/data/upgrade/3.4.0/schema_update.sql

@ -0,0 +1,230 @@
--
-- Copyright © 2016-2022 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 rule_node_debug_event (
id uuid NOT NULL,
tenant_id uuid NOT NULL ,
ts bigint NOT NULL,
entity_id uuid NOT NULL,
service_id varchar,
e_type varchar,
e_entity_id uuid,
e_entity_type varchar,
e_msg_id uuid,
e_msg_type varchar,
e_data_type varchar,
e_relation_type varchar,
e_data varchar,
e_metadata varchar,
e_error varchar
) PARTITION BY RANGE (ts);
CREATE TABLE IF NOT EXISTS rule_chain_debug_event (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
ts bigint NOT NULL,
entity_id uuid NOT NULL,
service_id varchar NOT NULL,
e_message varchar,
e_error varchar
) PARTITION BY RANGE (ts);
CREATE TABLE IF NOT EXISTS stats_event (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
ts bigint NOT NULL,
entity_id uuid NOT NULL,
service_id varchar NOT NULL,
e_messages_processed bigint NOT NULL,
e_errors_occurred bigint NOT NULL
) PARTITION BY RANGE (ts);
CREATE TABLE IF NOT EXISTS lc_event (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
ts bigint NOT NULL,
entity_id uuid NOT NULL,
service_id varchar NOT NULL,
e_type varchar NOT NULL,
e_success boolean NOT NULL,
e_error varchar
) PARTITION BY RANGE (ts);
CREATE TABLE IF NOT EXISTS error_event (
id uuid NOT NULL,
tenant_id uuid NOT NULL,
ts bigint NOT NULL,
entity_id uuid NOT NULL,
service_id varchar NOT NULL,
e_method varchar NOT NULL,
e_error varchar
) PARTITION BY RANGE (ts);
CREATE INDEX IF NOT EXISTS idx_rule_node_debug_event_main
ON rule_node_debug_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95);
CREATE INDEX IF NOT EXISTS idx_rule_chain_debug_event_main
ON rule_chain_debug_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95);
CREATE INDEX IF NOT EXISTS idx_stats_event_main
ON stats_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95);
CREATE INDEX IF NOT EXISTS idx_lc_event_main
ON lc_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95);
CREATE INDEX IF NOT EXISTS idx_error_event_main
ON error_event (tenant_id ASC, entity_id ASC, ts DESC NULLS LAST) WITH (FILLFACTOR=95);
CREATE OR REPLACE FUNCTION to_safe_json(p_json text) RETURNS json
LANGUAGE plpgsql AS
$$
BEGIN
return REPLACE(p_json, '\u0000', '' )::json;
EXCEPTION
WHEN OTHERS THEN
return '{}'::json;
END;
$$;
-- Useful to migrate old events to the new table structure;
CREATE OR REPLACE PROCEDURE migrate_regular_events(IN start_ts_in_ms bigint, IN end_ts_in_ms bigint, IN partition_size_in_hours int)
LANGUAGE plpgsql AS
$$
DECLARE
partition_size_in_ms bigint;
p record;
table_name varchar;
BEGIN
partition_size_in_ms = partition_size_in_hours * 3600 * 1000;
FOR p IN SELECT DISTINCT event_type as event_type, (created_time - created_time % partition_size_in_ms) as partition_ts FROM event e WHERE e.event_type in ('STATS', 'LC_EVENT', 'ERROR') and ts >= start_ts_in_ms and ts < end_ts_in_ms
LOOP
IF p.event_type = 'STATS' THEN
table_name := 'stats_event';
ELSEIF p.event_type = 'LC_EVENT' THEN
table_name := 'lc_event';
ELSEIF p.event_type = 'ERROR' THEN
table_name := 'error_event';
END IF;
RAISE NOTICE '[%] Partition to create : [%-%]', table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms);
EXECUTE format('CREATE TABLE IF NOT EXISTS %s_%s PARTITION OF %s FOR VALUES FROM ( %s ) TO ( %s )', table_name, p.partition_ts, table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms));
END LOOP;
INSERT INTO stats_event
SELECT id,
tenant_id,
ts,
entity_id,
body ->> 'server',
(body ->> 'messagesProcessed')::bigint,
(body ->> 'errorsOccurred')::bigint
FROM
(select id, tenant_id, ts, entity_id, to_safe_json(body) as body
FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'STATS' AND to_safe_json(body) ->> 'server' IS NOT NULL
) safe_event
ON CONFLICT DO NOTHING;
INSERT INTO lc_event
SELECT id,
tenant_id,
ts,
entity_id,
body ->> 'server',
body ->> 'event',
(body ->> 'success')::boolean,
body ->> 'error'
FROM
(select id, tenant_id, ts, entity_id, to_safe_json(body) as body
FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'LC_EVENT' AND to_safe_json(body) ->> 'server' IS NOT NULL
) safe_event
ON CONFLICT DO NOTHING;
INSERT INTO error_event
SELECT id,
tenant_id,
ts,
entity_id,
body ->> 'server',
body ->> 'method',
body ->> 'error'
FROM
(select id, tenant_id, ts, entity_id, to_safe_json(body) as body
FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'ERROR' AND to_safe_json(body) ->> 'server' IS NOT NULL
) safe_event
ON CONFLICT DO NOTHING;
END
$$;
-- Useful to migrate old debug events to the new table structure;
CREATE OR REPLACE PROCEDURE migrate_debug_events(IN start_ts_in_ms bigint, IN end_ts_in_ms bigint, IN partition_size_in_hours int)
LANGUAGE plpgsql AS
$$
DECLARE
partition_size_in_ms bigint;
p record;
table_name varchar;
BEGIN
partition_size_in_ms = partition_size_in_hours * 3600 * 1000;
FOR p IN SELECT DISTINCT event_type as event_type, (created_time - created_time % partition_size_in_ms) as partition_ts FROM event e WHERE e.event_type in ('DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') and ts >= start_ts_in_ms and ts < end_ts_in_ms
LOOP
IF p.event_type = 'DEBUG_RULE_NODE' THEN
table_name := 'rule_node_debug_event';
ELSEIF p.event_type = 'DEBUG_RULE_CHAIN' THEN
table_name := 'rule_chain_debug_event';
END IF;
RAISE NOTICE '[%] Partition to create : [%-%]', table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms);
EXECUTE format('CREATE TABLE IF NOT EXISTS %s_%s PARTITION OF %s FOR VALUES FROM ( %s ) TO ( %s )', table_name, p.partition_ts, table_name, p.partition_ts, (p.partition_ts + partition_size_in_ms));
END LOOP;
INSERT INTO rule_node_debug_event
SELECT id,
tenant_id,
ts,
entity_id,
body ->> 'server',
body ->> 'type',
(body ->> 'entityId')::uuid,
body ->> 'entityName',
(body ->> 'msgId')::uuid,
body ->> 'msgType',
body ->> 'dataType',
body ->> 'relationType',
body ->> 'data',
body ->> 'metadata',
body ->> 'error'
FROM
(select id, tenant_id, ts, entity_id, to_safe_json(body) as body
FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'DEBUG_RULE_NODE' AND to_safe_json(body) ->> 'server' IS NOT NULL
) safe_event
ON CONFLICT DO NOTHING;
INSERT INTO rule_chain_debug_event
SELECT id,
tenant_id,
ts,
entity_id,
body ->> 'server',
body ->> 'message',
body ->> 'error'
FROM
(select id, tenant_id, ts, entity_id, to_safe_json(body) as body
FROM event WHERE ts >= start_ts_in_ms and ts < end_ts_in_ms AND event_type = 'DEBUG_RULE_CHAIN' AND to_safe_json(body) ->> 'server' IS NOT NULL
) safe_event
ON CONFLICT DO NOTHING;
END
$$;

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

@ -37,10 +37,11 @@ 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.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.event.ErrorEvent;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.event.LifecycleEvent;
import org.thingsboard.server.common.data.event.RuleChainDebugEvent;
import org.thingsboard.server.common.data.event.RuleNodeDebugEvent;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
@ -112,6 +113,29 @@ import java.util.concurrent.TimeUnit;
@Component @Component
public class ActorSystemContext { public class ActorSystemContext {
private static final FutureCallback<Void> RULE_CHAIN_DEBUG_EVENT_ERROR_CALLBACK = new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void event) {
}
@Override
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Rule Chain", th);
}
};
private static final FutureCallback<Void> RULE_NODE_DEBUG_EVENT_ERROR_CALLBACK = new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void event) {
}
@Override
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Node", th);
}
};
protected final ObjectMapper mapper = new ObjectMapper(); protected final ObjectMapper mapper = new ObjectMapper();
private final ConcurrentMap<TenantId, DebugTbRateLimits> debugPerTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, DebugTbRateLimits> debugPerTenantLimits = new ConcurrentHashMap<>();
@ -462,25 +486,28 @@ public class ActorSystemContext {
} }
public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) { public void persistError(TenantId tenantId, EntityId entityId, String method, Exception e) {
Event event = new Event(); eventService.saveAsync(ErrorEvent.builder()
event.setTenantId(tenantId); .tenantId(tenantId)
event.setEntityId(entityId); .entityId(entityId.getId())
event.setType(DataConstants.ERROR); .serviceId(getServiceId())
event.setBody(toBodyJson(serviceInfoProvider.getServiceInfo().getServiceId(), method, toString(e))); .method(method)
persistEvent(event); .error(toString(e)).build());
} }
public void persistLifecycleEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent lcEvent, Exception e) { public void persistLifecycleEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent lcEvent, Exception e) {
Event event = new Event(); LifecycleEvent.LifecycleEventBuilder event = LifecycleEvent.builder()
event.setTenantId(tenantId); .tenantId(tenantId)
event.setEntityId(entityId); .entityId(entityId.getId())
event.setType(DataConstants.LC_EVENT); .serviceId(getServiceId())
event.setBody(toBodyJson(serviceInfoProvider.getServiceInfo().getServiceId(), lcEvent, Optional.ofNullable(e))); .lcEventType(lcEvent.name());
persistEvent(event);
} if (e != null) {
event.success(false).error(toString(e));
} else {
event.success(true);
}
private void persistEvent(Event event) { eventService.saveAsync(event.build());
eventService.saveAsync(event);
} }
private String toString(Throwable e) { private String toString(Throwable e) {
@ -489,21 +516,6 @@ public class ActorSystemContext {
return sw.toString(); return sw.toString();
} }
private JsonNode toBodyJson(String serviceId, ComponentLifecycleEvent event, Optional<Exception> e) {
ObjectNode node = mapper.createObjectNode().put("server", serviceId).put("event", event.name());
if (e.isPresent()) {
node = node.put("success", false);
node = node.put("error", toString(e.get()));
} else {
node = node.put("success", true);
}
return node;
}
private JsonNode toBodyJson(String serviceId, String method, String body) {
return mapper.createObjectNode().put("server", serviceId).put("method", method).put("error", body);
}
public TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId) { public TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId) {
return partitionService.resolve(serviceType, tenantId, entityId); return partitionService.resolve(serviceType, tenantId, entityId);
} }
@ -539,44 +551,27 @@ public class ActorSystemContext {
private void persistDebugAsync(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, String relationType, Throwable error, String failureMessage) { private void persistDebugAsync(TenantId tenantId, EntityId entityId, String type, TbMsg tbMsg, String relationType, Throwable error, String failureMessage) {
if (checkLimits(tenantId, tbMsg, error)) { if (checkLimits(tenantId, tbMsg, error)) {
try { try {
Event event = new Event(); RuleNodeDebugEvent.RuleNodeDebugEventBuilder event = RuleNodeDebugEvent.builder()
event.setTenantId(tenantId); .tenantId(tenantId)
event.setEntityId(entityId); .entityId(entityId.getId())
event.setType(DataConstants.DEBUG_RULE_NODE); .serviceId(getServiceId())
.eventType(type)
String metadata = mapper.writeValueAsString(tbMsg.getMetaData().getData()); .eventEntity(tbMsg.getOriginator())
.msgId(tbMsg.getId())
ObjectNode node = mapper.createObjectNode() .msgType(tbMsg.getType())
.put("type", type) .dataType(tbMsg.getDataType().name())
.put("server", getServiceId()) .relationType(relationType)
.put("entityId", tbMsg.getOriginator().getId().toString()) .data(tbMsg.getData())
.put("entityName", tbMsg.getOriginator().getEntityType().name()) .metadata(mapper.writeValueAsString(tbMsg.getMetaData().getData()));
.put("msgId", tbMsg.getId().toString())
.put("msgType", tbMsg.getType())
.put("dataType", tbMsg.getDataType().name())
.put("relationType", relationType)
.put("data", tbMsg.getData())
.put("metadata", metadata);
if (error != null) { if (error != null) {
node = node.put("error", toString(error)); event.error(toString(error));
} else if (failureMessage != null) { } else if (failureMessage != null) {
node = node.put("error", failureMessage); event.error(failureMessage);
} }
event.setBody(node); ListenableFuture<Void> future = eventService.saveAsync(event.build());
ListenableFuture<Void> future = eventService.saveAsync(event); Futures.addCallback(future, RULE_NODE_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void event) {
}
@Override
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Node", th);
}
}, MoreExecutors.directExecutor());
} catch (IOException ex) { } catch (IOException ex) {
log.warn("Failed to persist rule node debug message", ex); log.warn("Failed to persist rule node debug message", ex);
} }
@ -603,33 +598,17 @@ public class ActorSystemContext {
} }
private void persistRuleChainDebugModeEvent(TenantId tenantId, EntityId entityId, Throwable error) { private void persistRuleChainDebugModeEvent(TenantId tenantId, EntityId entityId, Throwable error) {
Event event = new Event(); RuleChainDebugEvent.RuleChainDebugEventBuilder event = RuleChainDebugEvent.builder()
event.setTenantId(tenantId); .tenantId(tenantId)
event.setEntityId(entityId); .entityId(entityId.getId())
event.setType(DataConstants.DEBUG_RULE_CHAIN); .serviceId(getServiceId())
.message("Reached debug mode rate limit!");
ObjectNode node = mapper.createObjectNode()
//todo: what fields are needed here?
.put("server", getServiceId())
.put("message", "Reached debug mode rate limit!");
if (error != null) { if (error != null) {
node = node.put("error", toString(error)); event.error(toString(error));
} }
event.setBody(node); ListenableFuture<Void> future = eventService.saveAsync(event.build());
ListenableFuture<Void> future = eventService.saveAsync(event); Futures.addCallback(future, RULE_CHAIN_DEBUG_EVENT_ERROR_CALLBACK, MoreExecutors.directExecutor());
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Void event) {
}
@Override
public void onFailure(Throwable th) {
log.error("Could not save debug Event for Rule Chain", th);
}
}, MoreExecutors.directExecutor());
} }
public static Exception toException(Throwable error) { public static Exception toException(Throwable error) {

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

@ -20,13 +20,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
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.TbActorId; import org.thingsboard.server.actors.TbActorId;
import org.thingsboard.server.actors.TbStringActorId; import org.thingsboard.server.actors.TbStringActorId;
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.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.StatisticsEvent;
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;
@ -54,12 +54,14 @@ public class StatsActor extends ContextAwareActor {
if (msg.isEmpty()) { if (msg.isEmpty()) {
return; return;
} }
Event event = new Event(); systemContext.getEventService().saveAsync(StatisticsEvent.builder()
event.setEntityId(msg.getEntityId()); .tenantId(msg.getTenantId())
event.setTenantId(msg.getTenantId()); .entityId(msg.getEntityId().getId())
event.setType(DataConstants.STATS); .serviceId(systemContext.getServiceInfoProvider().getServiceId())
event.setBody(toBodyJson(systemContext.getServiceInfoProvider().getServiceId(), msg.getMessagesProcessed(), msg.getErrorsOccurred())); .messagesProcessed(msg.getMessagesProcessed())
systemContext.getEventService().saveAsync(event); .errorsOccurred(msg.getErrorsOccurred())
.build()
);
} }
private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) { private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) {

5
application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java

@ -18,7 +18,6 @@ package org.thingsboard.server.config;
import com.fasterxml.classmate.TypeResolver; import com.fasterxml.classmate.TypeResolver;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -153,7 +152,7 @@ public class SwaggerConfiguration {
} }
@Override @Override
public boolean supports(@NotNull DocumentationType delimiter) { public boolean supports(DocumentationType delimiter) {
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter); return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
} }
}; };
@ -175,7 +174,7 @@ public class SwaggerConfiguration {
} }
@Override @Override
public boolean supports(@NotNull DocumentationType delimiter) { public boolean supports(DocumentationType delimiter) {
return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter); return DocumentationType.SWAGGER_2.equals(delimiter) || DocumentationType.OAS_30.equals(delimiter);
} }
}; };

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

@ -126,7 +126,9 @@ public class AlarmController extends BaseController {
"\n\nPlatform also deduplicate the alarms based on the entity id of originator and alarm 'type'. " + "\n\nPlatform also deduplicate the alarms based on the entity id of originator and alarm 'type'. " +
"For example, if the user or system component create the alarm with the type 'HighTemperature' for device 'Device A' the new active alarm is created. " + "For example, if the user or system component create the alarm with the type 'HighTemperature' for device 'Device A' the new active alarm is created. " +
"If the user tries to create 'HighTemperature' alarm for the same device again, the previous alarm will be updated (the 'end_ts' will be set to current timestamp). " + "If the user tries to create 'HighTemperature' alarm for the same device again, the previous alarm will be updated (the 'end_ts' will be set to current timestamp). " +
"If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH "If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Alarm entity. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
, produces = MediaType.APPLICATION_JSON_VALUE) , produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm", method = RequestMethod.POST) @RequestMapping(value = "/alarm", method = RequestMethod.POST)

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

@ -94,7 +94,7 @@ public class ControllerConstants {
protected static final String DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, transportType, description, isDefault"; protected static final String DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, transportType, description, isDefault";
protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; protected static final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status"; protected static final String ALARM_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, startTs, endTs, type, ackTs, clearTs, severity, status";
protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, id"; protected static final String EVENT_SORT_PROPERTY_ALLOWABLE_VALUES = "ts, id";
protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle"; protected static final String EDGE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root"; protected static final String RULE_CHAIN_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, root";
protected static final String WIDGET_BUNDLE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, tenantId"; protected static final String WIDGET_BUNDLE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, tenantId";
@ -112,6 +112,10 @@ public class ControllerConstants {
protected static final String DEVICE_PROFILE_INFO_DESCRIPTION = "Device Profile Info is a lightweight object that includes main information about Device Profile excluding the heavyweight configuration object. "; protected static final String DEVICE_PROFILE_INFO_DESCRIPTION = "Device Profile Info is a lightweight object that includes main information about Device Profile excluding the heavyweight configuration object. ";
protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)"; protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)";
protected static final String QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES = "TB-RULE-ENGINE, TB-CORE, TB-TRANSPORT, JS-EXECUTOR"; protected static final String QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES = "TB-RULE-ENGINE, TB-CORE, TB-TRANSPORT, JS-EXECUTOR";
protected static final String QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name.";
protected static final String QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, topic";
protected static final String QUEUE_ID_PARAM_DESCRIPTION = "A string value representing the queue id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String QUEUE_NAME_PARAM_DESCRIPTION = "A string value representing the queue id. For example, 'Main'";
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";

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

@ -138,7 +138,9 @@ public class CustomerController extends BaseController {
notes = "Creates or Updates the Customer. When creating customer, platform generates Customer Id as " + UUID_WIKI_LINK + notes = "Creates or Updates the Customer. When creating customer, platform generates Customer Id as " + UUID_WIKI_LINK +
"The newly created Customer Id will be present in the response. " + "The newly created Customer Id will be present in the response. " +
"Specify existing Customer Id to update the Customer. " + "Specify existing Customer Id to update the Customer. " +
"Referencing non-existing Customer Id will cause 'Not Found' error." + TENANT_AUTHORITY_PARAGRAPH) "Referencing non-existing Customer Id will cause 'Not Found' error." +
"Remove 'id', 'tenantId' from the request body example (below) to create new Customer entity. " +
TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer", method = RequestMethod.POST) @RequestMapping(value = "/customer", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -172,6 +172,7 @@ public class DashboardController extends BaseController {
"The newly created Dashboard id will be present in the response. " + "The newly created Dashboard id will be present in the response. " +
"Specify existing Dashboard id to update the dashboard. " + "Specify existing Dashboard id to update the dashboard. " +
"Referencing non-existing dashboard Id will cause 'Not Found' error. " + "Referencing non-existing dashboard Id will cause 'Not Found' error. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Dashboard entity. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE) consumes = MediaType.APPLICATION_JSON_VALUE)

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

@ -158,8 +158,9 @@ public class DeviceController extends BaseController {
"The newly created device id will be present in the response. " + "The newly created device id will be present in the response. " +
"Specify existing Device id to update the device. " + "Specify existing Device id to update the device. " +
"Referencing non-existing device Id will cause 'Not Found' error." + "Referencing non-existing device Id will cause 'Not Found' error." +
"\n\nDevice name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the device names and non-unique 'label' field for user-friendly visualization purposes." "\n\nDevice name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the device names and non-unique 'label' field for user-friendly visualization purposes." +
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device", method = RequestMethod.POST) @RequestMapping(value = "/device", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -181,6 +182,7 @@ public class DeviceController extends BaseController {
"Requires to provide the Device Credentials object as well. Useful to create device and credentials in one request. " + "Requires to provide the Device Credentials object as well. Useful to create device and credentials in one request. " +
"You may find the example of LwM2M device and RPK credentials below: \n\n" + "You may find the example of LwM2M device and RPK credentials below: \n\n" +
DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST) @RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST)

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

@ -191,6 +191,7 @@ public class DeviceProfileController extends BaseController {
"Specify existing device profile id to update the device profile. " + "Specify existing device profile id to update the device profile. " +
"Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE + "Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE +
"Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA + "Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device Profile entity. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json", produces = "application/json",
consumes = "application/json") consumes = "application/json")

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

@ -141,8 +141,9 @@ public class EdgeController extends BaseController {
"The newly created edge id will be present in the response. " + "The newly created edge id will be present in the response. " +
"Specify existing Edge id to update the edge. " + "Specify existing Edge id to update the edge. " +
"Referencing non-existing Edge Id will cause 'Not Found' error." + "Referencing non-existing Edge Id will cause 'Not Found' error." +
"\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." +
+ TENANT_AUTHORITY_PARAGRAPH, "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " +
TENANT_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge", method = RequestMethod.POST) @RequestMapping(value = "/edge", method = RequestMethod.POST)

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

@ -132,7 +132,9 @@ public class EntityViewController extends BaseController {
} }
@ApiOperation(value = "Save or update entity view (saveEntityView)", @ApiOperation(value = "Save or update entity view (saveEntityView)",
notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION, notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Entity View entity." +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
produces = MediaType.APPLICATION_JSON_VALUE) produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView", method = RequestMethod.POST) @RequestMapping(value = "/entityView", method = RequestMethod.POST)

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

@ -29,8 +29,11 @@ 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.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.DataConstants;
import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
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;
@ -42,6 +45,8 @@ import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Operation;
import java.util.Locale;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE; import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE;
@ -110,7 +115,7 @@ public class EventController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET) @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Event> getEvents( public PageData<EventInfo> getEvents(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable(ENTITY_TYPE) String strEntityType, @PathVariable(ENTITY_TYPE) String strEntityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@ -135,16 +140,12 @@ public class EventController extends BaseController {
@RequestParam(required = false) Long endTime) throws ThingsboardException { @RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
try { TenantId tenantId = TenantId.fromUUID(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);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEvents(tenantId, entityId, eventType, pageLink)); return checkNotNull(eventService.findEvents(tenantId, entityId, resolveEventType(eventType), pageLink));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get Events (getEvents)", @ApiOperation(value = "Get Events (getEvents)",
@ -153,7 +154,7 @@ public class EventController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET) @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Event> getEvents( public PageData<EventInfo> getEvents(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable(ENTITY_TYPE) String strEntityType, @PathVariable(ENTITY_TYPE) String strEntityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@ -176,18 +177,14 @@ public class EventController extends BaseController {
@RequestParam(required = false) Long endTime) throws ThingsboardException { @RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
try { TenantId tenantId = TenantId.fromUUID(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);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEvents(tenantId, entityId, pageLink)); return checkNotNull(eventService.findEvents(tenantId, entityId, EventType.LC_EVENT, pageLink));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Get Events by event filter (getEvents)", @ApiOperation(value = "Get Events by event filter (getEvents)",
@ -198,7 +195,7 @@ public class EventController extends BaseController {
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST) @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public PageData<Event> getEvents( public PageData<EventInfo> getEvents(
@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true)
@PathVariable(ENTITY_TYPE) String strEntityType, @PathVariable(ENTITY_TYPE) String strEntityType,
@ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
@ -223,21 +220,13 @@ public class EventController extends BaseController {
@RequestParam(required = false) Long endTime) throws ThingsboardException { @RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter("EntityId", strEntityId); checkParameter("EntityId", strEntityId);
checkParameter("EntityType", strEntityType); checkParameter("EntityType", strEntityType);
try { TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId));
EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
checkEntityId(entityId, Operation.READ);
if (sortProperty != null && sortProperty.equals("createdTime") && eventFilter.hasFilterForJsonBody()) { EntityId entityId = EntityIdFactory.getByTypeAndId(strEntityType, strEntityId);
sortProperty = ModelConstants.CREATED_TIME_PROPERTY; checkEntityId(entityId, Operation.READ);
}
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
return checkNotNull(eventService.findEventsByFilter(tenantId, entityId, eventFilter, pageLink)); return checkNotNull(eventService.findEventsByFilter(tenantId, entityId, eventFilter, pageLink));
} catch (Exception e) {
throw handleException(e);
}
} }
@ApiOperation(value = "Clear Events (clearEvents)", notes = "Clears events by filter for specified entity.") @ApiOperation(value = "Clear Events (clearEvents)", notes = "Clears events by filter for specified entity.")
@ -266,4 +255,13 @@ public class EventController extends BaseController {
} }
} }
private static EventType resolveEventType(String eventType) throws ThingsboardException {
for (var et : EventType.values()) {
if (et.name().equalsIgnoreCase(eventType) || et.getOldName().equalsIgnoreCase(eventType)) {
return et;
}
}
throw new ThingsboardException("Event type: '" + eventType + "' is not supported!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
} }

55
application/src/main/java/org/thingsboard/server/controller/QueueController.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
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;
@ -37,6 +39,22 @@ import org.thingsboard.server.service.security.permission.Resource;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_NAME_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH;
import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@RequestMapping("/api") @RequestMapping("/api")
@ -45,14 +63,23 @@ public class QueueController extends BaseController {
private final TbQueueService tbQueueService; private final TbQueueService tbQueueService;
@ApiOperation(value = "Get Queues (getTenantQueuesByServiceType)",
notes = "Returns a page of queues registered in the platform. " +
PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/queues", params = {"serviceType", "pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/queues", params = {"serviceType", "pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Queue> getTenantQueuesByServiceType(@RequestParam String serviceType, public PageData<Queue> getTenantQueuesByServiceType(@ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true)
@RequestParam String serviceType,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("serviceType", serviceType); checkParameter("serviceType", serviceType);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
@ -65,28 +92,44 @@ public class QueueController extends BaseController {
} }
} }
@ApiOperation(value = "Get Queue (getQueueById)",
notes = "Fetch the Queue object based on the provided Queue Id. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/queues/{queueId}", method = RequestMethod.GET) @RequestMapping(value = "/queues/{queueId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Queue getQueueById(@PathVariable("queueId") String queueIdStr) throws ThingsboardException { public Queue getQueueById(@ApiParam(value = QUEUE_ID_PARAM_DESCRIPTION)
@PathVariable("queueId") String queueIdStr) throws ThingsboardException {
checkParameter("queueId", queueIdStr); checkParameter("queueId", queueIdStr);
QueueId queueId = new QueueId(UUID.fromString(queueIdStr)); QueueId queueId = new QueueId(UUID.fromString(queueIdStr));
checkQueueId(queueId, Operation.READ); checkQueueId(queueId, Operation.READ);
return checkNotNull(queueService.findQueueById(getTenantId(), queueId)); return checkNotNull(queueService.findQueueById(getTenantId(), queueId));
} }
@ApiOperation(value = "Get Queue (getQueueByName)",
notes = "Fetch the Queue object based on the provided Queue name. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/queues/name/{queueName}", method = RequestMethod.GET) @RequestMapping(value = "/queues/name/{queueName}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Queue getQueueByName(@PathVariable("queueName") String queueName) throws ThingsboardException { public Queue getQueueByName(@ApiParam(value = QUEUE_NAME_PARAM_DESCRIPTION)
@PathVariable("queueName") String queueName) throws ThingsboardException {
checkParameter("queueName", queueName); checkParameter("queueName", queueName);
return checkNotNull(queueService.findQueueByTenantIdAndName(getTenantId(), queueName)); return checkNotNull(queueService.findQueueByTenantIdAndName(getTenantId(), queueName));
} }
@ApiOperation(value = "Create Or Update Queue (saveQueue)",
notes = "Create or update the Queue. When creating queue, platform generates Queue Id as " + UUID_WIKI_LINK +
"Specify existing Queue id to update the queue. " +
"Referencing non-existing Queue Id will cause 'Not Found' error." +
"\n\nQueue name is unique in the scope of sysadmin. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Queue entity. " +
SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/queues", params = {"serviceType"}, method = RequestMethod.POST) @RequestMapping(value = "/queues", params = {"serviceType"}, method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Queue saveQueue(@RequestBody Queue queue,
public Queue saveQueue(@ApiParam(value = "A JSON value representing the queue.")
@RequestBody Queue queue,
@ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true)
@RequestParam String serviceType) throws ThingsboardException { @RequestParam String serviceType) throws ThingsboardException {
checkParameter("serviceType", serviceType); checkParameter("serviceType", serviceType);
queue.setTenantId(getCurrentUser().getTenantId()); queue.setTenantId(getCurrentUser().getTenantId());
@ -105,10 +148,12 @@ public class QueueController extends BaseController {
} }
} }
@ApiOperation(value = "Delete Queue (deleteQueue)", notes = "Deletes the Queue. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/queues/{queueId}", method = RequestMethod.DELETE) @RequestMapping(value = "/queues/{queueId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public void deleteQueue(@PathVariable("queueId") String queueIdStr) throws ThingsboardException { public void deleteQueue(@ApiParam(value = QUEUE_ID_PARAM_DESCRIPTION)
@PathVariable("queueId") String queueIdStr) throws ThingsboardException {
checkParameter("queueId", queueIdStr); checkParameter("queueId", queueIdStr);
QueueId queueId = new QueueId(toUUID(queueIdStr)); QueueId queueId = new QueueId(toUUID(queueIdStr));
checkQueueId(queueId, Operation.DELETE); checkQueueId(queueId, Operation.DELETE);

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

@ -39,10 +39,10 @@ import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
@ -70,7 +70,6 @@ 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.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -227,7 +226,9 @@ public class RuleChainController extends BaseController {
"The newly created Rule Chain Id will be present in the response. " + "The newly created Rule Chain Id will be present in the response. " +
"Specify existing Rule Chain id to update the rule chain. " + "Specify existing Rule Chain id to update the rule chain. " +
"Referencing non-existing rule chain Id will cause 'Not Found' error." + "Referencing non-existing rule chain Id will cause 'Not Found' error." +
"\n\n" + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH) "\n\n" + RULE_CHAIN_DESCRIPTION +
"Remove 'id', 'tenantId' from the request body example (below) to create new Rule Chain entity." +
TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/ruleChain", method = RequestMethod.POST) @RequestMapping(value = "/ruleChain", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -351,10 +352,10 @@ public class RuleChainController extends BaseController {
RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId)); RuleNodeId ruleNodeId = new RuleNodeId(toUUID(strRuleNodeId));
checkRuleNode(ruleNodeId, Operation.READ); checkRuleNode(ruleNodeId, Operation.READ);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
List<Event> events = eventService.findLatestEvents(tenantId, ruleNodeId, DataConstants.DEBUG_RULE_NODE, 2); List<EventInfo> events = eventService.findLatestEvents(tenantId, ruleNodeId, EventType.DEBUG_RULE_NODE, 2);
JsonNode result = null; JsonNode result = null;
if (events != null) { if (events != null) {
for (Event event : events) { for (EventInfo event : events) {
JsonNode body = event.getBody(); JsonNode body = event.getBody();
if (body.has("type") && body.get("type").asText().equals("IN")) { if (body.has("type") && body.get("type").asText().equals("IN")) {
result = body; result = body;

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

@ -139,7 +139,9 @@ public class TbResourceController extends BaseController {
"The newly created Resource id will be present in the response. " + "The newly created Resource id will be present in the response. " +
"Specify existing Resource id to update the Resource. " + "Specify existing Resource id to update the Resource. " +
"Referencing non-existing Resource Id will cause 'Not Found' error. " + "Referencing non-existing Resource Id will cause 'Not Found' error. " +
"\n\nResource combination of the title with the key is unique in the scope of tenant. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, "\n\nResource combination of the title with the key is unique in the scope of tenant. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Resource entity." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json", produces = "application/json",
consumes = "application/json") consumes = "application/json")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")

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

@ -115,6 +115,7 @@ public class TenantController extends BaseController {
"The newly created Tenant Id will be present in the response. " + "The newly created Tenant Id will be present in the response. " +
"Specify existing Tenant Id id to update the Tenant. " + "Specify existing Tenant Id id to update the Tenant. " +
"Referencing non-existing Tenant Id will cause 'Not Found' error." + "Referencing non-existing Tenant Id will cause 'Not Found' error." +
"Remove 'id', 'tenantId' from the request body example (below) to create new Tenant entity." +
SYSTEM_AUTHORITY_PARAGRAPH) SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant", method = RequestMethod.POST) @RequestMapping(value = "/tenant", method = RequestMethod.POST)

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

@ -164,6 +164,7 @@ public class TenantProfileController extends BaseController {
" \"default\": true\n" + " \"default\": true\n" +
"}" + "}" +
MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END +
"Remove 'id', from the request body example (below) to create new Tenant Profile entity." +
SYSTEM_AUTHORITY_PARAGRAPH) SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenantProfile", method = RequestMethod.POST) @RequestMapping(value = "/tenantProfile", method = RequestMethod.POST)

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

@ -176,7 +176,9 @@ public class UserController extends BaseController {
"The newly created User Id will be present in the response. " + "The newly created User Id will be present in the response. " +
"Specify existing User Id to update the device. " + "Specify existing User Id to update the device. " +
"Referencing non-existing User Id will cause 'Not Found' error." + "Referencing non-existing User Id will cause 'Not Found' error." +
"\n\nDevice email is unique for entire platform setup.") "\n\nDevice email is unique for entire platform setup." +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new User entity." +
"\n\nAvailable for users with 'SYS_ADMIN', 'TENANT_ADMIN' or 'CUSTOMER_USER' authority.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user", method = RequestMethod.POST) @RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -85,8 +85,9 @@ public class WidgetTypeController extends AutoCommitController {
"Specify existing Widget Type id to update the Widget Type. " + "Specify existing Widget Type id to update the Widget Type. " +
"Referencing non-existing Widget Type Id will cause 'Not Found' error." + "Referencing non-existing Widget Type Id will cause 'Not Found' error." +
"\n\nWidget Type alias is unique in the scope of Widget Bundle. " + "\n\nWidget Type alias is unique in the scope of Widget Bundle. " +
"Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create request is sent by user with 'SYS_ADMIN' authority." "Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create request is sent by user with 'SYS_ADMIN' authority." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) "Remove 'id', 'tenantId' rom the request body example (below) to create new Widget Type entity." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/widgetType", method = RequestMethod.POST) @RequestMapping(value = "/widgetType", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -89,8 +89,9 @@ public class WidgetsBundleController extends BaseController {
"Specify existing Widget Bundle id to update the Widget Bundle. " + "Specify existing Widget Bundle id to update the Widget Bundle. " +
"Referencing non-existing Widget Bundle Id will cause 'Not Found' error." + "Referencing non-existing Widget Bundle Id will cause 'Not Found' error." +
"\n\nWidget Bundle alias is unique in the scope of tenant. " + "\n\nWidget Bundle alias is unique in the scope of tenant. " +
"Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create bundle request is sent by user with 'SYS_ADMIN' authority." "Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create bundle request is sent by user with 'SYS_ADMIN' authority." +
+ SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) "Remove 'id', 'tenantId' from the request body example (below) to create new Widgets Bundle entity." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/widgetsBundle", method = RequestMethod.POST) @RequestMapping(value = "/widgetsBundle", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -143,7 +143,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
externalSessionMap.put(externalSessionId, internalSessionId); externalSessionMap.put(externalSessionId, internalSessionId);
processInWebSocketService(sessionRef, SessionEvent.onEstablished()); processInWebSocketService(sessionRef, SessionEvent.onEstablished());
log.info("[{}][{}][{}] Session is opened", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId()); log.info("[{}][{}][{}] Session is opened from address: {}", sessionRef.getSecurityCtx().getTenantId(), externalSessionId, session.getId(), session.getRemoteAddress());
} catch (InvalidParameterException e) { } catch (InvalidParameterException e) {
log.warn("[{}] Failed to start session", session.getId(), e); log.warn("[{}] Failed to start session", session.getId(), e);
session.close(CloseStatus.BAD_DATA.withReason(e.getMessage())); session.close(CloseStatus.BAD_DATA.withReason(e.getMessage()));
@ -173,8 +173,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
cleanupLimits(session, sessionMd.sessionRef); cleanupLimits(session, sessionMd.sessionRef);
externalSessionMap.remove(sessionMd.sessionRef.getSessionId()); externalSessionMap.remove(sessionMd.sessionRef.getSessionId());
processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed()); processInWebSocketService(sessionMd.sessionRef, SessionEvent.onClosed());
log.info("[{}][{}][{}] Session is closed", sessionMd.sessionRef.getSecurityCtx().getTenantId(), sessionMd.sessionRef.getSessionId(), session.getId());
} else {
log.info("[{}] Session is closed", session.getId());
} }
log.info("[{}] Session is closed", session.getId());
} }
private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) { private void processInWebSocketService(TelemetryWebSocketSessionRef sessionRef, SessionEvent event) {

2
application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java

@ -34,7 +34,7 @@ public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorRespo
return new ThingsboardCredentialsExpiredResponse(message, resetToken); return new ThingsboardCredentialsExpiredResponse(message, resetToken);
} }
@ApiModelProperty(position = 5, value = "Password reset token", readOnly = true) @ApiModelProperty(position = 5, value = "Password reset token", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public String getResetToken() { public String getResetToken() {
return resetToken; return resetToken;
} }

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

@ -46,12 +46,12 @@ public class ThingsboardErrorResponse {
return new ThingsboardErrorResponse(message, errorCode, status); return new ThingsboardErrorResponse(message, errorCode, status);
} }
@ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", readOnly = true) @ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Integer getStatus() { public Integer getStatus() {
return status.value(); return status.value();
} }
@ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", readOnly = true) @ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public String getMessage() { public String getMessage() {
return message; return message;
} }
@ -69,12 +69,12 @@ public class ThingsboardErrorResponse {
"\n\n* `34` - Too many updates (Too many updates over Websocket session)" + "\n\n* `34` - Too many updates (Too many updates over Websocket session)" +
"\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)", "\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)",
example = "10", dataType = "integer", example = "10", dataType = "integer",
readOnly = true) accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public ThingsboardErrorCode getErrorCode() { public ThingsboardErrorCode getErrorCode() {
return errorCode; return errorCode;
} }
@ApiModelProperty(position = 4, value = "Timestamp", readOnly = true) @ApiModelProperty(position = 4, value = "Timestamp", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Date getTimestamp() { public Date getTimestamp() {
return timestamp; return timestamp;
} }

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

@ -17,7 +17,6 @@ package org.thingsboard.server.exception;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -139,12 +138,11 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
} }
} }
@NotNull
@Override @Override
protected ResponseEntity<Object> handleExceptionInternal( protected ResponseEntity<Object> handleExceptionInternal(
@NotNull Exception ex, @Nullable Object body, Exception ex, @Nullable Object body,
@NotNull HttpHeaders headers, @NotNull HttpStatus status, HttpHeaders headers, HttpStatus status,
@NotNull WebRequest request) { WebRequest request) {
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
} }

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

@ -221,6 +221,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.3.4 to 3.4.0 ..."); log.info("Upgrading ThingsBoard from version 3.3.4 to 3.4.0 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.4"); databaseEntitiesUpgradeService.upgradeDatabase("3.3.4");
dataUpdateService.updateData("3.3.4"); dataUpdateService.updateData("3.3.4");
case "3.4.0":
log.info("Upgrading ThingsBoard from version 3.4.0 to 3.4.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.4.0");
dataUpdateService.updateData("3.4.0");
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;

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

@ -70,6 +70,7 @@ 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.Collections;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -79,6 +80,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -500,19 +502,19 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
} }
@Override @Override
protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) { protected Map<TopicPartitionInfo, List<ListenableFuture<?>>> onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
var result = new HashMap<TopicPartitionInfo, List<ListenableFuture<?>>>();
try { try {
log.info("Initializing tenant states."); log.info("Initializing tenant states.");
updateLock.lock(); updateLock.lock();
try { try {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024);
List<ListenableFuture<?>> futures = new ArrayList<>();
for (Tenant tenant : tenantIterator) { for (Tenant tenant : tenantIterator) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId());
if (addedPartitions.contains(tpi)) { if (addedPartitions.contains(tpi)) {
if (!myUsageStates.containsKey(tenant.getId()) && tpi.isMyPartition()) { if (!myUsageStates.containsKey(tenant.getId()) && tpi.isMyPartition()) {
log.debug("[{}] Initializing tenant state.", tenant.getId()); log.debug("[{}] Initializing tenant state.", tenant.getId());
futures.add(dbExecutor.submit(() -> { result.computeIfAbsent(tpi, tmp -> new ArrayList<>()).add(dbExecutor.submit(() -> {
try { try {
updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId()));
log.debug("[{}] Initialized tenant state.", tenant.getId()); log.debug("[{}] Initialized tenant state.", tenant.getId());
@ -526,14 +528,13 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
log.debug("[{}][{}] Tenant doesn't belong to current partition. tpi [{}]", tenant.getName(), tenant.getId(), tpi); log.debug("[{}][{}] Tenant doesn't belong to current partition. tpi [{}]", tenant.getName(), tenant.getId(), tpi);
} }
} }
Futures.whenAllComplete(futures);
} finally { } finally {
updateLock.unlock(); updateLock.unlock();
} }
log.info("Initialized {} tenant states.", myUsageStates.size());
} catch (Exception e) { } catch (Exception e) {
log.warn("Unknown failure", e); log.warn("Unknown failure", e);
} }
return result;
} }
@PreDestroy @PreDestroy

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java

@ -59,6 +59,10 @@ public class DeviceMsgConstructor {
if (device.getAdditionalInfo() != null) { if (device.getAdditionalInfo() != null) {
builder.setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); builder.setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo()));
} }
if (device.getFirmwareId() != null) {
builder.setFirmwareIdMSB(device.getFirmwareId().getId().getMostSignificantBits())
.setFirmwareIdLSB(device.getFirmwareId().getId().getLeastSignificantBits());
}
if (conflictName != null) { if (conflictName != null) {
builder.setConflictName(conflictName); builder.setConflictName(conflictName);
} }

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java

@ -20,9 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -66,6 +66,10 @@ public class DeviceProfileMsgConstructor {
if (deviceProfile.getImage() != null) { if (deviceProfile.getImage() != null) {
builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8))); builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8)));
} }
if (deviceProfile.getFirmwareId() != null) {
builder.setFirmwareIdMSB(deviceProfile.getFirmwareId().getId().getMostSignificantBits())
.setFirmwareIdLSB(deviceProfile.getFirmwareId().getId().getLeastSignificantBits());
}
return builder.build(); return builder.build();
} }

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

@ -33,13 +33,16 @@ public class OtaPackageMsgConstructor {
.setMsgType(msgType) .setMsgType(msgType)
.setIdMSB(otaPackage.getId().getId().getMostSignificantBits()) .setIdMSB(otaPackage.getId().getId().getMostSignificantBits())
.setIdLSB(otaPackage.getId().getId().getLeastSignificantBits()) .setIdLSB(otaPackage.getId().getId().getLeastSignificantBits())
.setDeviceProfileIdMSB(otaPackage.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(otaPackage.getDeviceProfileId().getId().getLeastSignificantBits())
.setType(otaPackage.getType().name()) .setType(otaPackage.getType().name())
.setTitle(otaPackage.getTitle()) .setTitle(otaPackage.getTitle())
.setVersion(otaPackage.getVersion()) .setVersion(otaPackage.getVersion())
.setTag(otaPackage.getTag()); .setTag(otaPackage.getTag());
if (otaPackage.getDeviceProfileId() != null) {
builder.setDeviceProfileIdMSB(otaPackage.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(otaPackage.getDeviceProfileId().getId().getLeastSignificantBits());
}
if (otaPackage.getUrl() != null) { if (otaPackage.getUrl() != null) {
builder.setUrl(otaPackage.getUrl()); builder.setUrl(otaPackage.getUrl());
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -72,6 +72,7 @@ import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
@ -83,6 +84,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Service @Service
@TbCoreComponent
@Slf4j @Slf4j
public class DefaultEdgeRequestsService implements EdgeRequestsService { public class DefaultEdgeRequestsService implements EdgeRequestsService {

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

@ -25,6 +25,7 @@ import org.springframework.context.annotation.Lazy;
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.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.QueueId;
@ -596,6 +597,18 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e); log.error("Failed updating schema!!!", e);
} }
break; break;
case "3.4.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004001;");
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);
} }
@ -666,5 +679,4 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
} }
} }

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

@ -58,6 +58,7 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
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.event.EventService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
@ -128,6 +129,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private SystemDataLoaderService systemDataLoaderService; private SystemDataLoaderService systemDataLoaderService;
@Autowired
private EventService eventService;
@Override @Override
public void updateData(String fromVersion) throws Exception { public void updateData(String fromVersion) throws Exception {
switch (fromVersion) { switch (fromVersion) {
@ -156,8 +160,15 @@ public class DefaultDataUpdateService implements DataUpdateService {
break; break;
case "3.3.4": case "3.3.4":
log.info("Updating data from version 3.3.4 to 3.4.0 ..."); log.info("Updating data from version 3.3.4 to 3.4.0 ...");
rateLimitsUpdater.updateEntities();
tenantsProfileQueueConfigurationUpdater.updateEntities(); tenantsProfileQueueConfigurationUpdater.updateEntities();
rateLimitsUpdater.updateEntities();
break;
case "3.4.0":
String skipEventsMigration = System.getenv("TB_SKIP_EVENTS_MIGRATION");
if (skipEventsMigration == null || skipEventsMigration.equalsIgnoreCase("false")) {
log.info("Updating data from version 3.4.0 to 3.4.1 ...");
eventService.migrateEvents();
}
break; break;
default: default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
@ -602,7 +613,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
}); });
} }
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to update tenant profile queue configuration name=["+profile.getName()+"], id=["+ profile.getId().getId() +"]", e); log.error("Failed to update tenant profile queue configuration name=[" + profile.getName() + "], id=[" + profile.getId().getId() + "]", e);
} }
} }

57
application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java

@ -15,9 +15,12 @@
*/ */
package org.thingsboard.server.service.partition; package org.thingsboard.server.service.partition;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.ListeningScheduledExecutorService;
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.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
@ -25,9 +28,14 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -37,6 +45,7 @@ import java.util.concurrent.Executors;
public abstract class AbstractPartitionBasedService<T extends EntityId> extends TbApplicationEventListener<PartitionChangeEvent> { public abstract class AbstractPartitionBasedService<T extends EntityId> extends TbApplicationEventListener<PartitionChangeEvent> {
protected final ConcurrentMap<TopicPartitionInfo, Set<T>> partitionedEntities = new ConcurrentHashMap<>(); protected final ConcurrentMap<TopicPartitionInfo, Set<T>> partitionedEntities = new ConcurrentHashMap<>();
protected final ConcurrentMap<TopicPartitionInfo, List<ListenableFuture<?>>> partitionedFetchTasks = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>(); final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
protected ListeningScheduledExecutorService scheduledExecutor; protected ListeningScheduledExecutorService scheduledExecutor;
@ -45,7 +54,7 @@ public abstract class AbstractPartitionBasedService<T extends EntityId> extends
abstract protected String getSchedulerExecutorName(); abstract protected String getSchedulerExecutorName();
abstract protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions); abstract protected Map<TopicPartitionInfo, List<ListenableFuture<?>>> onAddedPartitions(Set<TopicPartitionInfo> addedPartitions);
abstract protected void cleanupEntityOnPartitionRemoval(T entityId); abstract protected void cleanupEntityOnPartitionRemoval(T entityId);
@ -108,29 +117,59 @@ public abstract class AbstractPartitionBasedService<T extends EntityId> extends
log.info("[{}] REMOVED PARTITIONS: {}", getServiceName(), removedPartitions); log.info("[{}] REMOVED PARTITIONS: {}", getServiceName(), removedPartitions);
boolean partitionListChanged = false;
// We no longer manage current partition of entities; // We no longer manage current partition of entities;
removedPartitions.forEach(partition -> { for (var partition : removedPartitions) {
Set<T> entities = partitionedEntities.remove(partition); Set<T> entities = partitionedEntities.remove(partition);
entities.forEach(this::cleanupEntityOnPartitionRemoval); if (entities != null) {
}); entities.forEach(this::cleanupEntityOnPartitionRemoval);
}
List<ListenableFuture<?>> fetchTasks = partitionedFetchTasks.remove(partition);
if (fetchTasks != null) {
fetchTasks.forEach(f -> f.cancel(false));
}
partitionListChanged = true;
}
onRepartitionEvent(); onRepartitionEvent();
addedPartitions.forEach(tpi -> partitionedEntities.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); addedPartitions.forEach(tpi -> partitionedEntities.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
if (!addedPartitions.isEmpty()) { if (!addedPartitions.isEmpty()) {
onAddedPartitions(addedPartitions); var fetchTasks = onAddedPartitions(addedPartitions);
if (fetchTasks != null && !fetchTasks.isEmpty()) {
partitionedFetchTasks.putAll(fetchTasks);
}
partitionListChanged = true;
} }
log.info("[{}] Managing following partitions:", getServiceName()); if (partitionListChanged) {
partitionedEntities.forEach((tpi, entities) -> { List<ListenableFuture<?>> partitionFetchFutures = new ArrayList<>();
log.info("[{}][{}]: {} entities", getServiceName(), tpi.getFullTopicName(), entities.size()); partitionedFetchTasks.values().forEach(partitionFetchFutures::addAll);
}); DonAsynchron.withCallback(Futures.allAsList(partitionFetchFutures), t -> logPartitions(), this::logFailure);
}
} catch (Throwable t) { } catch (Throwable t) {
log.warn("[{}] Failed to init entities state from DB", getServiceName(), t); log.warn("[{}] Failed to init entities state from DB", getServiceName(), t);
} }
} }
private void logFailure(Throwable e) {
if (e instanceof CancellationException) {
//Probably this is fine and happens due to re-balancing.
log.trace("Partition fetch task error", e);
} else {
log.error("Partition fetch task error", e);
}
}
private void logPartitions() {
log.info("[{}] Managing following partitions:", getServiceName());
partitionedEntities.forEach((tpi, entities) -> {
log.info("[{}][{}]: {} entities", getServiceName(), tpi.getFullTopicName(), entities.size());
});
}
protected void onRepartitionEvent() { protected void onRepartitionEvent() {
} }

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

@ -42,9 +42,8 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
@Override @Override
public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
Tenant tenant = tenantService.findTenantById(tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
if (tenant != null) { if (tenantProfile != null) {
TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId());
return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine()); return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine());
} else { } else {
throw new RuntimeException("Tenant not found!"); throw new RuntimeException("Tenant not found!");

3
application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/OtpBasedTwoFaProvider.java

@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.security.model.mfa.provider.OtpBasedTw
import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider; import org.thingsboard.server.service.security.auth.mfa.provider.TwoFaProvider;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import java.io.Serializable;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public abstract class OtpBasedTwoFaProvider<C extends OtpBasedTwoFaProviderConfig, A extends OtpBasedTwoFaAccountConfig> implements TwoFaProvider<C, A> { public abstract class OtpBasedTwoFaProvider<C extends OtpBasedTwoFaProviderConfig, A extends OtpBasedTwoFaAccountConfig> implements TwoFaProvider<C, A> {
@ -67,7 +68,7 @@ public abstract class OtpBasedTwoFaProvider<C extends OtpBasedTwoFaProviderConfi
@Data @Data
public static class Otp { public static class Otp implements Serializable {
private final long timestamp; private final long timestamp;
private final String value; private final String value;
private final OtpBasedTwoFaAccountConfig accountConfig; private final OtpBasedTwoFaAccountConfig accountConfig;

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

@ -17,9 +17,12 @@ package org.thingsboard.server.service.state;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Function; import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -27,12 +30,15 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService; 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.Tenant; import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; 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.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -42,7 +48,12 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; 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.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink; 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.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.EntityListFilter;
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;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
@ -51,10 +62,13 @@ 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.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.sql.query.EntityQueryRepository;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.util.DbTypeInfoComponent;
import org.thingsboard.server.gen.transport.TransportProtos; 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.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
@ -66,16 +80,21 @@ 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.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT; import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT;
import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT;
@ -98,8 +117,28 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime"; public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime";
public static final String INACTIVITY_TIMEOUT = "inactivityTimeout"; public static final String INACTIVITY_TIMEOUT = "inactivityTimeout";
private static final List<EntityKey> PERSISTENT_TELEMETRY_KEYS = Arrays.asList(
new EntityKey(EntityKeyType.TIME_SERIES, LAST_ACTIVITY_TIME),
new EntityKey(EntityKeyType.TIME_SERIES, INACTIVITY_ALARM_TIME),
new EntityKey(EntityKeyType.TIME_SERIES, INACTIVITY_TIMEOUT),
new EntityKey(EntityKeyType.TIME_SERIES, ACTIVITY_STATE),
new EntityKey(EntityKeyType.TIME_SERIES, LAST_CONNECT_TIME),
new EntityKey(EntityKeyType.TIME_SERIES, LAST_DISCONNECT_TIME));
private static final List<EntityKey> PERSISTENT_ATTRIBUTE_KEYS = Arrays.asList(
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, LAST_ACTIVITY_TIME),
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, INACTIVITY_ALARM_TIME),
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, INACTIVITY_TIMEOUT),
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, ACTIVITY_STATE),
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, LAST_CONNECT_TIME),
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, LAST_DISCONNECT_TIME));
public static final List<String> PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, public static final List<String> PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME,
LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT); LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT);
private static final List<EntityKey> PERSISTENT_ENTITY_FIELDS = Arrays.asList(
new EntityKey(EntityKeyType.ENTITY_FIELD, "name"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "type"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"));
private final TenantService tenantService; private final TenantService tenantService;
private final DeviceService deviceService; private final DeviceService deviceService;
@ -107,6 +146,9 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private final TimeseriesService tsService; private final TimeseriesService tsService;
private final TbClusterService clusterService; private final TbClusterService clusterService;
private final PartitionService partitionService; private final PartitionService partitionService;
private final TbServiceInfoProvider serviceInfoProvider;
private final EntityQueryRepository entityQueryRepository;
private final DbTypeInfoComponent dbTypeInfoComponent;
private TelemetrySubscriptionService tsSubService; private TelemetrySubscriptionService tsSubService;
@ -122,23 +164,29 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
@Getter @Getter
private boolean persistToTelemetry; private boolean persistToTelemetry;
@Value("${state.initFetchPackSize:1000}") @Value("${state.initFetchPackSize:50000}")
@Getter @Getter
private int initFetchPackSize; private int initFetchPackSize;
private ExecutorService deviceStateExecutor; private ListeningExecutorService deviceStateExecutor;
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>(); final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService, public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService,
AttributesService attributesService, TimeseriesService tsService, AttributesService attributesService, TimeseriesService tsService,
TbClusterService clusterService, PartitionService partitionService) { TbClusterService clusterService, PartitionService partitionService,
TbServiceInfoProvider serviceInfoProvider,
EntityQueryRepository entityQueryRepository,
DbTypeInfoComponent dbTypeInfoComponent) {
this.tenantService = tenantService; this.tenantService = tenantService;
this.deviceService = deviceService; this.deviceService = deviceService;
this.attributesService = attributesService; this.attributesService = attributesService;
this.tsService = tsService; this.tsService = tsService;
this.clusterService = clusterService; this.clusterService = clusterService;
this.partitionService = partitionService; this.partitionService = partitionService;
this.serviceInfoProvider = serviceInfoProvider;
this.entityQueryRepository = entityQueryRepository;
this.dbTypeInfoComponent = dbTypeInfoComponent;
} }
@Autowired @Autowired
@ -149,8 +197,8 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
@PostConstruct @PostConstruct
public void init() { public void init() {
super.init(); super.init();
deviceStateExecutor = Executors.newFixedThreadPool( deviceStateExecutor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(
Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("device-state")); Math.max(4, Runtime.getRuntime().availableProcessors()), "device-state"));
scheduledExecutor.scheduleAtFixedRate(this::updateInactivityStateIfExpired, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); scheduledExecutor.scheduleAtFixedRate(this::updateInactivityStateIfExpired, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
} }
@ -167,7 +215,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
return "Device State"; return "Device State";
} }
@Override @Override
protected String getSchedulerExecutorName() { protected String getSchedulerExecutorName() {
return "device-state-scheduled"; return "device-state-scheduled";
@ -231,17 +278,16 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
@Override @Override
public void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout) { public void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout) {
if (inactivityTimeout <= 0L) {
return;
}
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) { if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return; return;
} }
if (inactivityTimeout <= 0L) {
inactivityTimeout = defaultInactivityTimeoutInSec;
}
log.trace("on Device Activity Timeout Update device id {} inactivityTimeout {}", deviceId, inactivityTimeout); log.trace("on Device Activity Timeout Update device id {} inactivityTimeout {}", deviceId, inactivityTimeout);
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
stateData.getState().setInactivityTimeout(inactivityTimeout); stateData.getState().setInactivityTimeout(inactivityTimeout);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
} }
@Override @Override
@ -260,8 +306,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
@Override @Override
public void onSuccess(@Nullable DeviceStateData state) { public void onSuccess(@Nullable DeviceStateData state) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId()); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId());
if (partitionedEntities.containsKey(tpi)) { if (addDeviceUsingState(tpi, state)) {
addDeviceUsingState(tpi, state);
save(deviceId, ACTIVITY_STATE, false); save(deviceId, ACTIVITY_STATE, false);
callback.onSuccess(); callback.onSuccess();
} else { } else {
@ -297,60 +342,62 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
} }
@Override @Override
protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) { protected Map<TopicPartitionInfo, List<ListenableFuture<?>>> onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); var result = new HashMap<TopicPartitionInfo, List<ListenableFuture<?>>>();
for (Tenant tenant : tenantIterator) { PageDataIterable<DeviceIdInfo> deviceIdInfos = new PageDataIterable<>(deviceService::findDeviceIdInfos, initFetchPackSize);
log.debug("Finding devices for tenant [{}]", tenant.getName()); Map<TopicPartitionInfo, List<DeviceIdInfo>> tpiDeviceMap = new HashMap<>();
final PageLink pageLink = new PageLink(initFetchPackSize);
processPageAndSubmitNextPage(addedPartitions, tenant, pageLink); for (DeviceIdInfo idInfo : deviceIdInfos) {
} TopicPartitionInfo tpi;
} try {
tpi = partitionService.resolve(ServiceType.TB_CORE, idInfo.getTenantId(), idInfo.getDeviceId());
private void processPageAndSubmitNextPage(final Set<TopicPartitionInfo> addedPartitions, final Tenant tenant, final PageLink pageLink) { } catch (Exception e) {
log.trace("[{}] Process page {} from {}", tenant, pageLink.getPage(), pageLink.getPageSize()); log.warn("Failed to resolve partition for device with id [{}], tenant id [{}], customer id [{}]. Reason: {}",
List<ListenableFuture<Void>> fetchFutures = new ArrayList<>(); idInfo.getDeviceId(), idInfo.getTenantId(), idInfo.getCustomerId(), e.getMessage());
PageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); continue;
for (Device device : page.getData()) { }
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), device.getId()); if (addedPartitions.contains(tpi) && !deviceStates.containsKey(idInfo.getDeviceId())) {
if (addedPartitions.contains(tpi) && !deviceStates.containsKey(device.getId())) { tpiDeviceMap.computeIfAbsent(tpi, tmp -> new ArrayList<>()).add(idInfo);
log.debug("[{}][{}] Device belong to current partition. tpi [{}]. Fetching state from DB", device.getName(), device.getId(), tpi); }
ListenableFuture<Void> future = Futures.transform(fetchDeviceState(device), new Function<>() { }
@Nullable
@Override for (var entry : tpiDeviceMap.entrySet()) {
public Void apply(@Nullable DeviceStateData state) { AtomicInteger counter = new AtomicInteger(0);
if (state != null) { // hard-coded limit of 1000 is due to the Entity Data Query limitations and should not be changed.
addDeviceUsingState(tpi, state); for (List<DeviceIdInfo> partition : Lists.partition(entry.getValue(), 1000)) {
checkAndUpdateState(device.getId(), state); log.info("[{}] Submit task for device states: {}", entry.getKey(), partition.size());
DevicePackFutureHolder devicePackFutureHolder = new DevicePackFutureHolder();
var devicePackFuture = deviceStateExecutor.submit(() -> {
try {
List<DeviceStateData> states;
if (persistToTelemetry && !dbTypeInfoComponent.isLatestTsDaoStoredToSql()) {
states = fetchDeviceStateDataUsingSeparateRequests(partition);
} else { } else {
log.warn("{}][{}] Fetched null state from DB", device.getName(), device.getId()); states = fetchDeviceStateDataUsingEntityDataQuery(partition);
} }
return null; if (devicePackFutureHolder.future == null || !devicePackFutureHolder.future.isCancelled()) {
for (var state : states) {
if (!addDeviceUsingState(entry.getKey(), state)) {
return;
}
checkAndUpdateState(state.getDeviceId(), state);
}
log.info("[{}] Initialized {} out of {} device states", entry.getKey().getPartition().orElse(0), counter.addAndGet(states.size()), entry.getValue().size());
}
} catch (Throwable t) {
log.error("Unexpected exception while device pack fetching", t);
throw t;
} }
}, deviceStateExecutor); });
fetchFutures.add(future); devicePackFutureHolder.future = devicePackFuture;
} else { result.computeIfAbsent(entry.getKey(), tmp -> new ArrayList<>()).add(devicePackFuture);
log.debug("[{}][{}] Device doesn't belong to current partition. tpi [{}]", device.getName(), device.getId(), tpi);
} }
} }
return result;
}
Futures.addCallback(Futures.successfulAsList(fetchFutures), new FutureCallback<>() { private static class DevicePackFutureHolder {
@Override private volatile ListenableFuture<?> future;
public void onSuccess(List<Void> result) {
log.trace("[{}] Success init device state from DB for batch size {}", tenant.getId(), result.size());
}
@Override
public void onFailure(Throwable t) {
log.warn("[" + tenant.getId() + "] Failed to init device state service from DB", t);
log.warn("[{}] Failed to init device state service from DB", tenant.getId(), t);
}
}, deviceStateExecutor);
final PageLink nextPageLink = page.hasNext() ? pageLink.nextPageLink() : null;
if (nextPageLink != null) {
log.trace("[{}] Submit next page {} from {}", tenant, nextPageLink.getPage(), nextPageLink.getPageSize());
processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink);
}
} }
void checkAndUpdateState(@Nonnull DeviceId deviceId, @Nonnull DeviceStateData state) { void checkAndUpdateState(@Nonnull DeviceId deviceId, @Nonnull DeviceStateData state) {
@ -364,25 +411,34 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
} }
} }
private void addDeviceUsingState(TopicPartitionInfo tpi, DeviceStateData state) { private boolean addDeviceUsingState(TopicPartitionInfo tpi, DeviceStateData state) {
Set<DeviceId> deviceIds = partitionedEntities.get(tpi); Set<DeviceId> deviceIds = partitionedEntities.get(tpi);
if (deviceIds != null) { if (deviceIds != null) {
deviceIds.add(state.getDeviceId()); deviceIds.add(state.getDeviceId());
deviceStates.putIfAbsent(state.getDeviceId(), state); deviceStates.putIfAbsent(state.getDeviceId(), state);
return true;
} else { } else {
log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName()); log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName());
throw new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!"); return false;
} }
} }
void updateInactivityStateIfExpired() { void updateInactivityStateIfExpired() {
final long ts = System.currentTimeMillis(); try {
partitionedEntities.forEach((tpi, deviceIds) -> { final long ts = System.currentTimeMillis();
log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size()); partitionedEntities.forEach((tpi, deviceIds) -> {
for (DeviceId deviceId : deviceIds) { log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size());
updateInactivityStateIfExpired(ts, deviceId); for (DeviceId deviceId : deviceIds) {
} try {
}); updateInactivityStateIfExpired(ts, deviceId);
} catch (Exception e) {
log.warn("[{}] Failed to update inactivity state", deviceId, e);
}
}
});
} catch (Throwable t) {
log.warn("Failed to update inactivity states", t);
}
} }
void updateInactivityStateIfExpired(long ts, DeviceId deviceId) { void updateInactivityStateIfExpired(long ts, DeviceId deviceId) {
@ -423,10 +479,10 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
if (deviceStateData != null) { if (deviceStateData != null) {
return deviceStateData; return deviceStateData;
} }
return fetchDeviceStateData(deviceId); return fetchDeviceStateDataUsingEntityDataQuery(deviceId);
} }
DeviceStateData fetchDeviceStateData(final DeviceId deviceId) { DeviceStateData fetchDeviceStateDataUsingEntityDataQuery(final DeviceId deviceId) {
final Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId); final Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId);
if (device == null) { if (device == null) {
log.warn("[{}] Failed to fetch device by Id!", deviceId); log.warn("[{}] Failed to fetch device by Id!", deviceId);
@ -540,6 +596,106 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
}; };
} }
private List<DeviceStateData> fetchDeviceStateDataUsingSeparateRequests(List<DeviceIdInfo> deviceIds) {
List<Device> devices = deviceService.findDevicesByIds(deviceIds.stream().map(DeviceIdInfo::getDeviceId).collect(Collectors.toList()));
List<ListenableFuture<DeviceStateData>> deviceStateFutures = new ArrayList<>();
for (Device device : devices) {
deviceStateFutures.add(fetchDeviceState(device));
}
try {
List<DeviceStateData> result = Futures.successfulAsList(deviceStateFutures).get(5, TimeUnit.MINUTES);
boolean success = true;
for (int i = 0; i < result.size(); i++) {
success = false;
if (result.get(i) == null) {
DeviceIdInfo deviceIdInfo = deviceIds.get(i);
log.warn("[{}][{}] Failed to initialized device state due to:", deviceIdInfo.getTenantId(), deviceIdInfo.getDeviceId());
}
}
return success ? result : result.stream().filter(Objects::nonNull).collect(Collectors.toList());
} catch (InterruptedException | ExecutionException | TimeoutException e) {
log.warn("Failed to initialized device state futures for ids: {} due to:", deviceIds, e);
throw new RuntimeException(e);
}
}
private List<DeviceStateData> fetchDeviceStateDataUsingEntityDataQuery(List<DeviceIdInfo> deviceIds) {
EntityListFilter ef = new EntityListFilter();
ef.setEntityType(EntityType.DEVICE);
ef.setEntityList(deviceIds.stream().map(DeviceIdInfo::getDeviceId).map(DeviceId::getId).map(UUID::toString).collect(Collectors.toList()));
EntityDataQuery query = new EntityDataQuery(ef,
new EntityDataPageLink(deviceIds.size(), 0, null, null),
PERSISTENT_ENTITY_FIELDS,
persistToTelemetry ? PERSISTENT_TELEMETRY_KEYS : PERSISTENT_ATTRIBUTE_KEYS, Collections.emptyList());
PageData<EntityData> queryResult = entityQueryRepository.findEntityDataByQueryInternal(query);
Map<EntityId, DeviceIdInfo> deviceIdInfos = deviceIds.stream().collect(Collectors.toMap(DeviceIdInfo::getDeviceId, java.util.function.Function.identity()));
return queryResult.getData().stream().map(ed -> toDeviceStateData(ed, deviceIdInfos.get(ed.getEntityId()))).collect(Collectors.toList());
}
private DeviceStateData toDeviceStateData(EntityData ed, DeviceIdInfo deviceIdInfo) {
long lastActivityTime = getEntryValue(ed, getKeyType(), LAST_ACTIVITY_TIME, 0L);
long inactivityAlarmTime = getEntryValue(ed, getKeyType(), INACTIVITY_ALARM_TIME, 0L);
long inactivityTimeout = getEntryValue(ed, getKeyType(), INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec));
//Actual active state by wall-clock will updated outside this method. This method is only for fetch persistent state
final boolean active = getEntryValue(ed, getKeyType(), ACTIVITY_STATE, false);
DeviceState deviceState = DeviceState.builder()
.active(active)
.lastConnectTime(getEntryValue(ed, getKeyType(), LAST_CONNECT_TIME, 0L))
.lastDisconnectTime(getEntryValue(ed, getKeyType(), LAST_DISCONNECT_TIME, 0L))
.lastActivityTime(lastActivityTime)
.lastInactivityAlarmTime(inactivityAlarmTime)
.inactivityTimeout(inactivityTimeout)
.build();
TbMsgMetaData md = new TbMsgMetaData();
md.putValue("deviceName", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "name", ""));
md.putValue("deviceType", getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "type", ""));
return DeviceStateData.builder()
.customerId(deviceIdInfo.getCustomerId())
.tenantId(deviceIdInfo.getTenantId())
.deviceId(deviceIdInfo.getDeviceId())
.deviceCreationTime(getEntryValue(ed, EntityKeyType.ENTITY_FIELD, "createdTime", 0L))
.metaData(md)
.state(deviceState).build();
}
private EntityKeyType getKeyType() {
return persistToTelemetry ? EntityKeyType.TIME_SERIES : EntityKeyType.SERVER_ATTRIBUTE;
}
private String getEntryValue(EntityData ed, EntityKeyType keyType, String keyName, String defaultValue) {
return getEntryValue(ed, keyType, keyName, s -> s, defaultValue);
}
private long getEntryValue(EntityData ed, EntityKeyType keyType, String keyName, long defaultValue) {
return getEntryValue(ed, keyType, keyName, Long::parseLong, defaultValue);
}
private boolean getEntryValue(EntityData ed, EntityKeyType keyType, String keyName, boolean defaultValue) {
return getEntryValue(ed, keyType, keyName, Boolean::parseBoolean, defaultValue);
}
private <T> T getEntryValue(EntityData ed, EntityKeyType entityKeyType, String attributeName, Function<String, T> converter, T defaultValue) {
if (ed != null && ed.getLatest() != null) {
var map = ed.getLatest().get(entityKeyType);
if (map != null) {
var value = map.get(attributeName);
if (value != null && !StringUtils.isEmpty(value.getValue())) {
try {
return converter.apply(value.getValue());
} catch (Exception e) {
return defaultValue;
}
}
}
}
return defaultValue;
}
private long getEntryValue(List<? extends KvEntry> kvEntries, String attributeName, long defaultValue) { private long getEntryValue(List<? extends KvEntry> kvEntries, String attributeName, long defaultValue) {
if (kvEntries != null) { if (kvEntries != null) {
for (KvEntry entry : kvEntries) { for (KvEntry entry : kvEntries) {

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

@ -278,7 +278,15 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
private void updateDeviceInactivityTimeout(TenantId tenantId, EntityId entityId, List<? extends KvEntry> kvEntries) { private void updateDeviceInactivityTimeout(TenantId tenantId, EntityId entityId, List<? extends KvEntry> kvEntries) {
for (KvEntry kvEntry : kvEntries) { for (KvEntry kvEntry : kvEntries) {
if (kvEntry.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) { if (kvEntry.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
deviceStateService.onDeviceInactivityTimeoutUpdate(tenantId, new DeviceId(entityId.getId()), kvEntry.getLongValue().orElse(0L)); deviceStateService.onDeviceInactivityTimeoutUpdate(tenantId, new DeviceId(entityId.getId()), getLongValue(kvEntry));
}
}
}
private void deleteDeviceInactivityTimeout(TenantId tenantId, EntityId entityId, List<String> keys) {
for (String key : keys) {
if (key.equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) {
deviceStateService.onDeviceInactivityTimeoutUpdate(tenantId, new DeviceId(entityId.getId()), 0);
} }
} }
} }
@ -340,6 +348,9 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
return subscriptionUpdate; return subscriptionUpdate;
}, false); }, false);
if (entityId.getEntityType() == EntityType.DEVICE) {
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
}
callback.onSuccess(); callback.onSuccess();
} }
@ -364,6 +375,9 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
} }
return subscriptionUpdate; return subscriptionUpdate;
}, false); }, false);
if (entityId.getEntityType() == EntityType.DEVICE) {
deleteDeviceInactivityTimeout(tenantId, entityId, keys);
}
callback.onSuccess(); callback.onSuccess();
} }
@ -557,4 +571,27 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg); return new TbProtoQueueMsg<>(subscription.getEntityId().getId(), toCoreMsg);
} }
private static long getLongValue(KvEntry kve) {
switch (kve.getDataType()) {
case LONG:
return kve.getLongValue().orElse(0L);
case DOUBLE:
return kve.getDoubleValue().orElse(0.0).longValue();
case STRING:
try {
return Long.parseLong(kve.getStrValue().orElse("0"));
} catch (NumberFormatException e) {
return 0L;
}
case JSON:
try {
return Long.parseLong(kve.getJsonValue().orElse("0"));
} catch (NumberFormatException e) {
return 0L;
}
default:
return 0L;
}
}
} }

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

@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -294,25 +295,53 @@ 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::checkAndResetInvocationCounter, dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS); () -> refreshAlarmQuery(finalCtx), dynamicPageLinkRefreshInterval, dynamicPageLinkRefreshInterval, TimeUnit.SECONDS);
finalCtx.setRefreshTask(task); finalCtx.setRefreshTask(task);
} }
} }
} }
private void refreshDynamicQuery(TbAbstractSubCtx finalCtx) { private boolean validate(TbAbstractSubCtx<?> finalCtx) {
if (finalCtx.isStopped()) {
log.warn("[{}][{}][{}] Received validation task for already stopped context.", finalCtx.getTenantId(), finalCtx.getSessionId(), finalCtx.getCmdId());
return false;
}
var cmdMap = subscriptionsBySessionId.get(finalCtx.getSessionId());
if (cmdMap == null) {
log.warn("[{}][{}][{}] Received validation task for already removed session.", finalCtx.getTenantId(), finalCtx.getSessionId(), finalCtx.getCmdId());
return false;
} else if (!cmdMap.containsKey(finalCtx.getCmdId())) {
log.warn("[{}][{}][{}] Received validation task for unregistered cmdId.", finalCtx.getTenantId(), finalCtx.getSessionId(), finalCtx.getCmdId());
return false;
}
return true;
}
private void refreshDynamicQuery(TbAbstractSubCtx<?> finalCtx) {
try { try {
long start = System.currentTimeMillis(); if (validate(finalCtx)) {
finalCtx.update(); long start = System.currentTimeMillis();
long end = System.currentTimeMillis(); finalCtx.update();
log.trace("[{}][{}] Executing query: {}", finalCtx.getSessionId(), finalCtx.getCmdId(), finalCtx.getQuery()); long end = System.currentTimeMillis();
stats.getDynamicQueryInvocationCnt().incrementAndGet(); log.trace("[{}][{}] Executing query: {}", finalCtx.getSessionId(), finalCtx.getCmdId(), finalCtx.getQuery());
stats.getDynamicQueryTimeSpent().addAndGet(end - start); stats.getDynamicQueryInvocationCnt().incrementAndGet();
stats.getDynamicQueryTimeSpent().addAndGet(end - start);
} else {
finalCtx.stop();
}
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}][{}] Failed to refresh query", finalCtx.getSessionId(), finalCtx.getCmdId(), e); log.warn("[{}][{}] Failed to refresh query", finalCtx.getSessionId(), finalCtx.getCmdId(), e);
} }
} }
private void refreshAlarmQuery(TbAlarmDataSubCtx finalCtx) {
if (validate(finalCtx)) {
finalCtx.checkAndResetInvocationCounter();
} else {
finalCtx.stop();
}
}
@Scheduled(fixedDelayString = "${server.ws.dynamic_page_link.stats:10000}") @Scheduled(fixedDelayString = "${server.ws.dynamic_page_link.stats:10000}")
public void printStats() { public void printStats() {
int alarmQueryInvocationCntValue = stats.getAlarmQueryInvocationCnt().getAndSet(0); int alarmQueryInvocationCntValue = stats.getAlarmQueryInvocationCnt().getAndSet(0);
@ -526,8 +555,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private void cleanupAndCancel(TbAbstractSubCtx ctx) { private void cleanupAndCancel(TbAbstractSubCtx ctx) {
if (ctx != null) { if (ctx != null) {
ctx.cancelTasks(); ctx.stop();
ctx.clearSubscriptions();
if (ctx.getSessionId() != null) { if (ctx.getSessionId() != null) {
Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(ctx.getSessionId()); Map<Integer, TbAbstractSubCtx> sessionSubs = subscriptionsBySessionId.get(ctx.getSessionId());
if (sessionSubs != null) { if (sessionSubs != null) {

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

@ -78,6 +78,7 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
protected T query; protected T query;
@Setter @Setter
protected volatile ScheduledFuture<?> refreshTask; protected volatile ScheduledFuture<?> refreshTask;
protected volatile boolean stopped;
public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService, public TbAbstractSubCtx(String serviceId, TelemetryWebSocketService wsService,
EntityService entityService, TbLocalSubscriptionService localSubscriptionService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService,
@ -189,6 +190,12 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
clearDynamicValueSubscriptions(); clearDynamicValueSubscriptions();
} }
public void stop() {
stopped = true;
cancelTasks();
clearSubscriptions();
}
@Data @Data
private static class DynamicValueKeySub { private static class DynamicValueKeySub {
private final DynamicValueKey key; private final DynamicValueKey key;
@ -299,7 +306,11 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
} }
public void setRefreshTask(ScheduledFuture<?> task) { public void setRefreshTask(ScheduledFuture<?> task) {
this.refreshTask = task; if (!stopped) {
this.refreshTask = task;
} else {
task.cancel(true);
}
} }
public void cancelTasks() { public void cancelTasks() {

4
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java

@ -308,6 +308,10 @@ public abstract class BaseEntityImportService<I extends EntityId, E extends Expo
public <ID extends EntityId> ID getInternalId(ID externalId, boolean throwExceptionIfNotFound) { public <ID extends EntityId> ID getInternalId(ID externalId, boolean throwExceptionIfNotFound) {
if (externalId == null || externalId.isNullUid()) return null; if (externalId == null || externalId.isNullUid()) return null;
if (EntityType.TENANT.equals(externalId.getEntityType())) {
return (ID) ctx.getTenantId();
}
EntityId localId = ctx.getInternalId(externalId); EntityId localId = ctx.getInternalId(externalId);
if (localId != null) { if (localId != null) {
return (ID) localId; return (ID) localId;

23
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java

@ -76,11 +76,13 @@ import org.thingsboard.server.service.sync.vc.data.VoidGitRequest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -102,7 +104,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
private final SchedulerComponent scheduler; private final SchedulerComponent scheduler;
private final Map<UUID, PendingGitRequest<?>> pendingRequestMap = new HashMap<>(); private final Map<UUID, PendingGitRequest<?>> pendingRequestMap = new HashMap<>();
private final Map<UUID, LinkedHashMap<String, String[]>> chunkedMsgs = new ConcurrentHashMap<>(); private final Map<UUID, HashMap<Integer, String[]>> chunkedMsgs = new ConcurrentHashMap<>();
@Value("${queue.vc.request-timeout:60000}") @Value("${queue.vc.request-timeout:60000}")
private int requestTimeout; private int requestTimeout;
@ -286,7 +288,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public ListenableFuture<EntityExportData> getEntity(TenantId tenantId, String versionId, EntityId entityId) { public ListenableFuture<EntityExportData> getEntity(TenantId tenantId, String versionId, EntityId entityId) {
EntityContentGitRequest request = new EntityContentGitRequest(tenantId, versionId, entityId); EntityContentGitRequest request = new EntityContentGitRequest(tenantId, versionId, entityId);
chunkedMsgs.put(request.getRequestId(), new LinkedHashMap<>()); chunkedMsgs.put(request.getRequestId(), new HashMap<>());
registerAndSend(request, builder -> builder.setEntityContentRequest(EntityContentRequestMsg.newBuilder() registerAndSend(request, builder -> builder.setEntityContentRequest(EntityContentRequestMsg.newBuilder()
.setVersionId(versionId) .setVersionId(versionId)
.setEntityType(entityId.getEntityType().name()) .setEntityType(entityId.getEntityType().name())
@ -328,7 +330,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public ListenableFuture<List<EntityExportData>> getEntities(TenantId tenantId, String versionId, EntityType entityType, int offset, int limit) { public ListenableFuture<List<EntityExportData>> getEntities(TenantId tenantId, String versionId, EntityType entityType, int offset, int limit) {
EntitiesContentGitRequest request = new EntitiesContentGitRequest(tenantId, versionId, entityType); EntitiesContentGitRequest request = new EntitiesContentGitRequest(tenantId, versionId, entityType);
chunkedMsgs.put(request.getRequestId(), new LinkedHashMap<>()); chunkedMsgs.put(request.getRequestId(), new HashMap<>());
registerAndSend(request, builder -> builder.setEntitiesContentRequest(EntitiesContentRequestMsg.newBuilder() registerAndSend(request, builder -> builder.setEntitiesContentRequest(EntitiesContentRequestMsg.newBuilder()
.setVersionId(versionId) .setVersionId(versionId)
.setEntityType(entityType.name()) .setEntityType(entityType.name())
@ -412,10 +414,10 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
((ListVersionsGitRequest) request).getFuture().set(toPageData(listVersionsResponse)); ((ListVersionsGitRequest) request).getFuture().set(toPageData(listVersionsResponse));
} else if (vcResponseMsg.hasEntityContentResponse()) { } else if (vcResponseMsg.hasEntityContentResponse()) {
TransportProtos.EntityContentResponseMsg responseMsg = vcResponseMsg.getEntityContentResponse(); TransportProtos.EntityContentResponseMsg responseMsg = vcResponseMsg.getEntityContentResponse();
log.trace("[{}] received chunk {} for 'getEntity'", responseMsg.getChunkedMsgId(), responseMsg.getChunkIndex()); log.trace("Received chunk {} for 'getEntity'", responseMsg.getChunkIndex());
var joined = joinChunks(requestId, responseMsg, 1); var joined = joinChunks(requestId, responseMsg, 0, 1);
if (joined.isPresent()) { if (joined.isPresent()) {
log.trace("[{}] collected all chunks for 'getEntity'", responseMsg.getChunkedMsgId()); log.trace("Collected all chunks for 'getEntity'");
((EntityContentGitRequest) request).getFuture().set(joined.get().get(0)); ((EntityContentGitRequest) request).getFuture().set(joined.get().get(0));
} else { } else {
completed = false; completed = false;
@ -424,7 +426,7 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
TransportProtos.EntitiesContentResponseMsg responseMsg = vcResponseMsg.getEntitiesContentResponse(); TransportProtos.EntitiesContentResponseMsg responseMsg = vcResponseMsg.getEntitiesContentResponse();
TransportProtos.EntityContentResponseMsg item = responseMsg.getItem(); TransportProtos.EntityContentResponseMsg item = responseMsg.getItem();
if (responseMsg.getItemsCount() > 0) { if (responseMsg.getItemsCount() > 0) {
var joined = joinChunks(requestId, item, responseMsg.getItemsCount()); var joined = joinChunks(requestId, item, responseMsg.getItemIdx(), responseMsg.getItemsCount());
if (joined.isPresent()) { if (joined.isPresent()) {
((EntitiesContentGitRequest) request).getFuture().set(joined.get()); ((EntitiesContentGitRequest) request).getFuture().set(joined.get());
} else { } else {
@ -459,16 +461,17 @@ public class DefaultGitVersionControlQueueService implements GitVersionControlQu
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private Optional<List<EntityExportData>> joinChunks(UUID requestId, TransportProtos.EntityContentResponseMsg responseMsg, int expectedMsgCount) { private Optional<List<EntityExportData>> joinChunks(UUID requestId, TransportProtos.EntityContentResponseMsg responseMsg, int itemIdx, int expectedMsgCount) {
var chunksMap = chunkedMsgs.get(requestId); var chunksMap = chunkedMsgs.get(requestId);
if (chunksMap == null) { if (chunksMap == null) {
return Optional.empty(); return Optional.empty();
} }
String[] msgChunks = chunksMap.computeIfAbsent(responseMsg.getChunkedMsgId(), id -> new String[responseMsg.getChunksCount()]); String[] msgChunks = chunksMap.computeIfAbsent(itemIdx, id -> new String[responseMsg.getChunksCount()]);
msgChunks[responseMsg.getChunkIndex()] = responseMsg.getData(); msgChunks[responseMsg.getChunkIndex()] = responseMsg.getData();
if (chunksMap.size() == expectedMsgCount && chunksMap.values().stream() if (chunksMap.size() == expectedMsgCount && chunksMap.values().stream()
.allMatch(chunks -> CollectionsUtil.countNonNull(chunks) == chunks.length)) { .allMatch(chunks -> CollectionsUtil.countNonNull(chunks) == chunks.length)) {
return Optional.of(chunksMap.values().stream() return Optional.of(chunksMap.entrySet().stream()
.sorted(Comparator.comparingInt(Map.Entry::getKey)).map(Map.Entry::getValue)
.map(chunks -> String.join("", chunks)) .map(chunks -> String.join("", chunks))
.map(this::toData) .map(this::toData)
.collect(Collectors.toList())); .collect(Collectors.toList()));

6
application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java

@ -32,17 +32,17 @@ public class AttributeData implements Comparable<AttributeData>{
this.value = value; this.value = value;
} }
@ApiModelProperty(position = 1, value = "Timestamp last updated attribute, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 1, value = "Timestamp last updated attribute, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public long getLastUpdateTs() { public long getLastUpdateTs() {
return lastUpdateTs; return lastUpdateTs;
} }
@ApiModelProperty(position = 2, value = "String representing attribute key", example = "active", readOnly = true) @ApiModelProperty(position = 2, value = "String representing attribute key", example = "active", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public String getKey() { public String getKey() {
return key; return key;
} }
@ApiModelProperty(position = 3, value = "Object representing value of attribute key", example = "false", readOnly = true) @ApiModelProperty(position = 3, value = "Object representing value of attribute key", example = "false", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Object getValue() { public Object getValue() {
return value; return value;
} }

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

@ -20,7 +20,6 @@ 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.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -145,7 +144,6 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
} }
} }
@NotNull
private FutureCallback<Integer> getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) { private FutureCallback<Integer> getCallback(TenantId tenantId, CustomerId customerId, boolean sysTenant, FutureCallback<Void> callback) {
return new FutureCallback<>() { return new FutureCallback<>() {
@Override @Override

4
application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java

@ -30,12 +30,12 @@ public class TsData implements Comparable<TsData>{
this.value = value; this.value = value;
} }
@ApiModelProperty(position = 1, value = "Timestamp last updated timeseries, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 1, value = "Timestamp last updated timeseries, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public long getTs() { public long getTs() {
return ts; return ts;
} }
@ApiModelProperty(position = 2, value = "Object representing value of timeseries key", example = "20", readOnly = true) @ApiModelProperty(position = 2, value = "Object representing value of timeseries key", example = "20", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Object getValue() { public Object getValue() {
return value; return value;
} }

29
application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java

@ -25,7 +25,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@TbCoreComponent
@Slf4j @Slf4j
@Service @Service
public class EventsCleanUpService extends AbstractCleanUpService { public class EventsCleanUpService extends AbstractCleanUpService {
@ -39,9 +38,6 @@ public class EventsCleanUpService extends AbstractCleanUpService {
@Value("${sql.ttl.events.debug_events_ttl}") @Value("${sql.ttl.events.debug_events_ttl}")
private long debugTtlInSec; private long debugTtlInSec;
@Value("${sql.ttl.events.execution_interval_ms}")
private long executionIntervalInMs;
@Value("${sql.ttl.events.enabled}") @Value("${sql.ttl.events.enabled}")
private boolean ttlTaskExecutionEnabled; private boolean ttlTaskExecutionEnabled;
@ -54,28 +50,11 @@ public class EventsCleanUpService extends AbstractCleanUpService {
@Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.events.execution_interval_ms}") @Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.events.execution_interval_ms}")
public void cleanUp() { public void cleanUp() {
if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { if (ttlTaskExecutionEnabled) {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
long regularEventStartTs; long regularEventExpTs = ttlInSec > 0 ? ts - TimeUnit.SECONDS.toMillis(ttlInSec) : 0;
long regularEventEndTs; long debugEventExpTs = debugTtlInSec > 0 ? ts - TimeUnit.SECONDS.toMillis(debugTtlInSec) : 0;
long debugEventStartTs; eventService.cleanupEvents(regularEventExpTs, debugEventExpTs, isSystemTenantPartitionMine());
long debugEventEndTs;
if (ttlInSec > 0) {
regularEventEndTs = ts - TimeUnit.SECONDS.toMillis(ttlInSec);
regularEventStartTs = regularEventEndTs - 2 * executionIntervalInMs;
} else {
regularEventStartTs = regularEventEndTs = 0;
}
if (debugTtlInSec > 0) {
debugEventEndTs = ts - TimeUnit.SECONDS.toMillis(debugTtlInSec);
debugEventStartTs = debugEventEndTs - 2 * executionIntervalInMs;
} else {
debugEventStartTs = debugEventEndTs = 0;
}
eventService.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
} }
} }

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

@ -221,9 +221,6 @@ cassandra:
ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
ts_key_value_partitions_max_cache_size: "${TS_KV_PARTITIONS_MAX_CACHE_SIZE:100000}" ts_key_value_partitions_max_cache_size: "${TS_KV_PARTITIONS_MAX_CACHE_SIZE:100000}"
ts_key_value_ttl: "${TS_KV_TTL:0}" ts_key_value_ttl: "${TS_KV_TTL:0}"
events_ttl: "${TS_EVENTS_TTL:0}"
# Specify TTL of debug log in seconds. The current value corresponds to one week
debug_events_ttl: "${DEBUG_EVENTS_TTL:604800}"
buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}" buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}" concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}"
permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}" permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}"
@ -262,6 +259,8 @@ sql:
batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
partition_size: "${SQL_EVENTS_REGULAR_PARTITION_SIZE_HOURS:168}" # Number of hours to partition the events. The current value corresponds to one week.
debug_partition_size: "${SQL_EVENTS_REGULAR_PARTITION_SIZE_HOURS:1}" # Number of hours to partition the debug events. The current value corresponds to one hour.
edge_events: edge_events:
batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}" batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}"
batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}" batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}"
@ -287,9 +286,11 @@ sql:
ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds ts_key_value_ttl: "${SQL_TTL_TS_TS_KEY_VALUE_TTL:0}" # Number of seconds
events: events:
enabled: "${SQL_TTL_EVENTS_ENABLED:true}" enabled: "${SQL_TTL_EVENTS_ENABLED:true}"
execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:2220000}" # Number of milliseconds (max random initial delay and fixed period). # 37minutes to avoid common interval spikes execution_interval_ms: "${SQL_TTL_EVENTS_EXECUTION_INTERVAL:3600000}" # Number of milliseconds (max random initial delay and fixed period).
events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}" # Number of seconds # Number of seconds. TTL is disabled by default. Accuracy of the cleanup depends on the sql.events.partition_size parameter.
debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}" # Number of seconds. The current value corresponds to one week events_ttl: "${SQL_TTL_EVENTS_EVENTS_TTL:0}"
# Number of seconds. The current value corresponds to one week. Accuracy of the cleanup depends on the sql.events.debug_partition_size parameter.
debug_events_ttl: "${SQL_TTL_EVENTS_DEBUG_EVENTS_TTL:604800}"
edge_events: edge_events:
enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}" enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}"
execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day
@ -1037,7 +1038,7 @@ queue:
poll-interval: "${TB_QUEUE_VC_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_VC_INTERVAL_MS:25}"
pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:60000}"
request-timeout: "${TB_QUEUE_VC_REQUEST_TIMEOUT:60000}" request-timeout: "${TB_QUEUE_VC_REQUEST_TIMEOUT:60000}"
msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:500000}" msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:250000}"
js: js:
# JS Eval request topic # JS Eval request topic
request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}" request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}"

3
application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java

@ -18,7 +18,8 @@ package org.thingsboard.server.actors.stats;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;

17
application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java

@ -19,8 +19,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -60,20 +60,19 @@ public abstract class AbstractRuleEngineControllerTest extends AbstractControlle
return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class); return doGet("/api/ruleChain/metadata/" + ruleChainId.getId().toString(), RuleChainMetaData.class);
} }
protected PageData<Event> getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception { protected PageData<EventInfo> getDebugEvents(TenantId tenantId, EntityId entityId, int limit) throws Exception {
return getEvents(tenantId, entityId, DataConstants.DEBUG_RULE_NODE, limit); return getEvents(tenantId, entityId, EventType.DEBUG_RULE_NODE.getOldName(), limit);
} }
protected PageData<Event> getEvents(TenantId tenantId, EntityId entityId, String eventType, int limit) throws Exception { protected PageData<EventInfo> getEvents(TenantId tenantId, EntityId entityId, String eventType, int limit) throws Exception {
TimePageLink pageLink = new TimePageLink(limit); TimePageLink pageLink = new TimePageLink(limit);
return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&", return doGetTypedWithTimePageLink("/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&",
new TypeReference<PageData<Event>>() { new TypeReference<PageData<EventInfo>>() {
}, pageLink, entityId.getEntityType(), entityId.getId(), eventType, tenantId.getId()); }, pageLink, entityId.getEntityType(), entityId.getId(), eventType, tenantId.getId());
} }
protected JsonNode getMetadata(EventInfo outEvent) {
protected JsonNode getMetadata(Event outEvent) {
String metaDataStr = outEvent.getBody().get("metadata").asText(); String metaDataStr = outEvent.getBody().get("metadata").asText();
try { try {
return mapper.readTree(metaDataStr); return mapper.readTree(metaDataStr);
@ -82,7 +81,7 @@ public abstract class AbstractRuleEngineControllerTest extends AbstractControlle
} }
} }
protected Predicate<Event> filterByCustomEvent() { protected Predicate<EventInfo> filterByCustomEvent() {
return event -> event.getBody().get("msgType").textValue().equals("CUSTOM"); return event -> event.getBody().get("msgType").textValue().equals("CUSTOM");
} }

56
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -71,10 +71,10 @@ 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.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
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;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.RuleChainId; 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.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
@ -207,13 +207,6 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
verifyEdgeConnectionAndInitialData(); verifyEdgeConnectionAndInitialData();
} }
private QueueId getRandomQueueId() throws Exception {
List<Queue> ruleEngineQueues = doGetTypedWithPageLink("/api/queues?serviceType={serviceType}&",
new TypeReference<PageData<Queue>>() {}, new PageLink(100), ServiceType.TB_RULE_ENGINE.name())
.getData();
return ruleEngineQueues.get(0).getId();
}
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
try { try {
@ -388,6 +381,21 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits());
// 2 // 2
OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(deviceProfile.getId());
edgeImitator.expectMessageAmount(1);
Assert.assertTrue(edgeImitator.waitForMessages());
deviceProfile.setFirmwareId(firmwareOtaPackageInfo.getId());
edgeImitator.expectMessageAmount(1);
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getFirmwareIdMSB());
Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getFirmwareIdLSB());
// 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) doDelete("/api/deviceProfile/" + deviceProfile.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
@ -1867,18 +1875,30 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception { private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice(RandomStringUtils.randomAlphanumeric(15), "Default"); OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(thermostatDeviceProfile.getId());
Assert.assertTrue(edgeImitator.waitForMessages());
Device savedDevice = saveDevice(RandomStringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName());
savedDevice.setFirmwareId(firmwareOtaPackageInfo.getId());
savedDevice = doPost("/api/device", savedDevice, Device.class);
// wait until device UPDATED event is sent to edge notification service
// to avoid edge notification service to send device UPDATED event before ASSIGNED_TO_EDGE
Thread.sleep(500);
edgeImitator.expectMessageAmount(1);
doPost("/api/edge/" + edge.getUuidId() doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class); + "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName()); Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getFirmwareIdMSB());
Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getFirmwareIdLSB());
return savedDevice; return savedDevice;
} }
@ -1904,7 +1924,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
return asset; return asset;
} }
private Device saveDevice(String deviceName, String type) throws Exception { private Device saveDevice(String deviceName, String type) {
Device device = new Device(); Device device = new Device();
device.setName(deviceName); device.setName(deviceName);
device.setType(type); device.setType(type);
@ -1918,6 +1938,20 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
return doPost("/api/asset", asset, Asset.class); return doPost("/api/asset", asset, Asset.class);
} }
private OtaPackageInfo saveOtaPackageInfo(DeviceProfileId deviceProfileId) {
SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
firmwareInfo.setDeviceProfileId(deviceProfileId);
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle("Firmware Edge " + RandomStringUtils.randomAlphanumeric(3));
firmwareInfo.setVersion("v1.0");
firmwareInfo.setTag("My firmware #1 v1.0");
firmwareInfo.setUsesUrl(true);
firmwareInfo.setUrl("http://localhost:8080/v1/package");
firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
firmwareInfo.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
}
private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) { private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) {
EdgeEvent edgeEvent = new EdgeEvent(); EdgeEvent edgeEvent = new EdgeEvent();
edgeEvent.setEdgeId(edgeId); edgeEvent.setEdgeId(edgeId);

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

@ -30,9 +30,10 @@ import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
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.Event; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -177,15 +178,15 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
actorSystem.tell(qMsg); actorSystem.tell(qMsg);
Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess(); Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess();
PageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); PageData<EventInfo> eventsPage = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000);
List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); List<EventInfo> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
Assert.assertEquals(2, events.size()); Assert.assertEquals(2, events.size());
Event inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); EventInfo inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId()); Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
Event outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); EventInfo outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId()); Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());
@ -299,16 +300,16 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess(); Mockito.verify(tbMsgCallback, Mockito.timeout(10000)).onSuccess();
PageData<Event> eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); PageData<EventInfo> eventsPage = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000);
List<Event> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); List<EventInfo> events = eventsPage.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
Assert.assertEquals(2, events.size()); Assert.assertEquals(2, events.size());
Event inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); EventInfo inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), inEvent.getEntityId()); Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), inEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
Event outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); EventInfo outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), outEvent.getEntityId()); Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());

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

@ -20,18 +20,15 @@ import org.awaitility.Awaitility;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.support.TestPropertySourceUtils;
import org.testcontainers.containers.GenericContainer;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
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.Event; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
@ -109,11 +106,11 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
Assert.assertNotNull(ruleChainFinal.getFirstRuleNodeId()); Assert.assertNotNull(ruleChainFinal.getFirstRuleNodeId());
//TODO find out why RULE_NODE update event did not appear all the time //TODO find out why RULE_NODE update event did not appear all the time
List<Event> rcEvents = Awaitility.await("Rule Node started successfully") List<EventInfo> rcEvents = Awaitility.await("Rule Node started successfully")
.pollInterval(10, MILLISECONDS) .pollInterval(10, MILLISECONDS)
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> { .until(() -> {
List<Event> debugEvents = getEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), DataConstants.LC_EVENT, 1000) List<EventInfo> debugEvents = getEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), EventType.LC_EVENT.getOldName(), 1000)
.getData().stream().filter(e -> { .getData().stream().filter(e -> {
var body = e.getBody(); var body = e.getBody();
return body.has("event") && body.get("event").asText().equals("STARTED") return body.has("event") && body.get("event").asText().equals("STARTED")
@ -145,11 +142,11 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
log.warn("awaiting tbMsgCallback"); log.warn("awaiting tbMsgCallback");
Mockito.verify(tbMsgCallback, Mockito.timeout(TimeUnit.SECONDS.toMillis(TIMEOUT))).onSuccess(); Mockito.verify(tbMsgCallback, Mockito.timeout(TimeUnit.SECONDS.toMillis(TIMEOUT))).onSuccess();
log.warn("awaiting events"); log.warn("awaiting events");
List<Event> events = Awaitility.await("get debug by custom event") List<EventInfo> events = Awaitility.await("get debug by custom event")
.pollInterval(10, MILLISECONDS) .pollInterval(10, MILLISECONDS)
.atMost(TIMEOUT, TimeUnit.SECONDS) .atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> { .until(() -> {
List<Event> debugEvents = getDebugEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), 1000) List<EventInfo> debugEvents = getDebugEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), 1000)
.getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList()); .getData().stream().filter(filterByCustomEvent()).collect(Collectors.toList());
log.warn("filtered debug events [{}]", debugEvents.size()); log.warn("filtered debug events [{}]", debugEvents.size());
debugEvents.forEach((e) -> log.warn("event: {}", e)); debugEvents.forEach((e) -> log.warn("event: {}", e));
@ -158,11 +155,11 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
x -> x.size() == 2); x -> x.size() == 2);
log.warn("asserting.."); log.warn("asserting..");
Event inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); EventInfo inEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get();
Assert.assertEquals(ruleChainFinal.getFirstRuleNodeId(), inEvent.getEntityId()); Assert.assertEquals(ruleChainFinal.getFirstRuleNodeId(), inEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText());
Event outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); EventInfo outEvent = events.stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get();
Assert.assertEquals(ruleChainFinal.getFirstRuleNodeId(), outEvent.getEntityId()); Assert.assertEquals(ruleChainFinal.getFirstRuleNodeId(), outEvent.getEntityId());
Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText());

13
application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java

@ -18,7 +18,6 @@ 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.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -315,7 +314,6 @@ public class RuleChainMsgConstructorTest {
} }
@NotNull
private RuleNode getOutputNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getOutputNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.flow.TbRuleChainOutputNode", "org.thingsboard.rule.engine.flow.TbRuleChainOutputNode",
@ -324,7 +322,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":592}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":592}"));
} }
@NotNull
private RuleNode getCheckpointNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getCheckpointNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.flow.TbCheckpointNode", "org.thingsboard.rule.engine.flow.TbCheckpointNode",
@ -333,7 +330,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":647}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":647}"));
} }
@NotNull
private RuleNode getSaveTimeSeriesNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getSaveTimeSeriesNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
@ -342,7 +338,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":823,\"layoutY\":157}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":823,\"layoutY\":157}"));
} }
@NotNull
private RuleNode getMessageTypeSwitchNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getMessageTypeSwitchNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
@ -351,7 +346,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":347,\"layoutY\":149}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":347,\"layoutY\":149}"));
} }
@NotNull
private RuleNode getLogOtherNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getLogOtherNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.action.TbLogNode", "org.thingsboard.rule.engine.action.TbLogNode",
@ -360,7 +354,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":378}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":378}"));
} }
@NotNull
private RuleNode getPushToCloudNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getPushToCloudNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
@ -369,7 +362,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":1129,\"layoutY\":52}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":1129,\"layoutY\":52}"));
} }
@NotNull
private RuleNode getAcknowledgeNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getAcknowledgeNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.flow.TbAckNode", "org.thingsboard.rule.engine.flow.TbAckNode",
@ -378,7 +370,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":177,\"layoutY\":703}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":177,\"layoutY\":703}"));
} }
@NotNull
private RuleNode getDeviceProfileNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getDeviceProfileNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
@ -387,7 +378,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \\\"Success\\\" relation type.\",\"layoutX\":187,\"layoutY\":468}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \\\"Success\\\" relation type.\",\"layoutX\":187,\"layoutY\":468}"));
} }
@NotNull
private RuleNode getSaveClientAttributesNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getSaveClientAttributesNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
@ -396,7 +386,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":52}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":52}"));
} }
@NotNull
private RuleNode getLogRpcFromDeviceNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getLogRpcFromDeviceNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.action.TbLogNode", "org.thingsboard.rule.engine.action.TbLogNode",
@ -405,7 +394,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":825,\"layoutY\":266}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":825,\"layoutY\":266}"));
} }
@NotNull
private RuleNode getRpcCallRequestNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getRpcCallRequestNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
@ -414,7 +402,6 @@ public class RuleChainMsgConstructorTest {
JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":466}")); JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":466}"));
} }
@NotNull
private RuleNode getPushToAnalyticsNode(RuleChainId ruleChainId) throws JsonProcessingException { private RuleNode getPushToAnalyticsNode(RuleChainId ruleChainId) throws JsonProcessingException {
return createRuleNode(ruleChainId, return createRuleNode(ruleChainId,
"org.thingsboard.rule.engine.flow.TbRuleChainInputNode", "org.thingsboard.rule.engine.flow.TbRuleChainInputNode",

2
application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java

@ -17,7 +17,6 @@ package org.thingsboard.server.service.sql;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import org.jetbrains.annotations.NotNull;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -180,7 +179,6 @@ public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest
saveTimeseries(asset.getId(), saveTsKvEntry); saveTimeseries(asset.getId(), saveTsKvEntry);
} }
@NotNull
JsonObject getJsonObject(String key, long value, Optional<String> tsKvEntryOpt) { JsonObject getJsonObject(String key, long value, Optional<String> tsKvEntryOpt) {
JsonObject jsonObject = new JsonObject(); JsonObject jsonObject = new JsonObject();
if (tsKvEntryOpt.isPresent()) { if (tsKvEntryOpt.isPresent()) {

11
application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java

@ -28,6 +28,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -53,6 +54,8 @@ public class DefaultDeviceStateServiceTest {
PartitionService partitionService; PartitionService partitionService;
@Mock @Mock
DeviceStateData deviceStateDataMock; DeviceStateData deviceStateDataMock;
@Mock
TbServiceInfoProvider serviceInfoProvider;
DeviceId deviceId = DeviceId.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"); DeviceId deviceId = DeviceId.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112");
@ -60,7 +63,7 @@ public class DefaultDeviceStateServiceTest {
@Before @Before
public void setUp() { public void setUp() {
service = spy(new DefaultDeviceStateService(tenantService, deviceService, attributesService, tsService, clusterService, partitionService)); service = spy(new DefaultDeviceStateService(tenantService, deviceService, attributesService, tsService, clusterService, partitionService, serviceInfoProvider, null, null));
} }
@Test @Test
@ -68,16 +71,16 @@ public class DefaultDeviceStateServiceTest {
service.deviceStates.put(deviceId, deviceStateDataMock); service.deviceStates.put(deviceId, deviceStateDataMock);
DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId);
assertThat(deviceStateData, is(deviceStateDataMock)); assertThat(deviceStateData, is(deviceStateDataMock));
Mockito.verify(service, never()).fetchDeviceStateData(deviceId); Mockito.verify(service, never()).fetchDeviceStateDataUsingEntityDataQuery(deviceId);
} }
@Test @Test
public void givenDeviceIdWithoutDeviceStateInMap_whenGetOrFetchDeviceStateData_thenFetchDeviceStateData() { public void givenDeviceIdWithoutDeviceStateInMap_whenGetOrFetchDeviceStateData_thenFetchDeviceStateData() {
service.deviceStates.clear(); service.deviceStates.clear();
willReturn(deviceStateDataMock).given(service).fetchDeviceStateData(deviceId); willReturn(deviceStateDataMock).given(service).fetchDeviceStateDataUsingEntityDataQuery(deviceId);
DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId);
assertThat(deviceStateData, is(deviceStateDataMock)); assertThat(deviceStateData, is(deviceStateDataMock));
Mockito.verify(service, times(1)).fetchDeviceStateData(deviceId); Mockito.verify(service, times(1)).fetchDeviceStateDataUsingEntityDataQuery(deviceId);
} }
} }

32
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -16,11 +16,13 @@
package org.thingsboard.server.transport.lwm2m; package org.thingsboard.server.transport.lwm2m;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClient;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.ResponseCode;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Assert; import org.junit.Assert;
@ -172,7 +174,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTes
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS)); protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS));
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected DeviceProfile deviceProfile; protected DeviceProfile deviceProfile;
protected ScheduledExecutorService executor; protected ScheduledExecutorService executor;
protected LwM2MTestClient lwM2MTestClient; protected LwM2MTestClient lwM2MTestClient;
@ -235,6 +237,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTes
getWsClient().registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
createNewClient(security, coapConfig, false, endpoint, false, null); createNewClient(security, coapConfig, false, endpoint, false, null);
awaitObserveReadAll(0, false, device.getId().getId().toString());
String msg = getWsClient().waitForUpdate(); String msg = getWsClient().waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
@ -392,4 +395,31 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTes
.until(() -> leshanClient.getRegisteredServers().size() == 0); .until(() -> leshanClient.getRegisteredServers().size() == 0);
} }
protected void awaitObserveReadAll(int cntObserve, boolean isBootstrap, String deviceIdStr) throws Exception {
if (!isBootstrap) {
await("ObserveReadAll after start client: countObserve " + cntObserve)
.atMost(40, TimeUnit.SECONDS)
.until(() -> {
String actualResultReadAll = sendObserve("ObserveReadAll", null, deviceIdStr);
ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
Assert.assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
String actualValuesReadAll = rpcActualResultReadAll.get("value").asText();
log.warn("ObserveReadAll: [{}]", actualValuesReadAll);
int actualCntObserve = "[]".equals(actualValuesReadAll) ? 0 : actualValuesReadAll.split(",").length;
return cntObserve == actualCntObserve;
});
}
}
protected String sendObserve(String method, String params, String deviceIdStr) throws Exception {
String sendRpcRequest;
if (params == null) {
sendRpcRequest = "{\"method\": \"" + method + "\"}";
}
else {
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}";
}
return doPostAsync("/api/plugins/rpc/twoway/" + deviceIdStr, sendRpcRequest, String.class, status().isOk());
}
} }

72
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -53,9 +53,7 @@ import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
@ -110,7 +108,6 @@ public class LwM2MTestClient {
private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer; private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer;
private LwM2MLocationParams locationParams; private LwM2MLocationParams locationParams;
private LwM2mTemperatureSensor lwM2MTemperatureSensor; private LwM2mTemperatureSensor lwM2MTemperatureSensor;
private LwM2MClientState clientState;
private Set<LwM2MClientState> clientStates; private Set<LwM2MClientState> clientStates;
private DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
private LwM2mClientContext clientContext; private LwM2mClientContext clientContext;
@ -169,6 +166,7 @@ public class LwM2MTestClient {
DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
engineFactory.setReconnectOnUpdate(false); engineFactory.setReconnectOnUpdate(false);
engineFactory.setResumeOnConnect(true); engineFactory.setResumeOnConnect(true);
engineFactory.setCommunicationPeriod(5000);
LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
builder.setLocalAddress("0.0.0.0", port); builder.setLocalAddress("0.0.0.0", port);
@ -180,112 +178,94 @@ public class LwM2MTestClient {
builder.setDecoder(new DefaultLwM2mDecoder(false)); builder.setDecoder(new DefaultLwM2mDecoder(false));
builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false)); builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false));
clientState = ON_INIT;
clientStates = new HashSet<>(); clientStates = new HashSet<>();
clientStates.add(clientState); clientStates.add(ON_INIT);
leshanClient = builder.build(); leshanClient = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() { LwM2mClientObserver observer = new LwM2mClientObserver() {
@Override @Override
public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
clientState = ON_BOOTSTRAP_STARTED; clientStates.add(ON_BOOTSTRAP_STARTED);
clientStates.add(clientState);
} }
@Override @Override
public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
clientState = ON_BOOTSTRAP_SUCCESS; clientStates.add(ON_BOOTSTRAP_SUCCESS);
clientStates.add(clientState);
} }
@Override @Override
public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
clientState = ON_BOOTSTRAP_FAILURE; clientStates.add(ON_BOOTSTRAP_FAILURE);
clientStates.add(clientState);
} }
@Override @Override
public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
clientState = ON_BOOTSTRAP_TIMEOUT; clientStates.add(ON_BOOTSTRAP_TIMEOUT);
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
clientState = ON_REGISTRATION_STARTED; clientStates.add(ON_REGISTRATION_STARTED);
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
clientState = ON_REGISTRATION_SUCCESS; clientStates.add(ON_REGISTRATION_SUCCESS);
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
clientState = ON_REGISTRATION_FAILURE; clientStates.add(ON_REGISTRATION_FAILURE);
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
clientState = ON_REGISTRATION_TIMEOUT; clientStates.add(ON_REGISTRATION_TIMEOUT);
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
clientState = ON_UPDATE_STARTED; clientStates.add(ON_UPDATE_STARTED);
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
clientState = ON_UPDATE_SUCCESS; clientStates.add(ON_UPDATE_SUCCESS);
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
clientState = ON_UPDATE_FAILURE; clientStates.add(ON_UPDATE_FAILURE);
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
clientState = ON_UPDATE_TIMEOUT; clientStates.add(ON_UPDATE_TIMEOUT);
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
clientState = ON_DEREGISTRATION_STARTED; clientStates.add(ON_DEREGISTRATION_STARTED);
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
clientState = ON_DEREGISTRATION_SUCCESS; clientStates.add(ON_DEREGISTRATION_SUCCESS);
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
clientState = ON_DEREGISTRATION_FAILURE; clientStates.add(ON_DEREGISTRATION_FAILURE);
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
clientState = ON_DEREGISTRATION_TIMEOUT; clientStates.add(ON_DEREGISTRATION_TIMEOUT);
clientStates.add(clientState);
} }
@Override @Override
public void onUnexpectedError(Throwable unexpectedError) { public void onUnexpectedError(Throwable unexpectedError) {
clientState = ON_EXPECTED_ERROR; clientStates.add(ON_EXPECTED_ERROR);
clientStates.add(clientState);
} }
}; };
this.leshanClient.addObserver(observer); this.leshanClient.addObserver(observer);
@ -339,18 +319,6 @@ public class LwM2MTestClient {
private void awaitClientAfterStartConnectLw() { private void awaitClientAfterStartConnectLw() {
LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint); LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint);
CountDownLatch latch = new CountDownLatch(1); Mockito.doAnswer(invocationOnMock -> null).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true);
Mockito.doAnswer(invocation -> {
latch.countDown();
return null;
}).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true);
try {
if (!latch.await(1, TimeUnit.SECONDS)) {
throw new RuntimeException("Failed to await TimeOut lwm2m client initialization!");
}
} catch (InterruptedException e) {
throw new RuntimeException("Exception Failed to await lwm2m client initialization! ", e);
}
} }
} }

55
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java

@ -37,7 +37,6 @@ import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.hasSize;
import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries; import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING;
@ -91,13 +90,18 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
" ],\n" + " ],\n" +
" \"attributeLwm2m\": {}\n" + " \"attributeLwm2m\": {}\n" +
" }"; " }";
@Test
private List<OtaPackageUpdateStatus> expectedStatuses;
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception { public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration); createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, false, null); createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, false, null);
awaitObserveReadAll(0, false, device.getId().getId().toString());
device.setFirmwareId(createFirmware().getId()); device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class); final Device savedDevice = doPost("/api/device", device, Device.class);
@ -121,27 +125,22 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, false, null); createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, false, null);
awaitObserveReadAll(9, false, device.getId().getId().toString());
device.setFirmwareId(createFirmware().getId()); device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class); final Device savedDevice = doPost("/api/device", device, Device.class);
Thread.sleep(1000);
assertThat(savedDevice).as("saved device").isNotNull(); assertThat(savedDevice).as("saved device").isNotNull();
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice);
final List<OtaPackageUpdateStatus> expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
List<TsKvEntry> ts = await("await on timeseries") List<TsKvEntry> ts = await("await on timeseries")
.atMost(30, TimeUnit.SECONDS) .atMost(30, TimeUnit.SECONDS)
.until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + .until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" +
savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" +
System.currentTimeMillis(), new TypeReference<>() { System.currentTimeMillis(), new TypeReference<>() {
})), hasSize(expectedStatuses.size())); })), this::predicateForStatuses);
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator log.warn("Object5: Got the ts: {}", ts);
.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString)
.map(OtaPackageUpdateStatus::valueOf)
.collect(Collectors.toList());
Assert.assertEquals(expectedStatuses, statuses);
} }
/** /**
@ -156,34 +155,21 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, false, null); createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, false, null);
awaitObserveReadAll(9, false, device.getId().getId().toString());
Thread.sleep(1000);
device.setSoftwareId(createSoftware().getId()); device.setSoftwareId(createSoftware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class); //sync call final Device savedDevice = doPost("/api/device", device, Device.class); //sync call
Thread.sleep(1000);
assertThat(savedDevice).as("saved device").isNotNull(); assertThat(savedDevice).as("saved device").isNotNull();
assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice);
final List<OtaPackageUpdateStatus> expectedStatuses = List.of( expectedStatuses = List.of(
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED);
log.warn("AWAIT atMost {} SECONDS on timeseries List<TsKvEntry> by API with list size {}...", TIMEOUT, expectedStatuses.size());
List<TsKvEntry> ts = await("await on timeseries") List<TsKvEntry> ts = await("await on timeseries")
.atMost(30, TimeUnit.SECONDS) .atMost(30, TimeUnit.SECONDS)
.until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), hasSize(expectedStatuses.size())); .until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), this::predicateForStatuses);
log.warn("Got the ts: {}", ts); log.warn("Object9: Got the ts: {}", ts);
ts.sort(Comparator.comparingLong(TsKvEntry::getTs));
log.warn("Ts ordered: {}", ts);
ts.forEach((x) -> log.warn("ts: { Thread.sleep(1000);} ", x));
List<OtaPackageUpdateStatus> statuses = ts.stream().map(KvEntry::getValueAsString)
.map(OtaPackageUpdateStatus::valueOf)
.collect(Collectors.toList());
log.warn("Converted ts to statuses: {}", statuses);
assertThat(statuses).isEqualTo(expectedStatuses);
} }
private Device getDeviceFromAPI(UUID deviceId) throws Exception { private Device getDeviceFromAPI(UUID deviceId) throws Exception {
@ -198,4 +184,13 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries); log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries);
return tsKvEntries; return tsKvEntries;
} }
private boolean predicateForStatuses (List<TsKvEntry> ts) {
List<OtaPackageUpdateStatus> statuses = ts.stream().sorted(Comparator
.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString)
.map(OtaPackageUpdateStatus::valueOf)
.collect(Collectors.toList());
log.warn("{}", statuses);
return statuses.containsAll(expectedStatuses);
}
} }

1
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java

@ -147,6 +147,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
deviceId = device.getId().getId().toString(); deviceId = device.getId().getId().toString();
lwM2MTestClient.start(true); lwM2MTestClient.start(true);
awaitObserveReadAll(2, false, device.getId().getId().toString());
} }
protected String pathIdVerToObjectId(String pathIdVer) { protected String pathIdVerToObjectId(String pathIdVer) {

76
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.transport.lwm2m.rpc.sql; package org.thingsboard.server.transport.lwm2m.rpc.sql;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.junit.Test; import org.junit.Test;
@ -25,31 +26,34 @@ import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTes
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
@Slf4j
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest { public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest {
/** /**
* ObserveReadAll * ObserveReadAll&ObserveReadAll
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception { public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception {
String actualResultBefore = sendObserve("ObserveReadAll", null); String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
sendRpcObserve("Observe", fromVersionedIdToObjectId(idVer_3_0_0));
String actualResultBefore = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class); ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText());
assertTrue(rpcActualResultBefore.get("value").asText().contains(fromVersionedIdToObjectId(idVer_3_0_9))); int cntObserveBefore = rpcActualResultBefore.get("value").asText().split(",").length;
assertTrue(rpcActualResultBefore.get("value").asText().contains(fromVersionedIdToObjectId(idVer_19_0_0))); assertTrue(cntObserveBefore > 0);
String actualResult = sendObserve("ObserveCancelAll", null); String actualResult = sendRpcObserve("ObserveCancelAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("2", rpcActualResult.get("value").asText()); int cntObserveCancelAll = Integer.parseInt(rpcActualResult.get("value").asText());
String actualResultAfter = sendObserve("ObserveReadAll", null); assertTrue(cntObserveCancelAll > 0);
String actualResultAfter = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResultAfter = JacksonUtil.fromString(actualResultAfter, ObjectNode.class); ObjectNode rpcActualResultAfter = JacksonUtil.fromString(actualResultAfter, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultAfter.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultAfter.get("result").asText());
String expectResultAfter = "[]"; String expectResultAfter = "[]";
@ -63,7 +67,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
@Test @Test
public void testObserveSingleResourceWithout_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception { public void testObserveSingleResourceWithout_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult = sendObserve("Observe", fromVersionedIdToObjectId(expectedId)); String actualResult = sendRpcObserve("Observe", fromVersionedIdToObjectId(expectedId));
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
@ -75,7 +79,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
@Test @Test
public void testObserveSingleResourceWith_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception { public void testObserveSingleResourceWith_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String actualResult = sendObserve("Observe", expectedId); String actualResult = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
@ -91,7 +95,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
LwM2mPath expectedPath = new LwM2mPath(expectedInstance); LwM2mPath expectedPath = new LwM2mPath(expectedInstance);
int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String actualResult = sendObserve("Observe", expectedId); String actualResult = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: 1.0"; String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: 1.0";
@ -107,7 +111,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception { public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception {
String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get(); String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get();
String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0; String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0;
String actualResult = sendObserve("Observe", expected); String actualResult = sendRpcObserve("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText());
} }
@ -120,7 +124,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
@Test @Test
public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception {
String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3;
String actualResult = sendObserve("Observe", expected); String actualResult = sendRpcObserve("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String expectedValue = "value MUST NOT be null"; String expectedValue = "value MUST NOT be null";
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
@ -135,8 +139,8 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
@Test @Test
public void testObserveRSourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception { public void testObserveRSourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception {
String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0; String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0;
sendObserve("Observe", expectedId); sendRpcObserve("Observe", expectedId);
String actualResult = sendObserve("Observe", expectedId); String actualResult = sendRpcObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText());
} }
@ -148,7 +152,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/ */
@Test @Test
public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception { public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception {
String actualResult = sendObserve("Observe", idVer_3_0_9); String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
sendRpcObserve("Observe", fromVersionedIdToObjectId(idVer_3_0_0));
sendRpcObserve("ObserveReadAll", null);
String actualResult = sendRpcObserve("Observe", idVer_3_0_0);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Observation is already registered!"; String expected = "Observation is already registered!";
@ -161,18 +168,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/ */
@Test @Test
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception { public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception {
String actualResultCancel = sendObserve("ObserveCancelAll", null); String actualResultReadAll = sendRpcObserve("ObserveReadAll", null);
ObjectNode rpcActualResultCancel = JacksonUtil.fromString(actualResultCancel, ObjectNode.class); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancel.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText());
sendObserve("Observe",idVer_19_0_0); String actualValuesReadAll = rpcActualResultReadAll.get("value").asText();
sendObserve("Observe", idVer_3_0_9); log.warn("ObserveReadAll: [{}]", actualValuesReadAll);
String actualResult = sendObserve("ObserveReadAll", null); assertEquals(2, actualValuesReadAll.split(",").length);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_19_0_0)));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_3_0_9)));
assertEquals(2, actualValues.split(",").length);
} }
@ -182,25 +183,18 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/ */
@Test @Test
public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception { public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception {
sendObserve("ObserveCancelAll", null); sendRpcObserve("ObserveCancelAll", null);
String expectedId_3_0_3 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_3; String expectedId_3_0_3 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_3;
String expectedId_5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedId_5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
sendObserve("Observe", expectedId_3_0_3); sendRpcObserve("Observe", expectedId_3_0_3);
sendObserve("Observe", expectedId_5_0_3); sendRpcObserve("Observe", expectedId_5_0_3);
String actualResult = sendObserve("ObserveCancel", expectedId_3_0_3); String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_3);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText()); assertEquals("1", rpcActualResult.get("value").asText());
} }
private String sendObserve(String method, String params) throws Exception { private String sendRpcObserve(String method, String params) throws Exception {
String sendRpcRequest; return sendObserve(method, params, deviceId);
if (params == null) {
sendRpcRequest = "{\"method\": \"" + method + "\"}";
}
else {
sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}";
}
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk());
} }
} }

66
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java

@ -57,6 +57,7 @@ import java.security.PublicKey;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -67,7 +68,9 @@ import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
@ -190,15 +193,25 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M
boolean isBootstrap, boolean isBootstrap,
LwM2MClientState finishState, LwM2MClientState finishState,
boolean isStartLw) throws Exception { boolean isStartLw) throws Exception {
createNewClient(security, coapConfig, true, endpoint, isBootstrap, null);
createDeviceProfile(transportConfiguration); createDeviceProfile(transportConfiguration);
final Device device = createDevice(deviceCredentials, endpoint); final Device device = createDevice(deviceCredentials, endpoint);
device.getId().getId().toString(); device.getId().getId().toString();
createNewClient(security, coapConfig, true, endpoint, isBootstrap, null);
lwM2MTestClient.start(isStartLw); lwM2MTestClient.start(isStartLw);
awaitObserveReadAll(0, isBootstrap, device.getId().getId().toString());
await(awaitAlias)
.atMost(40, TimeUnit.SECONDS)
.until(() -> {
log.warn("basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates());
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED);
});
await(awaitAlias) await(awaitAlias)
.atMost(5000, TimeUnit.MILLISECONDS) .atMost(40, TimeUnit.SECONDS)
.until(() -> finishState.equals(lwM2MTestClient.getClientState())); .until(() -> {
Assert.assertEquals(expectedStatuses, lwM2MTestClient.getClientStates()); log.warn("basicTestConnection -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates());
return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS);
});
Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatuses));
} }
@ -228,27 +241,52 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M
Set<LwM2MClientState> expectedStatusesBs, Set<LwM2MClientState> expectedStatusesBs,
boolean isBootstrap, boolean isBootstrap,
Security securityBs) throws Exception { Security securityBs) throws Exception {
createNewClient(security, coapConfig, true, endpoint, isBootstrap, securityBs);
createDeviceProfile(transportConfiguration); createDeviceProfile(transportConfiguration);
final Device device = createDevice(deviceCredentials, endpoint); final Device device = createDevice(deviceCredentials, endpoint);
String deviceId = device.getId().getId().toString(); String deviceIdStr = device.getId().getId().toString();
createNewClient(security, coapConfig, true, endpoint, isBootstrap, securityBs);
lwM2MTestClient.start(true); lwM2MTestClient.start(true);
awaitObserveReadAll(0, isBootstrap, deviceIdStr);
await(awaitAlias)
.atMost(40, TimeUnit.SECONDS)
.until(() -> {
log.warn("basicTest First Connection started -> finishState: [{}] states: {}", ON_REGISTRATION_SUCCESS, lwM2MTestClient.getClientStates());
return lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED);
});
await(awaitAlias) await(awaitAlias)
.atMost(5000, TimeUnit.MILLISECONDS) .atMost(40, TimeUnit.SECONDS)
.until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState())); .until(() -> {
Assert.assertEquals(expectedStatusesLwm2m, lwM2MTestClient.getClientStates()); log.warn("basicTest First Connection -> finishState: [{}] states: {}", ON_REGISTRATION_SUCCESS, lwM2MTestClient.getClientStates());
return lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS);
});
Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesLwm2m));
String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version
+ "/0/" + RESOURCE_ID_9; + "/0/" + RESOURCE_ID_9;
String actualResult = sendRPCSecurityExecuteById(executedPath, deviceId, endpoint); lwM2MTestClient.setClientStates(new HashSet<>());
String actualResult = sendRPCSecurityExecuteById(executedPath, deviceIdStr, endpoint);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
if (!(rpcActualResult.get("result").asText().equals(ResponseCode.CHANGED.getName()))) {
actualResult = sendRPCSecurityExecuteById(executedPath, deviceIdStr, endpoint);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
}
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
expectedStatusesBs.add(ON_DEREGISTRATION_STARTED); expectedStatusesBs.add(ON_DEREGISTRATION_STARTED);
expectedStatusesBs.add(ON_DEREGISTRATION_SUCCESS); expectedStatusesBs.add(ON_DEREGISTRATION_SUCCESS);
await(awaitAlias) await(awaitAlias)
.atMost(5000, TimeUnit.MILLISECONDS) .atMost(40, TimeUnit.SECONDS)
.until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState())); .until(() -> {
Assert.assertEquals(expectedStatusesBs, lwM2MTestClient.getClientStates()); log.warn("basicTestConnection started -> finishState: [{}] states: {}", ON_REGISTRATION_SUCCESS, lwM2MTestClient.getClientStates());
return lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED);
});
await(awaitAlias)
.atMost(40, TimeUnit.SECONDS)
.until(() -> {
log.warn("basicTestConnection -> finishState: [{}] states: {}", ON_REGISTRATION_SUCCESS, lwM2MTestClient.getClientStates());
return lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_SUCCESS) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS);
});
Assert.assertTrue(lwM2MTestClient.getClientStates().containsAll(expectedStatusesBs));
} }
protected List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsSecure(LwM2MSecurityMode mode, LwM2MProfileBootstrapConfigType bootstrapConfigType) { protected List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsSecure(LwM2MSecurityMode mode, LwM2MProfileBootstrapConfigType bootstrapConfigType) {
@ -397,7 +435,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M
return doPost("/api/device/credentials", deviceCredentials).andReturn(); return doPost("/api/device/credentials", deviceCredentials).andReturn();
} }
private String sendRPCSecurityExecuteById(String path, String deviceId, String endpoint) throws Exception { protected String sendRPCSecurityExecuteById(String path, String deviceId, String endpoint) throws Exception {
log.info("endpoint1: [{}]", endpoint); log.info("endpoint1: [{}]", endpoint);

2
common/actor/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.4.0-SNAPSHOT</version> <version>3.4.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cache/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.4.0-SNAPSHOT</version> <version>3.4.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

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

8
common/cluster-api/src/main/proto/queue.proto

@ -803,9 +803,8 @@ message EntityContentRequestMsg {
message EntityContentResponseMsg { message EntityContentResponseMsg {
string data = 1; string data = 1;
string chunkedMsgId = 2; int32 chunkIndex = 2;
int32 chunkIndex = 3; int32 chunksCount = 3;
int32 chunksCount = 4;
} }
message EntitiesContentRequestMsg { message EntitiesContentRequestMsg {
@ -817,7 +816,8 @@ message EntitiesContentRequestMsg {
message EntitiesContentResponseMsg { message EntitiesContentResponseMsg {
EntityContentResponseMsg item = 1; EntityContentResponseMsg item = 1;
int32 itemsCount = 2; int32 itemIdx = 2;
int32 itemsCount = 3;
} }
message VersionsDiffRequestMsg { message VersionsDiffRequestMsg {

2
common/coap-server/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>3.4.0-SNAPSHOT</version> <version>3.4.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/pom.xml

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

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

@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -66,6 +67,8 @@ public interface DeviceService {
PageData<DeviceInfo> findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink); PageData<DeviceInfo> findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink);
PageData<DeviceIdInfo> findDeviceIdInfos(PageLink pageLink);
PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
PageData<Device> findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type, PageLink pageLink); PageData<Device> findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type, PageLink pageLink);
@ -78,6 +81,10 @@ public interface DeviceService {
ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds); ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds);
List<Device> findDevicesByIds(List<DeviceId> deviceIds);
ListenableFuture<List<Device>> findDevicesByIdsAsync(List<DeviceId> deviceIds);
void deleteDevicesByTenantId(TenantId tenantId); void deleteDevicesByTenantId(TenantId tenantId);
PageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); PageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);

18
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -16,34 +16,32 @@
package org.thingsboard.server.dao.event; package org.thingsboard.server.dao.event;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.EventInfo;
import org.thingsboard.server.common.data.event.Event;
import org.thingsboard.server.common.data.event.EventFilter; import org.thingsboard.server.common.data.event.EventFilter;
import org.thingsboard.server.common.data.event.EventType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List; import java.util.List;
import java.util.Optional;
public interface EventService { public interface EventService {
ListenableFuture<Void> saveAsync(Event event); ListenableFuture<Void> saveAsync(Event event);
Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid); PageData<EventInfo> findEvents(TenantId tenantId, EntityId entityId, EventType eventType, TimePageLink pageLink);
PageData<Event> findEvents(TenantId tenantId, EntityId entityId, TimePageLink pageLink); List<EventInfo> findLatestEvents(TenantId tenantId, EntityId entityId, EventType eventType, int limit);
PageData<Event> findEvents(TenantId tenantId, EntityId entityId, String eventType, TimePageLink pageLink); PageData<EventInfo> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
PageData<Event> findEventsByFilter(TenantId tenantId, EntityId entityId, EventFilter eventFilter, TimePageLink pageLink);
void removeEvents(TenantId tenantId, EntityId entityId); void removeEvents(TenantId tenantId, EntityId entityId);
void removeEvents(TenantId tenantId, EntityId entityId, EventFilter eventFilter, Long startTime, Long endTime); void removeEvents(TenantId tenantId, EntityId entityId, EventFilter eventFilter, Long startTime, Long endTime);
void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs); void cleanupEvents(long regularEventExpTs, long debugEventExpTs, boolean cleanupDb);
void migrateEvents();
} }

12
common/data/src/main/java/org/thingsboard/server/common/data/event/DebugRuleNodeEventFilter.java → common/dao-api/src/main/java/org/thingsboard/server/dao/util/DbTypeInfoComponent.java

@ -13,14 +13,10 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.common.data.event; package org.thingsboard.server.dao.util;
import io.swagger.annotations.ApiModel; public interface DbTypeInfoComponent {
boolean isLatestTsDaoStoredToSql();
@ApiModel
public class DebugRuleNodeEventFilter extends DebugEvent {
@Override
public EventType getEventType() {
return EventType.DEBUG_RULE_NODE;
}
} }

33
common/dao-api/src/main/java/org/thingsboard/server/dao/util/DefaultDbTypeInfoComponent.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.util;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class DefaultDbTypeInfoComponent implements DbTypeInfoComponent {
@Value("${database.ts_latest.type:sql}")
@Getter
private String latestTsDbType;
@Override
public boolean isLatestTsDaoStoredToSql() {
return !latestTsDbType.equalsIgnoreCase("cassandra");
}
}

2
common/data/pom.xml

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

4
common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java

@ -58,13 +58,13 @@ public class AdminSettings extends BaseData<AdminSettingsId> implements HasTenan
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();
} }
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/Customer.java

@ -83,7 +83,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId, E
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the customer creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the customer creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();
@ -159,7 +159,7 @@ public class Customer extends ContactBased<CustomerId> implements HasTenantId, E
@Override @Override
@JsonProperty(access = Access.READ_ONLY) @JsonProperty(access = Access.READ_ONLY)
@ApiModelProperty(position = 4, value = "Name of the customer. Read-only, duplicated from title for backward compatibility", example = "Company A", readOnly = true) @ApiModelProperty(position = 4, value = "Name of the customer. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public String getName() { public String getName() {
return title; return title;
} }

14
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -71,13 +71,13 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();
} }
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -95,7 +95,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.title = title; this.title = title;
} }
@ApiModelProperty(position = 8, value = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", readOnly = true) @ApiModelProperty(position = 8, value = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public String getImage() { public String getImage() {
return image; return image;
} }
@ -104,7 +104,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.image = image; this.image = image;
} }
@ApiModelProperty(position = 5, value = "List of assigned customers with their info.", readOnly = true) @ApiModelProperty(position = 5, value = "List of assigned customers with their info.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Set<ShortCustomerInfo> getAssignedCustomers() { public Set<ShortCustomerInfo> getAssignedCustomers() {
return assignedCustomers; return assignedCustomers;
} }
@ -113,7 +113,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.assignedCustomers = assignedCustomers; this.assignedCustomers = assignedCustomers;
} }
@ApiModelProperty(position = 6, value = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", readOnly = true) @ApiModelProperty(position = 6, value = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public boolean isMobileHide() { public boolean isMobileHide() {
return mobileHide; return mobileHide;
} }
@ -122,7 +122,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.mobileHide = mobileHide; this.mobileHide = mobileHide;
} }
@ApiModelProperty(position = 7, value = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", readOnly = true) @ApiModelProperty(position = 7, value = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public Integer getMobileOrder() { public Integer getMobileOrder() {
return mobileOrder; return mobileOrder;
} }
@ -180,7 +180,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
} }
} }
@ApiModelProperty(position = 4, value = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", readOnly = true) @ApiModelProperty(position = 4, value = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {

6
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -52,12 +52,6 @@ public class DataConstants {
} }
public static final String ALARM = "ALARM"; public static final String ALARM = "ALARM";
public static final String ERROR = "ERROR";
public static final String LC_EVENT = "LC_EVENT";
public static final String STATS = "STATS";
public static final String DEBUG_RULE_NODE = "DEBUG_RULE_NODE";
public static final String DEBUG_RULE_CHAIN = "DEBUG_RULE_CHAIN";
public static final String IN = "IN"; public static final String IN = "IN";
public static final String OUT = "OUT"; public static final String OUT = "OUT";

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

@ -112,13 +112,13 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the device creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the device creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();
} }
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -127,7 +127,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
} }

42
common/data/src/main/java/org/thingsboard/server/common/data/DeviceIdInfo.java

@ -0,0 +1,42 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import java.io.Serializable;
import java.util.UUID;
@Data
@Slf4j
public class DeviceIdInfo implements Serializable, HasTenantId {
private static final long serialVersionUID = 2233745129677581815L;
private final TenantId tenantId;
private final CustomerId customerId;
private final DeviceId deviceId;
public DeviceIdInfo(UUID tenantId, UUID customerId, UUID deviceId) {
this.tenantId = new TenantId(tenantId);
this.customerId = customerId != null ? new CustomerId(customerId) : null;
this.deviceId = new DeviceId(deviceId);
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java

@ -24,11 +24,11 @@ import org.thingsboard.server.common.data.id.DeviceId;
@Data @Data
public class DeviceInfo extends Device { public class DeviceInfo extends Device {
@ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", readOnly = true) @ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String customerTitle; private String customerTitle;
@ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", readOnly = true) @ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private boolean customerIsPublic; private boolean customerIsPublic;
@ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", readOnly = true) @ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String deviceProfileName; private String deviceProfileName;
public DeviceInfo() { public DeviceInfo() {

4
common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java

@ -49,7 +49,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
private static final long serialVersionUID = 6998485460273302018L; private static final long serialVersionUID = 6998485460273302018L;
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@NoXss @NoXss
@Length(fieldName = "name") @Length(fieldName = "name")
@ -129,7 +129,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

6
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -87,7 +87,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
return getName() /*What the ...*/; return getName() /*What the ...*/;
} }
@ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
@ -98,7 +98,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
return name; return name;
} }
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
@ -113,7 +113,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the Entity View creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the Entity View creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

4
common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java

@ -22,9 +22,9 @@ import org.thingsboard.server.common.data.id.EntityViewId;
@Data @Data
public class EntityViewInfo extends EntityView { public class EntityViewInfo extends EntityView {
@ApiModelProperty(position = 12, value = "Title of the Customer that owns the entity view.", readOnly = true) @ApiModelProperty(position = 12, value = "Title of the Customer that owns the entity view.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String customerTitle; private String customerTitle;
@ApiModelProperty(position = 13, value = "Indicates special 'Public' Customer that is auto-generated to use the entity view on public dashboards.", readOnly = true) @ApiModelProperty(position = 13, value = "Indicates special 'Public' Customer that is auto-generated to use the entity view on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private boolean customerIsPublic; private boolean customerIsPublic;
public EntityViewInfo() { public EntityViewInfo() {

14
common/data/src/main/java/org/thingsboard/server/common/data/Event.java → common/data/src/main/java/org/thingsboard/server/common/data/EventInfo.java

@ -28,32 +28,32 @@ import org.thingsboard.server.common.data.id.TenantId;
*/ */
@Data @Data
@ApiModel @ApiModel
public class Event extends BaseData<EventId> { public class EventInfo extends BaseData<EventId> {
@ApiModelProperty(position = 1, value = "JSON object with Tenant Id.", readOnly = true) @ApiModelProperty(position = 1, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@ApiModelProperty(position = 2, value = "Event type", example = "STATS") @ApiModelProperty(position = 2, value = "Event type", example = "STATS")
private String type; private String type;
@ApiModelProperty(position = 3, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") @ApiModelProperty(position = 3, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9")
private String uid; private String uid;
@ApiModelProperty(position = 4, value = "JSON object with Entity Id for which event is created.", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Entity Id for which event is created.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private EntityId entityId; private EntityId entityId;
@ApiModelProperty(position = 5, value = "Event body.", dataType = "com.fasterxml.jackson.databind.JsonNode") @ApiModelProperty(position = 5, value = "Event body.", dataType = "com.fasterxml.jackson.databind.JsonNode")
private transient JsonNode body; private transient JsonNode body;
public Event() { public EventInfo() {
super(); super();
} }
public Event(EventId id) { public EventInfo(EventId id) {
super(id); super(id);
} }
public Event(Event event) { public EventInfo(EventInfo event) {
super(event); super(event);
} }
@ApiModelProperty(position = 6, value = "Timestamp of the event creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 6, value = "Timestamp of the event creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

4
common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -23,10 +24,13 @@ public interface ExportableEntity<I extends EntityId> extends HasId<I>, HasName
void setId(I id); void setId(I id);
@ApiModelProperty(position = 100, value = "JSON object with External Id from the VCS", accessMode = ApiModelProperty.AccessMode.READ_ONLY, hidden = true)
I getExternalId(); I getExternalId();
void setExternalId(I externalId); void setExternalId(I externalId);
long getCreatedTime(); long getCreatedTime();
void setCreatedTime(long createdTime); void setCreatedTime(long createdTime);
void setTenantId(TenantId tenantId); void setTenantId(TenantId tenantId);

2
common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java

@ -30,7 +30,7 @@ public class OtaPackage extends OtaPackageInfo {
private static final long serialVersionUID = 3091601761339422546L; private static final long serialVersionUID = 3091601761339422546L;
@ApiModelProperty(position = 16, value = "OTA Package data.", readOnly = true) @ApiModelProperty(position = 16, value = "OTA Package data.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private transient ByteBuffer data; private transient ByteBuffer data;
public OtaPackage() { public OtaPackage() {

28
common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java

@ -40,44 +40,44 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
private static final long serialVersionUID = 3168391583570815419L; private static final long serialVersionUID = 3168391583570815419L;
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the ota package can't be changed.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the ota package can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@ApiModelProperty(position = 4, value = "JSON object with Device Profile Id. Device Profile Id of the ota package can't be changed.", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Device Profile Id. Device Profile Id of the ota package can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private DeviceProfileId deviceProfileId; private DeviceProfileId deviceProfileId;
@ApiModelProperty(position = 5, value = "OTA Package type.", example = "FIRMWARE", readOnly = true) @ApiModelProperty(position = 5, value = "OTA Package type.", example = "FIRMWARE", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private OtaPackageType type; private OtaPackageType type;
@Length(fieldName = "title") @Length(fieldName = "title")
@NoXss @NoXss
@ApiModelProperty(position = 6, value = "OTA Package title.", example = "fw", readOnly = true) @ApiModelProperty(position = 6, value = "OTA Package title.", example = "fw", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String title; private String title;
@Length(fieldName = "version") @Length(fieldName = "version")
@NoXss @NoXss
@ApiModelProperty(position = 7, value = "OTA Package version.", example = "1.0", readOnly = true) @ApiModelProperty(position = 7, value = "OTA Package version.", example = "1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String version; private String version;
@Length(fieldName = "tag") @Length(fieldName = "tag")
@NoXss @NoXss
@ApiModelProperty(position = 8, value = "OTA Package tag.", example = "fw_1.0", readOnly = true) @ApiModelProperty(position = 8, value = "OTA Package tag.", example = "fw_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String tag; private String tag;
@Length(fieldName = "url") @Length(fieldName = "url")
@NoXss @NoXss
@ApiModelProperty(position = 9, value = "OTA Package url.", example = "http://thingsboard.org/fw/1", readOnly = true) @ApiModelProperty(position = 9, value = "OTA Package url.", example = "http://thingsboard.org/fw/1", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String url; private String url;
@ApiModelProperty(position = 10, value = "Indicates OTA Package 'has data'. Field is returned from DB ('true' if data exists or url is set). If OTA Package 'has data' is 'false' we can not assign the OTA Package to the Device or Device Profile.", example = "true", readOnly = true) @ApiModelProperty(position = 10, value = "Indicates OTA Package 'has data'. Field is returned from DB ('true' if data exists or url is set). If OTA Package 'has data' is 'false' we can not assign the OTA Package to the Device or Device Profile.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private boolean hasData; private boolean hasData;
@Length(fieldName = "file name") @Length(fieldName = "file name")
@NoXss @NoXss
@ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", readOnly = true) @ApiModelProperty(position = 11, value = "OTA Package file name.", example = "fw_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String fileName; private String fileName;
@NoXss @NoXss
@Length(fieldName = "contentType") @Length(fieldName = "contentType")
@ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", readOnly = true) @ApiModelProperty(position = 12, value = "OTA Package content type.", example = "APPLICATION_OCTET_STREAM", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String contentType; private String contentType;
@ApiModelProperty(position = 13, value = "OTA Package checksum algorithm.", example = "CRC32", readOnly = true) @ApiModelProperty(position = 13, value = "OTA Package checksum algorithm.", example = "CRC32", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private ChecksumAlgorithm checksumAlgorithm; private ChecksumAlgorithm checksumAlgorithm;
@Length(fieldName = "checksum", max = 1020) @Length(fieldName = "checksum", max = 1020)
@ApiModelProperty(position = 14, value = "OTA Package checksum.", example = "0xd87f7e0c", readOnly = true) @ApiModelProperty(position = 14, value = "OTA Package checksum.", example = "0xd87f7e0c", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String checksum; private String checksum;
@ApiModelProperty(position = 15, value = "OTA Package data size.", example = "8", readOnly = true) @ApiModelProperty(position = 15, value = "OTA Package data size.", example = "8", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private Long dataSize; private Long dataSize;
public OtaPackageInfo() { public OtaPackageInfo() {
@ -114,7 +114,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the ota package creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the ota package creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

2
common/data/src/main/java/org/thingsboard/server/common/data/SaveOtaPackageInfoRequest.java

@ -26,7 +26,7 @@ import lombok.NoArgsConstructor;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@NoArgsConstructor @NoArgsConstructor
public class SaveOtaPackageInfoRequest extends OtaPackageInfo { public class SaveOtaPackageInfoRequest extends OtaPackageInfo {
@ApiModelProperty(position = 16, value = "Indicates OTA Package uses url. Should be 'true' if uses url or 'false' if will be used data.", example = "true", readOnly = true) @ApiModelProperty(position = 16, value = "Indicates OTA Package uses url. Should be 'true' if uses url or 'false' if will be used data.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
boolean usesUrl; boolean usesUrl;
public SaveOtaPackageInfoRequest(OtaPackageInfo otaPackageInfo, boolean usesUrl) { public SaveOtaPackageInfoRequest(OtaPackageInfo otaPackageInfo, boolean usesUrl) {

4
common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java

File diff suppressed because one or more lines are too long

10
common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java

@ -34,19 +34,19 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
private static final long serialVersionUID = 7282664529021651736L; private static final long serialVersionUID = 7282664529021651736L;
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@NoXss @NoXss
@Length(fieldName = "title") @Length(fieldName = "title")
@ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0") @ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0")
private String title; private String title;
@ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true) @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private ResourceType resourceType; private ResourceType resourceType;
@NoXss @NoXss
@Length(fieldName = "resourceKey") @Length(fieldName = "resourceKey")
@ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", readOnly = true) @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String resourceKey; private String resourceKey;
@ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", readOnly = true) @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String searchText; private String searchText;
public TbResourceInfo() { public TbResourceInfo() {
@ -75,7 +75,7 @@ public class TbResourceInfo extends SearchTextBased<TbResourceId> implements Has
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the resource creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the resource creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

4
common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java

@ -74,7 +74,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
} }
@Override @Override
@ApiModelProperty(position = 4, value = "Name of the tenant. Read-only, duplicated from title for backward compatibility", example = "Company A", readOnly = true) @ApiModelProperty(position = 4, value = "Name of the tenant. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {
return title; return title;
@ -110,7 +110,7 @@ public class Tenant extends ContactBased<TenantId> implements HasTenantId {
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the tenant creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the tenant creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

2
common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java

@ -87,7 +87,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the tenant profile creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the tenant profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

8
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -74,13 +74,13 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the user creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the user creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();
} }
@ApiModelProperty(position = 3, value = "JSON object with the Tenant Id.", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -89,7 +89,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
} }
@ -107,7 +107,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.email = email; this.email = email;
} }
@ApiModelProperty(position = 6, readOnly = true, value = "Duplicates the email of the user, readonly", example = "user@example.com") @ApiModelProperty(position = 6, accessMode = ApiModelProperty.AccessMode.READ_ONLY, value = "Duplicates the email of the user, readonly", example = "user@example.com")
@Override @Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {

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

@ -43,10 +43,10 @@ import java.util.List;
@AllArgsConstructor @AllArgsConstructor
public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, HasCustomerId { public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, HasCustomerId {
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@ApiModelProperty(position = 4, value = "JSON object with Customer Id", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId; private CustomerId customerId;
@ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm")
@ -124,7 +124,7 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
} }
@ApiModelProperty(position = 2, value = "Timestamp of the alarm creation, in milliseconds", example = "1634058704567", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the alarm creation, in milliseconds", example = "1634058704567", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

5
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -52,7 +52,6 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
@Length(fieldName = "label") @Length(fieldName = "label")
private String label; private String label;
@ApiModelProperty(position = 100, value = "JSON object with External Id from the VCS", accessMode = ApiModelProperty.AccessMode.READ_ONLY, hidden = true)
@Getter @Setter @Getter @Setter
private AssetId externalId; private AssetId externalId;
@ -93,7 +92,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
return super.getId(); return super.getId();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();
@ -108,7 +107,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java

@ -24,9 +24,9 @@ import org.thingsboard.server.common.data.id.AssetId;
@Data @Data
public class AssetInfo extends Asset { public class AssetInfo extends Asset {
@ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", readOnly = true) @ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String customerTitle; private String customerTitle;
@ApiModelProperty(position = 10, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", readOnly = true) @ApiModelProperty(position = 10, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private boolean customerIsPublic; private boolean customerIsPublic;
public AssetInfo() { public AssetInfo() {

22
common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java

@ -28,25 +28,25 @@ import org.thingsboard.server.common.data.id.*;
@Data @Data
public class AuditLog extends BaseData<AuditLogId> { public class AuditLog extends BaseData<AuditLogId> {
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id", readOnly = true) @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private TenantId tenantId; private TenantId tenantId;
@ApiModelProperty(position = 4, value = "JSON object with Customer Id", readOnly = true) @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private CustomerId customerId; private CustomerId customerId;
@ApiModelProperty(position = 5, value = "JSON object with Entity id", readOnly = true) @ApiModelProperty(position = 5, value = "JSON object with Entity id", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private EntityId entityId; private EntityId entityId;
@ApiModelProperty(position = 6, value = "Name of the logged entity", example = "Thermometer", readOnly = true) @ApiModelProperty(position = 6, value = "Name of the logged entity", example = "Thermometer", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String entityName; private String entityName;
@ApiModelProperty(position = 7, value = "JSON object with User id.", readOnly = true) @ApiModelProperty(position = 7, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private UserId userId; private UserId userId;
@ApiModelProperty(position = 8, value = "Unique user name(email) of the user that performed some action on logged entity", example = "tenant@thingsboard.org", readOnly = true) @ApiModelProperty(position = 8, value = "Unique user name(email) of the user that performed some action on logged entity", example = "tenant@thingsboard.org", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String userName; private String userName;
@ApiModelProperty(position = 9, value = "String represented Action type", example = "ADDED", readOnly = true) @ApiModelProperty(position = 9, value = "String represented Action type", example = "ADDED", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private ActionType actionType; private ActionType actionType;
@ApiModelProperty(position = 10, value = "JsonNode represented action data", readOnly = true) @ApiModelProperty(position = 10, value = "JsonNode represented action data", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private JsonNode actionData; private JsonNode actionData;
@ApiModelProperty(position = 11, value = "String represented Action status", example = "SUCCESS", allowableValues = "SUCCESS,FAILURE", readOnly = true) @ApiModelProperty(position = 11, value = "String represented Action status", example = "SUCCESS", allowableValues = "SUCCESS,FAILURE", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private ActionStatus actionStatus; private ActionStatus actionStatus;
@ApiModelProperty(position = 12, value = "Failure action details info. An empty string in case of action status type 'SUCCESS', otherwise includes stack trace of the caused exception.", readOnly = true) @ApiModelProperty(position = 12, value = "Failure action details info. An empty string in case of action status type 'SUCCESS', otherwise includes stack trace of the caused exception.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String actionFailureDetails; private String actionFailureDetails;
public AuditLog() { public AuditLog() {
@ -71,7 +71,7 @@ public class AuditLog extends BaseData<AuditLogId> {
this.actionFailureDetails = auditLog.getActionFailureDetails(); this.actionFailureDetails = auditLog.getActionFailureDetails();
} }
@ApiModelProperty(position = 2, value = "Timestamp of the auditLog creation, in milliseconds", example = "1609459200000", readOnly = true) @ApiModelProperty(position = 2, value = "Timestamp of the auditLog creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@Override @Override
public long getCreatedTime() { public long getCreatedTime() {
return super.getCreatedTime(); return super.getCreatedTime();

24
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java

@ -25,44 +25,44 @@ public class LwM2MServerSecurityConfig {
@ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + @ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " +
"This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " + "This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. " +
"The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", readOnly = true) "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected Integer shortServerId = 123; protected Integer shortServerId = 123;
/** Security -> ObjectId = 0 'LWM2M Security' */ /** Security -> ObjectId = 0 'LWM2M Security' */
@ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " + @ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " +
"The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " + "The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " +
"The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " + "The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " +
"(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", readOnly = true) "(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected boolean bootstrapServerIs = false; protected boolean bootstrapServerIs = false;
@ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) @ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected String host; protected String host;
@ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", readOnly = true) @ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected Integer port; protected Integer port;
@ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", readOnly = true) @ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected Integer clientHoldOffTime = 1; protected Integer clientHoldOffTime = 1;
@ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + @ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" +
"+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected String serverPublicKey; protected String serverPublicKey;
@ApiModelProperty(position = 9, value = "Server Public Key for 'Security' mode (DTLS): X509. Format: base64 encoded", example = "MMIICODCCAd6gAwIBAgIUI88U1zowOdrxDK/dOV+36gJxI2MwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMCVUs\n" + @ApiModelProperty(position = 9, value = "Server Public Key for 'Security' mode (DTLS): X509. Format: base64 encoded", example = "MMIICODCCAd6gAwIBAgIUI88U1zowOdrxDK/dOV+36gJxI2MwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMCVUs\n" +
"xEjAQBgNVBAgTCUt5aXYgY2l0eTENMAsGA1UEBxMES3lpdjEUMBIGA1UEChMLVGhpbmdzYm9hcmQxFzAVBgNVBAsMDkRFVkVMT1BFUl9URVNUMRkwFwYDVQQDDBBpbnRlcm1lZGlhdGVfY2EwMB4XDTIyMDEwOTEzMDMwMFoXDTI3MDEwODEzMDMwMFowFDESMBAGA1UEAxM\n" + "xEjAQBgNVBAgTCUt5aXYgY2l0eTENMAsGA1UEBxMES3lpdjEUMBIGA1UEChMLVGhpbmdzYm9hcmQxFzAVBgNVBAsMDkRFVkVMT1BFUl9URVNUMRkwFwYDVQQDDBBpbnRlcm1lZGlhdGVfY2EwMB4XDTIyMDEwOTEzMDMwMFoXDTI3MDEwODEzMDMwMFowFDESMBAGA1UEAxM\n" +
"JbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUO3vBo/JTv0eooY7XHiKAIVDoWKFqtrU7C6q8AIKqpLcqhCdW+haFeBOH3PjY6EwaWkY04Bir4oanU0s7tz2uKOBpzCBpDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/\n" + "JbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUO3vBo/JTv0eooY7XHiKAIVDoWKFqtrU7C6q8AIKqpLcqhCdW+haFeBOH3PjY6EwaWkY04Bir4oanU0s7tz2uKOBpzCBpDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/\n" +
"BAIwADAdBgNVHQ4EFgQUEjc3Q4a0TxzP/3x3EV4fHxYUg0YwHwYDVR0jBBgwFoAUuSquGycMU6Q0SYNcbtSkSD3TfH0wLwYDVR0RBCgwJoIVbG9jYWxob3N0LmxvY2FsZG9tYWlugglsb2NhbGhvc3SCAiAtMAoGCCqGSM49BAMCA0gAMEUCIQD7dbZObyUaoDiNbX+9fUNp\n" + "BAIwADAdBgNVHQ4EFgQUEjc3Q4a0TxzP/3x3EV4fHxYUg0YwHwYDVR0jBBgwFoAUuSquGycMU6Q0SYNcbtSkSD3TfH0wLwYDVR0RBCgwJoIVbG9jYWxob3N0LmxvY2FsZG9tYWlugglsb2NhbGhvc3SCAiAtMAoGCCqGSM49BAMCA0gAMEUCIQD7dbZObyUaoDiNbX+9fUNp\n" +
"AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", readOnly = true) "AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected String serverCertificate; protected String serverCertificate;
@ApiModelProperty(position = 10, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", readOnly = true) @ApiModelProperty(position = 10, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
Integer bootstrapServerAccountTimeout = 0; Integer bootstrapServerAccountTimeout = 0;
/** Config -> ObjectId = 1 'LwM2M Server' */ /** Config -> ObjectId = 1 'LwM2M Server' */
@ApiModelProperty(position = 11, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true) @ApiModelProperty(position = 11, value = "Specify the lifetime of the registration in seconds.", example = "300", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private Integer lifetime = 300; private Integer lifetime = 300;
@ApiModelProperty(position = 12, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + @ApiModelProperty(position = 12, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " +
"If this Resource doesn’t exist, the default value is 0.", example = "1", readOnly = true) "If this Resource doesn’t exist, the default value is 0.", example = "1", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private Integer defaultMinPeriod = 1; private Integer defaultMinPeriod = 1;
/** ResourceID=6 'Notification Storing When Disabled or Offline' */ /** ResourceID=6 'Notification Storing When Disabled or Offline' */
@ApiModelProperty(position = 13, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + @ApiModelProperty(position = 13, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " +
"If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " + "If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " +
"The default value is true.", example = "true", readOnly = true) "The default value is true.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private boolean notifIfDisabled = true; private boolean notifIfDisabled = true;
@ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " + @ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " +
"If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", readOnly = true) "If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String binding = "U"; private String binding = "U";
} }

4
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java

@ -22,8 +22,8 @@ import lombok.Data;
@ApiModel @ApiModel
@Data @Data
public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig { public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig {
@ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) @ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected String securityHost; protected String securityHost;
@ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", readOnly = true) @ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
protected Integer securityPort; protected Integer securityPort;
} }

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

Loading…
Cancel
Save