Commit b02fbe860c7cf4d5541f709aca68ca6ec9589c84

Authored by vzikratyi
2 parents 91f05282 fd602dec

Merge branch 'develop/3.2' into feature/dynamic-oauth2-new-table

# Conflicts:
#	application/src/main/java/org/thingsboard/server/controller/BaseController.java
#	application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
#	application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
#	application/src/main/java/org/thingsboard/server/service/security/permission/SysAdminPermissions.java
#	common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
#	common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
#	dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
#	dao/src/test/resources/sql/hsql/drop-all-tables.sql
#	dao/src/test/resources/sql/psql/drop-all-tables.sql
#	dao/src/test/resources/sql/timescale/drop-all-tables.sql
#	ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts
Showing 74 changed files with 3142 additions and 190 deletions

Too many changes to show.

To preserve performance only 74 of 594 files are displayed.

... ... @@ -20,7 +20,7 @@
20 20 <modelVersion>4.0.0</modelVersion>
21 21 <parent>
22 22 <groupId>org.thingsboard</groupId>
23   - <version>3.1.1-SNAPSHOT</version>
  23 + <version>3.2.0-SNAPSHOT</version>
24 24 <artifactId>thingsboard</artifactId>
25 25 </parent>
26 26 <artifactId>application</artifactId>
... ... @@ -47,10 +47,6 @@
47 47
48 48 <dependencies>
49 49 <dependency>
50   - <groupId>de.ruedigermoeller</groupId>
51   - <artifactId>fst</artifactId>
52   - </dependency>
53   - <dependency>
54 50 <groupId>io.netty</groupId>
55 51 <artifactId>netty-transport-native-epoll</artifactId>
56 52 <version>${netty.version}</version>
... ...
... ... @@ -43,7 +43,8 @@
43 43 "name": "Save Client Attributes",
44 44 "debugMode": false,
45 45 "configuration": {
46   - "scope": "CLIENT_SCOPE"
  46 + "scope": "CLIENT_SCOPE",
  47 + "notifyDevice": "false"
47 48 }
48 49 },
49 50 {
... ...
... ... @@ -166,7 +166,7 @@
166 166 "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
167 167 "settingsSchema": "{}",
168 168 "dataKeySettingsSchema": "{}",
169   - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
  169 + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
170 170 }
171 171 }
172 172 ]
... ...
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": {
  4 + "description": ""
  5 + },
  6 + "name": "Device Profile Rule Chain Template",
  7 + "firstRuleNodeId": null,
  8 + "root": false,
  9 + "debugMode": false,
  10 + "configuration": null
  11 + },
  12 + "metadata": {
  13 + "firstNodeIndex": 6,
  14 + "nodes": [
  15 + {
  16 + "additionalInfo": {
  17 + "layoutX": 822,
  18 + "layoutY": 294
  19 + },
  20 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  21 + "name": "Save Timeseries",
  22 + "debugMode": false,
  23 + "configuration": {
  24 + "defaultTTL": 0
  25 + }
  26 + },
  27 + {
  28 + "additionalInfo": {
  29 + "layoutX": 824,
  30 + "layoutY": 221
  31 + },
  32 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  33 + "name": "Save Client Attributes",
  34 + "debugMode": false,
  35 + "configuration": {
  36 + "scope": "CLIENT_SCOPE"
  37 + }
  38 + },
  39 + {
  40 + "additionalInfo": {
  41 + "layoutX": 494,
  42 + "layoutY": 309
  43 + },
  44 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  45 + "name": "Message Type Switch",
  46 + "debugMode": false,
  47 + "configuration": {
  48 + "version": 0
  49 + }
  50 + },
  51 + {
  52 + "additionalInfo": {
  53 + "layoutX": 824,
  54 + "layoutY": 383
  55 + },
  56 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  57 + "name": "Log RPC from Device",
  58 + "debugMode": false,
  59 + "configuration": {
  60 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  61 + }
  62 + },
  63 + {
  64 + "additionalInfo": {
  65 + "layoutX": 823,
  66 + "layoutY": 444
  67 + },
  68 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  69 + "name": "Log Other",
  70 + "debugMode": false,
  71 + "configuration": {
  72 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  73 + }
  74 + },
  75 + {
  76 + "additionalInfo": {
  77 + "layoutX": 822,
  78 + "layoutY": 507
  79 + },
  80 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  81 + "name": "RPC Call Request",
  82 + "debugMode": false,
  83 + "configuration": {
  84 + "timeoutInSeconds": 60
  85 + }
  86 + },
  87 + {
  88 + "additionalInfo": {
  89 + "description": "",
  90 + "layoutX": 209,
  91 + "layoutY": 307
  92 + },
  93 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  94 + "name": "Device Profile Node",
  95 + "debugMode": false,
  96 + "configuration": {
  97 + "persistAlarmRulesState": false
  98 + }
  99 + }
  100 + ],
  101 + "connections": [
  102 + {
  103 + "fromIndex": 2,
  104 + "toIndex": 4,
  105 + "type": "Other"
  106 + },
  107 + {
  108 + "fromIndex": 2,
  109 + "toIndex": 1,
  110 + "type": "Post attributes"
  111 + },
  112 + {
  113 + "fromIndex": 2,
  114 + "toIndex": 0,
  115 + "type": "Post telemetry"
  116 + },
  117 + {
  118 + "fromIndex": 2,
  119 + "toIndex": 3,
  120 + "type": "RPC Request from Device"
  121 + },
  122 + {
  123 + "fromIndex": 2,
  124 + "toIndex": 5,
  125 + "type": "RPC Request to Device"
  126 + },
  127 + {
  128 + "fromIndex": 6,
  129 + "toIndex": 2,
  130 + "type": "Success"
  131 + }
  132 + ],
  133 + "ruleChainConnections": null
  134 + }
  135 +}
\ No newline at end of file
... ...
... ... @@ -31,7 +31,8 @@
31 31 "name": "Save Client Attributes",
32 32 "debugMode": false,
33 33 "configuration": {
34   - "scope": "CLIENT_SCOPE"
  34 + "scope": "CLIENT_SCOPE",
  35 + "notifyDevice": "false"
35 36 }
36 37 },
37 38 {
... ...
... ... @@ -64,6 +64,7 @@ BEGIN
64 64 AND tablename like 'ts_kv_' || '%'
65 65 AND tablename != 'ts_kv_latest'
66 66 AND tablename != 'ts_kv_dictionary'
  67 + AND tablename != 'ts_kv_indefinite'
67 68 LOOP
68 69 IF partition != partition_by_max_ttl_date THEN
69 70 IF partition_year IS NOT NULL THEN
... ...
... ... @@ -59,8 +59,8 @@ $$
59 59 DECLARE
60 60 tenant_cursor CURSOR FOR select tenant.id as tenant_id
61 61 from tenant;
62   - tenant_id_record varchar;
63   - customer_id_record varchar;
  62 + tenant_id_record uuid;
  63 + customer_id_record uuid;
64 64 tenant_ttl bigint;
65 65 customer_ttl bigint;
66 66 deleted_for_entities bigint;
... ...
  1 +--
  2 +-- Copyright © 2016-2020 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +DROP PROCEDURE IF EXISTS update_tenant_profiles;
  18 +DROP PROCEDURE IF EXISTS update_device_profiles;
  19 +
  20 +ALTER TABLE tenant ALTER COLUMN tenant_profile_id SET NOT NULL;
  21 +ALTER TABLE tenant DROP CONSTRAINT IF EXISTS fk_tenant_profile;
  22 +ALTER TABLE tenant ADD CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id);
  23 +ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_core;
  24 +ALTER TABLE tenant DROP COLUMN IF EXISTS isolated_tb_rule_engine;
  25 +
  26 +ALTER TABLE device ALTER COLUMN device_profile_id SET NOT NULL;
  27 +ALTER TABLE device DROP CONSTRAINT IF EXISTS fk_device_profile;
  28 +ALTER TABLE device ADD CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id);
... ...
  1 +--
  2 +-- Copyright © 2016-2020 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE TABLE IF NOT EXISTS device_profile (
  18 + id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
  19 + created_time bigint NOT NULL,
  20 + name varchar(255),
  21 + type varchar(255),
  22 + transport_type varchar(255),
  23 + profile_data jsonb,
  24 + description varchar,
  25 + search_text varchar(255),
  26 + is_default boolean,
  27 + tenant_id uuid,
  28 + default_rule_chain_id uuid,
  29 + CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
  30 + CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id)
  31 +);
  32 +
  33 +CREATE TABLE IF NOT EXISTS tenant_profile (
  34 + id uuid NOT NULL CONSTRAINT tenant_profile_pkey PRIMARY KEY,
  35 + created_time bigint NOT NULL,
  36 + name varchar(255),
  37 + profile_data jsonb,
  38 + description varchar,
  39 + search_text varchar(255),
  40 + is_default boolean,
  41 + isolated_tb_core boolean,
  42 + isolated_tb_rule_engine boolean,
  43 + CONSTRAINT tenant_profile_name_unq_key UNIQUE (name)
  44 +);
  45 +
  46 +CREATE OR REPLACE PROCEDURE update_tenant_profiles()
  47 + LANGUAGE plpgsql AS
  48 +$$
  49 +BEGIN
  50 + UPDATE tenant as t SET tenant_profile_id = p.id
  51 + FROM
  52 + (SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = false) as p
  53 + WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = false;
  54 +
  55 + UPDATE tenant as t SET tenant_profile_id = p.id
  56 + FROM
  57 + (SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = false) as p
  58 + WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = false;
  59 +
  60 + UPDATE tenant as t SET tenant_profile_id = p.id
  61 + FROM
  62 + (SELECT id from tenant_profile WHERE isolated_tb_core = false AND isolated_tb_rule_engine = true) as p
  63 + WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = false AND t.isolated_tb_rule_engine = true;
  64 +
  65 + UPDATE tenant as t SET tenant_profile_id = p.id
  66 + FROM
  67 + (SELECT id from tenant_profile WHERE isolated_tb_core = true AND isolated_tb_rule_engine = true) as p
  68 + WHERE t.tenant_profile_id IS NULL AND t.isolated_tb_core = true AND t.isolated_tb_rule_engine = true;
  69 +END;
  70 +$$;
  71 +
  72 +CREATE OR REPLACE PROCEDURE update_device_profiles()
  73 + LANGUAGE plpgsql AS
  74 +$$
  75 +BEGIN
  76 + UPDATE device as d SET device_profile_id = p.id, device_data = '{"configuration":{"type":"DEFAULT"}, "transportConfiguration":{"type":"DEFAULT"}}'
  77 + FROM
  78 + (SELECT id, tenant_id, name from device_profile) as p
  79 + WHERE d.device_profile_id IS NULL AND p.tenant_id = d.tenant_id AND d.type = p.name;
  80 +END;
  81 +$$;
... ...
... ... @@ -32,6 +32,7 @@ import org.springframework.data.redis.core.RedisTemplate;
32 32 import org.springframework.scheduling.annotation.Scheduled;
33 33 import org.springframework.stereotype.Component;
34 34 import org.thingsboard.rule.engine.api.MailService;
  35 +import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
35 36 import org.thingsboard.server.actors.service.ActorService;
36 37 import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
37 38 import org.thingsboard.server.common.data.DataConstants;
... ... @@ -44,7 +45,6 @@ import org.thingsboard.server.common.msg.TbMsg;
44 45 import org.thingsboard.server.common.msg.queue.ServiceType;
45 46 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
46 47 import org.thingsboard.server.common.msg.tools.TbRateLimits;
47   -import org.thingsboard.server.dao.alarm.AlarmService;
48 48 import org.thingsboard.server.dao.asset.AssetService;
49 49 import org.thingsboard.server.dao.attributes.AttributesService;
50 50 import org.thingsboard.server.dao.audit.AuditLogService;
... ... @@ -58,17 +58,20 @@ import org.thingsboard.server.dao.event.EventService;
58 58 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
59 59 import org.thingsboard.server.dao.relation.RelationService;
60 60 import org.thingsboard.server.dao.rule.RuleChainService;
  61 +import org.thingsboard.server.dao.rule.RuleNodeStateService;
  62 +import org.thingsboard.server.dao.tenant.TenantProfileService;
61 63 import org.thingsboard.server.dao.tenant.TenantService;
62 64 import org.thingsboard.server.dao.timeseries.TimeseriesService;
63 65 import org.thingsboard.server.dao.user.UserService;
64 66 import org.thingsboard.server.queue.discovery.PartitionService;
65 67 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
66 68 import org.thingsboard.server.service.component.ComponentDiscoveryService;
67   -import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
  69 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
68 70 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
69 71 import org.thingsboard.server.service.executors.ExternalCallExecutorService;
70 72 import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
71 73 import org.thingsboard.server.service.mail.MailExecutorService;
  74 +import org.thingsboard.server.service.profile.TbDeviceProfileCache;
72 75 import org.thingsboard.server.service.queue.TbClusterService;
73 76 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
74 77 import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
... ... @@ -89,7 +92,6 @@ import java.util.concurrent.ConcurrentHashMap;
89 92 import java.util.concurrent.ConcurrentMap;
90 93 import java.util.concurrent.ScheduledExecutorService;
91 94 import java.util.concurrent.TimeUnit;
92   -import java.util.concurrent.atomic.AtomicInteger;
93 95
94 96 @Slf4j
95 97 @Component
... ... @@ -127,6 +129,10 @@ public class ActorSystemContext {
127 129
128 130 @Autowired
129 131 @Getter
  132 + private TbDeviceProfileCache deviceProfileCache;
  133 +
  134 + @Autowired
  135 + @Getter
130 136 private AssetService assetService;
131 137
132 138 @Autowired
... ... @@ -139,6 +145,10 @@ public class ActorSystemContext {
139 145
140 146 @Autowired
141 147 @Getter
  148 + private TenantProfileService tenantProfileService;
  149 +
  150 + @Autowired
  151 + @Getter
142 152 private CustomerService customerService;
143 153
144 154 @Autowired
... ... @@ -150,6 +160,10 @@ public class ActorSystemContext {
150 160 private RuleChainService ruleChainService;
151 161
152 162 @Autowired
  163 + @Getter
  164 + private RuleNodeStateService ruleNodeStateService;
  165 +
  166 + @Autowired
153 167 private PartitionService partitionService;
154 168
155 169 @Autowired
... ... @@ -527,4 +541,5 @@ public class ActorSystemContext {
527 541 log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
528 542 getScheduler().schedule(() -> ctx.tell(msg), delayInMs, TimeUnit.MILLISECONDS);
529 543 }
  544 +
530 545 }
... ...
... ... @@ -27,6 +27,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
27 27 import org.thingsboard.server.actors.tenant.TenantActor;
28 28 import org.thingsboard.server.common.data.EntityType;
29 29 import org.thingsboard.server.common.data.Tenant;
  30 +import org.thingsboard.server.common.data.TenantProfile;
30 31 import org.thingsboard.server.common.data.id.EntityId;
31 32 import org.thingsboard.server.common.data.id.TenantId;
32 33 import org.thingsboard.server.common.data.page.PageDataIterable;
... ... @@ -116,7 +117,9 @@ public class AppActor extends ContextAwareActor {
116 117 boolean isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
117 118 boolean isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
118 119 for (Tenant tenant : tenantIterator) {
119   - if (isCore || (isRuleEngine && !tenant.isIsolatedTbRuleEngine())) {
  120 + // TODO: Tenant Profile from cache
  121 + TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId());
  122 + if (isCore || (isRuleEngine && !tenantProfile.isIsolatedTbRuleEngine())) {
120 123 log.debug("[{}] Creating tenant actor", tenant.getId());
121 124 getOrCreateTenantActor(tenant.getId());
122 125 log.debug("[{}] Tenant actor created.", tenant.getId());
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
28 28 import org.thingsboard.server.actors.ActorSystemContext;
29 29 import org.thingsboard.server.actors.TbActorCtx;
30 30 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
  31 +import org.thingsboard.server.common.data.DataConstants;
31 32 import org.thingsboard.server.common.data.Device;
32 33 import org.thingsboard.server.common.data.id.DeviceId;
33 34 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -79,8 +80,6 @@ import java.util.UUID;
79 80 import java.util.function.Consumer;
80 81 import java.util.stream.Collectors;
81 82
82   -import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
83   -import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
84 83
85 84 /**
86 85 * @author Andrew Shvayka
... ... @@ -279,17 +278,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
279 278 ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
280 279 ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
281 280 if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
282   - clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE);
283   - sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE);
  281 + clientAttributesFuture = findAllAttributesByScope(DataConstants.CLIENT_SCOPE);
  282 + sharedAttributesFuture = findAllAttributesByScope(DataConstants.SHARED_SCOPE);
284 283 } else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
285   - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
286   - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
  284 + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
  285 + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
287 286 } else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
288 287 clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
289   - sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
  288 + sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), DataConstants.SHARED_SCOPE);
290 289 } else {
291 290 sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
292   - clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
  291 + clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), DataConstants.CLIENT_SCOPE);
293 292 }
294 293 return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
295 294 }
... ... @@ -316,7 +315,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
316 315 AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
317 316 if (msg.isDeleted()) {
318 317 List<String> sharedKeys = msg.getDeletedKeys().stream()
319   - .filter(key -> SHARED_SCOPE.equals(key.getScope()))
  318 + .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
320 319 .map(AttributeKey::getAttributeKey)
321 320 .collect(Collectors.toList());
322 321 if (!sharedKeys.isEmpty()) {
... ... @@ -324,7 +323,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
324 323 hasNotificationData = true;
325 324 }
326 325 } else {
327   - if (SHARED_SCOPE.equals(msg.getScope())) {
  326 + if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
328 327 List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
329 328 if (attributes.size() > 0) {
330 329 List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
... ... @@ -334,7 +333,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
334 333 hasNotificationData = true;
335 334 }
336 335 } else {
337   - log.debug("[{}] No public server side attributes changed!", deviceId);
  336 + log.debug("[{}] No public shared side attributes changed!", deviceId);
338 337 }
339 338 }
340 339 }
... ...
... ... @@ -23,6 +23,7 @@ import org.springframework.data.redis.core.RedisTemplate;
23 23 import org.thingsboard.common.util.ListeningExecutor;
24 24 import org.thingsboard.rule.engine.api.MailService;
25 25 import org.thingsboard.rule.engine.api.RuleEngineAlarmService;
  26 +import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
26 27 import org.thingsboard.rule.engine.api.RuleEngineRpcService;
27 28 import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
28 29 import org.thingsboard.rule.engine.api.ScriptEngine;
... ... @@ -39,13 +40,15 @@ import org.thingsboard.server.common.data.id.EntityId;
39 40 import org.thingsboard.server.common.data.id.RuleChainId;
40 41 import org.thingsboard.server.common.data.id.RuleNodeId;
41 42 import org.thingsboard.server.common.data.id.TenantId;
  43 +import org.thingsboard.server.common.data.page.PageData;
  44 +import org.thingsboard.server.common.data.page.PageLink;
42 45 import org.thingsboard.server.common.data.rule.RuleNode;
  46 +import org.thingsboard.server.common.data.rule.RuleNodeState;
43 47 import org.thingsboard.server.common.msg.TbActorMsg;
44 48 import org.thingsboard.server.common.msg.TbMsg;
45 49 import org.thingsboard.server.common.msg.TbMsgMetaData;
46 50 import org.thingsboard.server.common.msg.queue.ServiceType;
47 51 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
48   -import org.thingsboard.server.dao.alarm.AlarmService;
49 52 import org.thingsboard.server.dao.asset.AssetService;
50 53 import org.thingsboard.server.dao.attributes.AttributesService;
51 54 import org.thingsboard.server.dao.cassandra.CassandraCluster;
... ... @@ -67,7 +70,6 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
67 70
68 71 import java.util.Collections;
69 72 import java.util.Set;
70   -import java.util.concurrent.TimeUnit;
71 73 import java.util.function.Consumer;
72 74
73 75 /**
... ... @@ -105,6 +107,7 @@ class DefaultTbContext implements TbContext {
105 107 if (nodeCtx.getSelf().isDebugMode()) {
106 108 relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
107 109 }
  110 + msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
108 111 nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
109 112 }
110 113
... ... @@ -203,6 +206,7 @@ class DefaultTbContext implements TbContext {
203 206 if (nodeCtx.getSelf().isDebugMode()) {
204 207 mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "ACK", null);
205 208 }
  209 + tbMsg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
206 210 tbMsg.getCallback().onSuccess();
207 211 }
208 212
... ... @@ -389,6 +393,11 @@ class DefaultTbContext implements TbContext {
389 393 }
390 394
391 395 @Override
  396 + public RuleEngineDeviceProfileCache getDeviceProfileCache() {
  397 + return mainCtx.getDeviceProfileCache();
  398 + }
  399 +
  400 + @Override
392 401 public EventLoopGroup getSharedEventLoop() {
393 402 return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();
394 403 }
... ... @@ -422,6 +431,30 @@ class DefaultTbContext implements TbContext {
422 431 return mainCtx.getRedisTemplate();
423 432 }
424 433
  434 + @Override
  435 + public PageData<RuleNodeState> findRuleNodeStates(PageLink pageLink) {
  436 + if (log.isDebugEnabled()) {
  437 + log.debug("[{}][{}] Fetch Rule Node States.", getTenantId(), getSelfId());
  438 + }
  439 + return mainCtx.getRuleNodeStateService().findByRuleNodeId(getTenantId(), getSelfId(), pageLink);
  440 + }
  441 +
  442 + @Override
  443 + public RuleNodeState findRuleNodeStateForEntity(EntityId entityId) {
  444 + if (log.isDebugEnabled()) {
  445 + log.debug("[{}][{}][{}] Fetch Rule Node State for entity.", getTenantId(), getSelfId(), entityId);
  446 + }
  447 + return mainCtx.getRuleNodeStateService().findByRuleNodeIdAndEntityId(getTenantId(), getSelfId(), entityId);
  448 + }
  449 +
  450 + @Override
  451 + public RuleNodeState saveRuleNodeState(RuleNodeState state) {
  452 + if (log.isDebugEnabled()) {
  453 + log.debug("[{}][{}][{}] Persist Rule Node State for entity: {}", getTenantId(), getSelfId(), state.getEntityId(), state.getStateData());
  454 + }
  455 + state.setRuleNodeId(getSelfId());
  456 + return mainCtx.getRuleNodeStateService().save(getTenantId(), state);
  457 + }
425 458
426 459 private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) {
427 460 TbMsgMetaData metaData = new TbMsgMetaData();
... ...
... ... @@ -103,7 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
103 103 }
104 104
105 105 void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
106   - msg.getMsg().getCallback().visit(info);
  106 + msg.getMsg().getCallback().onProcessingStart(info);
107 107 checkActive(msg.getMsg());
108 108 if (ruleNode.isDebugMode()) {
109 109 systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
... ...
... ... @@ -31,6 +31,7 @@ import org.thingsboard.server.actors.service.ContextBasedCreator;
31 31 import org.thingsboard.server.actors.service.DefaultActorService;
32 32 import org.thingsboard.server.common.data.EntityType;
33 33 import org.thingsboard.server.common.data.Tenant;
  34 +import org.thingsboard.server.common.data.TenantProfile;
34 35 import org.thingsboard.server.common.data.id.DeviceId;
35 36 import org.thingsboard.server.common.data.id.EntityId;
36 37 import org.thingsboard.server.common.data.id.RuleChainId;
... ... @@ -75,12 +76,16 @@ public class TenantActor extends RuleChainManagerActor {
75 76 // This Service may be started for specific tenant only.
76 77 Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
77 78
  79 + // TODO: Tenant Profile from cache
  80 +
  81 + TenantProfile tenantProfile = systemContext.getTenantProfileService().findTenantProfileById(tenantId, tenant.getTenantProfileId());
  82 +
78 83 isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
79 84 isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
80 85
81 86 if (isRuleEngineForCurrentTenant) {
82 87 try {
83   - if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenant.isIsolatedTbRuleEngine())) {
  88 + if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
84 89 log.info("[{}] Going to init rule chains", tenantId);
85 90 initRuleChains();
86 91 } else {
... ...
... ... @@ -90,7 +90,7 @@ public class AlarmController extends BaseController {
90 90 checkEntity(alarm.getId(), alarm, Resource.ALARM);
91 91
92 92 Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
93   - logEntityAction(savedAlarm.getId(), savedAlarm,
  93 + logEntityAction(savedAlarm.getOriginator(), savedAlarm,
94 94 getCurrentUser().getCustomerId(),
95 95 alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
96 96 return savedAlarm;
... ... @@ -126,7 +126,7 @@ public class AlarmController extends BaseController {
126 126 long ackTs = System.currentTimeMillis();
127 127 alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get();
128 128 alarm.setAckTs(ackTs);
129   - logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
  129 + logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
130 130 } catch (Exception e) {
131 131 throw handleException(e);
132 132 }
... ... @@ -143,7 +143,7 @@ public class AlarmController extends BaseController {
143 143 long clearTs = System.currentTimeMillis();
144 144 alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get();
145 145 alarm.setClearTs(clearTs);
146   - logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
  146 + logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
147 147 } catch (Exception e) {
148 148 throw handleException(e);
149 149 }
... ...
... ... @@ -36,6 +36,22 @@ import org.thingsboard.server.common.data.audit.ActionType;
36 36 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
37 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
38 38 import org.thingsboard.server.common.data.id.*;
  39 +import org.thingsboard.server.common.data.id.AlarmId;
  40 +import org.thingsboard.server.common.data.id.AssetId;
  41 +import org.thingsboard.server.common.data.id.CustomerId;
  42 +import org.thingsboard.server.common.data.id.DashboardId;
  43 +import org.thingsboard.server.common.data.id.DeviceId;
  44 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  45 +import org.thingsboard.server.common.data.id.EntityId;
  46 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  47 +import org.thingsboard.server.common.data.id.EntityViewId;
  48 +import org.thingsboard.server.common.data.id.RuleChainId;
  49 +import org.thingsboard.server.common.data.id.RuleNodeId;
  50 +import org.thingsboard.server.common.data.id.TenantId;
  51 +import org.thingsboard.server.common.data.id.TenantProfileId;
  52 +import org.thingsboard.server.common.data.id.UserId;
  53 +import org.thingsboard.server.common.data.id.WidgetTypeId;
  54 +import org.thingsboard.server.common.data.id.WidgetsBundleId;
39 55 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
40 56 import org.thingsboard.server.common.data.kv.DataType;
41 57 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -57,6 +73,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
57 73 import org.thingsboard.server.dao.dashboard.DashboardService;
58 74 import org.thingsboard.server.dao.device.ClaimDevicesService;
59 75 import org.thingsboard.server.dao.device.DeviceCredentialsService;
  76 +import org.thingsboard.server.dao.device.DeviceProfileService;
60 77 import org.thingsboard.server.dao.device.DeviceService;
61 78 import org.thingsboard.server.dao.entityview.EntityViewService;
62 79 import org.thingsboard.server.dao.exception.DataValidationException;
... ... @@ -66,6 +83,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
66 83 import org.thingsboard.server.dao.oauth2.OAuth2Service;
67 84 import org.thingsboard.server.dao.relation.RelationService;
68 85 import org.thingsboard.server.dao.rule.RuleChainService;
  86 +import org.thingsboard.server.dao.tenant.TenantProfileService;
69 87 import org.thingsboard.server.dao.tenant.TenantService;
70 88 import org.thingsboard.server.dao.user.UserService;
71 89 import org.thingsboard.server.dao.widget.WidgetTypeService;
... ... @@ -75,6 +93,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
75 93 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
76 94 import org.thingsboard.server.queue.util.TbCoreComponent;
77 95 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  96 +import org.thingsboard.server.service.profile.TbDeviceProfileCache;
78 97 import org.thingsboard.server.service.queue.TbClusterService;
79 98 import org.thingsboard.server.service.security.model.SecurityUser;
80 99 import org.thingsboard.server.service.security.permission.AccessControlService;
... ... @@ -112,6 +131,9 @@ public abstract class BaseController {
112 131 protected TenantService tenantService;
113 132
114 133 @Autowired
  134 + protected TenantProfileService tenantProfileService;
  135 +
  136 + @Autowired
115 137 protected CustomerService customerService;
116 138
117 139 @Autowired
... ... @@ -121,6 +143,9 @@ public abstract class BaseController {
121 143 protected DeviceService deviceService;
122 144
123 145 @Autowired
  146 + protected DeviceProfileService deviceProfileService;
  147 +
  148 + @Autowired
124 149 protected AssetService assetService;
125 150
126 151 @Autowired
... ... @@ -180,6 +205,9 @@ public abstract class BaseController {
180 205 @Autowired
181 206 protected TbQueueProducerProvider producerProvider;
182 207
  208 + @Autowired
  209 + protected TbDeviceProfileCache deviceProfileCache;
  210 +
183 211 @Value("${server.log_controller_error_stack_trace}")
184 212 @Getter
185 213 private boolean logControllerErrorStackTrace;
... ... @@ -295,6 +323,30 @@ public abstract class BaseController {
295 323 }
296 324 }
297 325
  326 + TenantInfo checkTenantInfoId(TenantId tenantId, Operation operation) throws ThingsboardException {
  327 + try {
  328 + validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
  329 + TenantInfo tenant = tenantService.findTenantInfoById(tenantId);
  330 + checkNotNull(tenant);
  331 + accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation, tenantId, tenant);
  332 + return tenant;
  333 + } catch (Exception e) {
  334 + throw handleException(e, false);
  335 + }
  336 + }
  337 +
  338 + TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException {
  339 + try {
  340 + validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId);
  341 + TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId);
  342 + checkNotNull(tenantProfile);
  343 + accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation);
  344 + return tenantProfile;
  345 + } catch (Exception e) {
  346 + throw handleException(e, false);
  347 + }
  348 + }
  349 +
298 350 protected TenantId getTenantId() throws ThingsboardException {
299 351 return getCurrentUser().getTenantId();
300 352 }
... ... @@ -343,12 +395,18 @@ public abstract class BaseController {
343 395 case DEVICE:
344 396 checkDeviceId(new DeviceId(entityId.getId()), operation);
345 397 return;
  398 + case DEVICE_PROFILE:
  399 + checkDeviceProfileId(new DeviceProfileId(entityId.getId()), operation);
  400 + return;
346 401 case CUSTOMER:
347 402 checkCustomerId(new CustomerId(entityId.getId()), operation);
348 403 return;
349 404 case TENANT:
350 405 checkTenantId(new TenantId(entityId.getId()), operation);
351 406 return;
  407 + case TENANT_PROFILE:
  408 + checkTenantProfileId(new TenantProfileId(entityId.getId()), operation);
  409 + return;
352 410 case RULE_CHAIN:
353 411 checkRuleChain(new RuleChainId(entityId.getId()), operation);
354 412 return;
... ... @@ -408,6 +466,18 @@ public abstract class BaseController {
408 466 }
409 467 }
410 468
  469 + DeviceProfile checkDeviceProfileId(DeviceProfileId deviceProfileId, Operation operation) throws ThingsboardException {
  470 + try {
  471 + validateId(deviceProfileId, "Incorrect deviceProfileId " + deviceProfileId);
  472 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(getCurrentUser().getTenantId(), deviceProfileId);
  473 + checkNotNull(deviceProfile);
  474 + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE_PROFILE, operation, deviceProfileId, deviceProfile);
  475 + return deviceProfile;
  476 + } catch (Exception e) {
  477 + throw handleException(e, false);
  478 + }
  479 + }
  480 +
411 481 protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException {
412 482 try {
413 483 validateId(entityViewId, "Incorrect entityViewId " + entityViewId);
... ...
... ... @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
47 47 import org.thingsboard.server.common.data.exception.ThingsboardException;
48 48 import org.thingsboard.server.common.data.id.CustomerId;
49 49 import org.thingsboard.server.common.data.id.DeviceId;
  50 +import org.thingsboard.server.common.data.id.DeviceProfileId;
50 51 import org.thingsboard.server.common.data.id.TenantId;
51 52 import org.thingsboard.server.common.data.page.PageData;
52 53 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -315,6 +316,7 @@ public class DeviceController extends BaseController {
315 316 @RequestParam int pageSize,
316 317 @RequestParam int page,
317 318 @RequestParam(required = false) String type,
  319 + @RequestParam(required = false) String deviceProfileId,
318 320 @RequestParam(required = false) String textSearch,
319 321 @RequestParam(required = false) String sortProperty,
320 322 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
... ... @@ -323,6 +325,9 @@ public class DeviceController extends BaseController {
323 325 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
324 326 if (type != null && type.trim().length() > 0) {
325 327 return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink));
  328 + } else if (deviceProfileId != null && deviceProfileId.length() > 0) {
  329 + DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
  330 + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId, profileId, pageLink));
326 331 } else {
327 332 return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink));
328 333 }
... ... @@ -379,6 +384,7 @@ public class DeviceController extends BaseController {
379 384 @RequestParam int pageSize,
380 385 @RequestParam int page,
381 386 @RequestParam(required = false) String type,
  387 + @RequestParam(required = false) String deviceProfileId,
382 388 @RequestParam(required = false) String textSearch,
383 389 @RequestParam(required = false) String sortProperty,
384 390 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
... ... @@ -390,6 +396,9 @@ public class DeviceController extends BaseController {
390 396 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
391 397 if (type != null && type.trim().length() > 0) {
392 398 return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
  399 + } else if (deviceProfileId != null && deviceProfileId.length() > 0) {
  400 + DeviceProfileId profileId = new DeviceProfileId(toUUID(deviceProfileId));
  401 + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId, customerId, profileId, pageLink));
393 402 } else {
394 403 return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink));
395 404 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.http.HttpStatus;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.PathVariable;
  22 +import org.springframework.web.bind.annotation.RequestBody;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.RequestParam;
  26 +import org.springframework.web.bind.annotation.ResponseBody;
  27 +import org.springframework.web.bind.annotation.ResponseStatus;
  28 +import org.springframework.web.bind.annotation.RestController;
  29 +import org.thingsboard.server.common.data.DeviceProfile;
  30 +import org.thingsboard.server.common.data.DeviceProfileInfo;
  31 +import org.thingsboard.server.common.data.EntityType;
  32 +import org.thingsboard.server.common.data.audit.ActionType;
  33 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  34 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  35 +import org.thingsboard.server.common.data.page.PageData;
  36 +import org.thingsboard.server.common.data.page.PageLink;
  37 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  38 +import org.thingsboard.server.queue.util.TbCoreComponent;
  39 +import org.thingsboard.server.service.security.permission.Operation;
  40 +import org.thingsboard.server.service.security.permission.Resource;
  41 +
  42 +@RestController
  43 +@TbCoreComponent
  44 +@RequestMapping("/api")
  45 +@Slf4j
  46 +public class DeviceProfileController extends BaseController {
  47 +
  48 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  49 + @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
  50 + @ResponseBody
  51 + public DeviceProfile getDeviceProfileById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
  52 + checkParameter("deviceProfileId", strDeviceProfileId);
  53 + try {
  54 + DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
  55 + return checkDeviceProfileId(deviceProfileId, Operation.READ);
  56 + } catch (Exception e) {
  57 + throw handleException(e);
  58 + }
  59 + }
  60 +
  61 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  62 + @RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
  63 + @ResponseBody
  64 + public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
  65 + checkParameter("deviceProfileId", strDeviceProfileId);
  66 + try {
  67 + DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
  68 + return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId));
  69 + } catch (Exception e) {
  70 + throw handleException(e);
  71 + }
  72 + }
  73 +
  74 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  75 + @RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET)
  76 + @ResponseBody
  77 + public DeviceProfileInfo getDefaultDeviceProfileInfo() throws ThingsboardException {
  78 + try {
  79 + return checkNotNull(deviceProfileService.findDefaultDeviceProfileInfo(getTenantId()));
  80 + } catch (Exception e) {
  81 + throw handleException(e);
  82 + }
  83 + }
  84 +
  85 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  86 + @RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
  87 + @ResponseBody
  88 + public DeviceProfile saveDeviceProfile(@RequestBody DeviceProfile deviceProfile) throws ThingsboardException {
  89 + try {
  90 + boolean created = deviceProfile.getId() == null;
  91 + deviceProfile.setTenantId(getTenantId());
  92 +
  93 + checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE);
  94 +
  95 + DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile));
  96 +
  97 + deviceProfileCache.put(savedDeviceProfile);
  98 + tbClusterService.onDeviceProfileChange(savedDeviceProfile, null);
  99 + tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(),
  100 + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  101 +
  102 + logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile,
  103 + null,
  104 + savedDeviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  105 +
  106 + return savedDeviceProfile;
  107 + } catch (Exception e) {
  108 + logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile,
  109 + null, deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  110 + throw handleException(e);
  111 + }
  112 + }
  113 +
  114 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  115 + @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE)
  116 + @ResponseStatus(value = HttpStatus.OK)
  117 + public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
  118 + checkParameter("deviceProfileId", strDeviceProfileId);
  119 + try {
  120 + DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
  121 + DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE);
  122 + deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId);
  123 + deviceProfileCache.evict(deviceProfileId);
  124 +
  125 + tbClusterService.onDeviceProfileDelete(deviceProfile, null);
  126 + tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED);
  127 +
  128 + logEntityAction(deviceProfileId, deviceProfile,
  129 + null,
  130 + ActionType.DELETED, null, strDeviceProfileId);
  131 +
  132 + } catch (Exception e) {
  133 + logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
  134 + null,
  135 + null,
  136 + ActionType.DELETED, e, strDeviceProfileId);
  137 + throw handleException(e);
  138 + }
  139 + }
  140 +
  141 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  142 + @RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
  143 + @ResponseBody
  144 + public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
  145 + checkParameter("deviceProfileId", strDeviceProfileId);
  146 + try {
  147 + DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
  148 + DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE);
  149 + DeviceProfile previousDefaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(getTenantId());
  150 + if (deviceProfileService.setDefaultDeviceProfile(getTenantId(), deviceProfileId)) {
  151 + if (previousDefaultDeviceProfile != null) {
  152 + previousDefaultDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), previousDefaultDeviceProfile.getId());
  153 +
  154 + logEntityAction(previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile,
  155 + null, ActionType.UPDATED, null);
  156 + }
  157 + deviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfileId);
  158 +
  159 + logEntityAction(deviceProfile.getId(), deviceProfile,
  160 + null, ActionType.UPDATED, null);
  161 + }
  162 + return deviceProfile;
  163 + } catch (Exception e) {
  164 + logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
  165 + null,
  166 + null,
  167 + ActionType.UPDATED, e, strDeviceProfileId);
  168 + throw handleException(e);
  169 + }
  170 + }
  171 +
  172 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  173 + @RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
  174 + @ResponseBody
  175 + public PageData<DeviceProfile> getDeviceProfiles(@RequestParam int pageSize,
  176 + @RequestParam int page,
  177 + @RequestParam(required = false) String textSearch,
  178 + @RequestParam(required = false) String sortProperty,
  179 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  180 + try {
  181 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  182 + return checkNotNull(deviceProfileService.findDeviceProfiles(getTenantId(), pageLink));
  183 + } catch (Exception e) {
  184 + throw handleException(e);
  185 + }
  186 + }
  187 +
  188 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  189 + @RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  190 + @ResponseBody
  191 + public PageData<DeviceProfileInfo> getDeviceProfileInfos(@RequestParam int pageSize,
  192 + @RequestParam int page,
  193 + @RequestParam(required = false) String textSearch,
  194 + @RequestParam(required = false) String sortProperty,
  195 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  196 + try {
  197 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  198 + return checkNotNull(deviceProfileService.findDeviceProfileInfos(getTenantId(), pageLink));
  199 + } catch (Exception e) {
  200 + throw handleException(e);
  201 + }
  202 + }
  203 +}
... ...
... ... @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.beans.factory.annotation.Value;
25 25 import org.springframework.http.HttpStatus;
26 26 import org.springframework.security.access.prepost.PreAuthorize;
  27 +import org.springframework.util.CollectionUtils;
27 28 import org.springframework.util.StringUtils;
28 29 import org.springframework.web.bind.annotation.PathVariable;
29 30 import org.springframework.web.bind.annotation.RequestBody;
... ... @@ -47,7 +48,10 @@ import org.thingsboard.server.common.data.id.TenantId;
47 48 import org.thingsboard.server.common.data.page.PageData;
48 49 import org.thingsboard.server.common.data.page.PageLink;
49 50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  51 +import org.thingsboard.server.common.data.rule.DefaultRuleChainCreateRequest;
50 52 import org.thingsboard.server.common.data.rule.RuleChain;
  53 +import org.thingsboard.server.common.data.rule.RuleChainData;
  54 +import org.thingsboard.server.common.data.rule.RuleChainImportResult;
51 55 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
52 56 import org.thingsboard.server.common.data.rule.RuleNode;
53 57 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -55,6 +59,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType;
55 59 import org.thingsboard.server.common.msg.TbMsgMetaData;
56 60 import org.thingsboard.server.dao.event.EventService;
57 61 import org.thingsboard.server.queue.util.TbCoreComponent;
  62 +import org.thingsboard.server.service.install.InstallScripts;
58 63 import org.thingsboard.server.service.script.JsInvokeService;
59 64 import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
60 65 import org.thingsboard.server.service.security.permission.Operation;
... ... @@ -78,6 +83,9 @@ public class RuleChainController extends BaseController {
78 83 private static final ObjectMapper objectMapper = new ObjectMapper();
79 84
80 85 @Autowired
  86 + private InstallScripts installScripts;
  87 +
  88 + @Autowired
81 89 private EventService eventService;
82 90
83 91 @Autowired
... ... @@ -147,6 +155,27 @@ public class RuleChainController extends BaseController {
147 155 }
148 156
149 157 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  158 + @RequestMapping(value = "/ruleChain/device/default", method = RequestMethod.POST)
  159 + @ResponseBody
  160 + public RuleChain saveRuleChain(@RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException {
  161 + try {
  162 + checkNotNull(request);
  163 + checkNotNull(request.getName());
  164 +
  165 + RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName());
  166 +
  167 + logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null);
  168 +
  169 + return savedRuleChain;
  170 + } catch (Exception e) {
  171 + RuleChain ruleChain = new RuleChain();
  172 + ruleChain.setName(request.getName());
  173 + logEntityAction(emptyId(EntityType.RULE_CHAIN), ruleChain, null, ActionType.ADDED, e);
  174 + throw handleException(e);
  175 + }
  176 + }
  177 +
  178 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
150 179 @RequestMapping(value = "/ruleChain/{ruleChainId}/root", method = RequestMethod.POST)
151 180 @ResponseBody
152 181 public RuleChain setRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
... ... @@ -360,6 +389,36 @@ public class RuleChainController extends BaseController {
360 389 }
361 390 }
362 391
  392 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  393 + @RequestMapping(value = "/ruleChains/export", params = {"limit"}, method = RequestMethod.GET)
  394 + @ResponseBody
  395 + public RuleChainData exportRuleChains(@RequestParam("limit") int limit) throws ThingsboardException {
  396 + try {
  397 + TenantId tenantId = getCurrentUser().getTenantId();
  398 + PageLink pageLink = new PageLink(limit);
  399 + return checkNotNull(ruleChainService.exportTenantRuleChains(tenantId, pageLink));
  400 + } catch (Exception e) {
  401 + throw handleException(e);
  402 + }
  403 + }
  404 +
  405 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  406 + @RequestMapping(value = "/ruleChains/import", method = RequestMethod.POST)
  407 + @ResponseBody
  408 + public void importRuleChains(@RequestBody RuleChainData ruleChainData, @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException {
  409 + try {
  410 + TenantId tenantId = getCurrentUser().getTenantId();
  411 + List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite);
  412 + if (!CollectionUtils.isEmpty(importResults)) {
  413 + for (RuleChainImportResult importResult : importResults) {
  414 + tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
  415 + }
  416 + }
  417 + } catch (Exception e) {
  418 + throw handleException(e);
  419 + }
  420 + }
  421 +
363 422 private String msgToOutput(TbMsg msg) throws Exception {
364 423 ObjectNode msgData = objectMapper.createObjectNode();
365 424 if (!StringUtils.isEmpty(msg.getData())) {
... ...
... ... @@ -197,19 +197,21 @@ public class TelemetryController extends BaseController {
197 197 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
198 198 @ResponseBody
199 199 public DeferredResult<ResponseEntity> getTimeseries(
200   - @PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
  200 + @PathVariable("entityType") String entityType,
  201 + @PathVariable("entityId") String entityIdStr,
201 202 @RequestParam(name = "keys") String keys,
202 203 @RequestParam(name = "startTs") Long startTs,
203 204 @RequestParam(name = "endTs") Long endTs,
204 205 @RequestParam(name = "interval", defaultValue = "0") Long interval,
205 206 @RequestParam(name = "limit", defaultValue = "100") Integer limit,
206 207 @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
  208 + @RequestParam(name= "orderBy", defaultValue = "DESC") String orderBy,
207 209 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
208 210 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
209 211 (result, tenantId, entityId) -> {
210 212 // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
211 213 Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
212   - List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
  214 + List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg, orderBy))
213 215 .collect(Collectors.toList());
214 216
215 217 Futures.addCallback(tsService.findAll(tenantId, entityId, queries), getTsKvListCallback(result, useStrictDataTypes), MoreExecutors.directExecutor());
... ...
... ... @@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
28 28 import org.springframework.web.bind.annotation.ResponseStatus;
29 29 import org.springframework.web.bind.annotation.RestController;
30 30 import org.thingsboard.server.common.data.Tenant;
  31 +import org.thingsboard.server.common.data.TenantInfo;
31 32 import org.thingsboard.server.common.data.exception.ThingsboardException;
32 33 import org.thingsboard.server.common.data.id.TenantId;
33 34 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -58,8 +59,20 @@ public class TenantController extends BaseController {
58 59 checkParameter("tenantId", strTenantId);
59 60 try {
60 61 TenantId tenantId = new TenantId(toUUID(strTenantId));
61   - checkTenantId(tenantId, Operation.READ);
62   - return checkNotNull(tenantService.findTenantById(tenantId));
  62 + return checkTenantId(tenantId, Operation.READ);
  63 + } catch (Exception e) {
  64 + throw handleException(e);
  65 + }
  66 + }
  67 +
  68 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
  69 + @RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET)
  70 + @ResponseBody
  71 + public TenantInfo getTenantInfoById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
  72 + checkParameter("tenantId", strTenantId);
  73 + try {
  74 + TenantId tenantId = new TenantId(toUUID(strTenantId));
  75 + return checkTenantInfoId(tenantId, Operation.READ);
63 76 } catch (Exception e) {
64 77 throw handleException(e);
65 78 }
... ... @@ -115,4 +128,20 @@ public class TenantController extends BaseController {
115 128 }
116 129 }
117 130
  131 + @PreAuthorize("hasAuthority('SYS_ADMIN')")
  132 + @RequestMapping(value = "/tenantInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  133 + @ResponseBody
  134 + public PageData<TenantInfo> getTenantInfos(@RequestParam int pageSize,
  135 + @RequestParam int page,
  136 + @RequestParam(required = false) String textSearch,
  137 + @RequestParam(required = false) String sortProperty,
  138 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  139 + try {
  140 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  141 + return checkNotNull(tenantService.findTenantInfos(pageLink));
  142 + } catch (Exception e) {
  143 + throw handleException(e);
  144 + }
  145 + }
  146 +
118 147 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.http.HttpStatus;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.PathVariable;
  22 +import org.springframework.web.bind.annotation.RequestBody;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.RequestParam;
  26 +import org.springframework.web.bind.annotation.ResponseBody;
  27 +import org.springframework.web.bind.annotation.ResponseStatus;
  28 +import org.springframework.web.bind.annotation.RestController;
  29 +import org.thingsboard.server.common.data.EntityInfo;
  30 +import org.thingsboard.server.common.data.TenantProfile;
  31 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  32 +import org.thingsboard.server.common.data.id.TenantProfileId;
  33 +import org.thingsboard.server.common.data.page.PageData;
  34 +import org.thingsboard.server.common.data.page.PageLink;
  35 +import org.thingsboard.server.queue.util.TbCoreComponent;
  36 +import org.thingsboard.server.service.security.permission.Operation;
  37 +import org.thingsboard.server.service.security.permission.Resource;
  38 +
  39 +@RestController
  40 +@TbCoreComponent
  41 +@RequestMapping("/api")
  42 +@Slf4j
  43 +public class TenantProfileController extends BaseController {
  44 +
  45 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  46 + @RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.GET)
  47 + @ResponseBody
  48 + public TenantProfile getTenantProfileById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
  49 + checkParameter("tenantProfileId", strTenantProfileId);
  50 + try {
  51 + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
  52 + return checkTenantProfileId(tenantProfileId, Operation.READ);
  53 + } catch (Exception e) {
  54 + throw handleException(e);
  55 + }
  56 + }
  57 +
  58 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  59 + @RequestMapping(value = "/tenantProfileInfo/{tenantProfileId}", method = RequestMethod.GET)
  60 + @ResponseBody
  61 + public EntityInfo getTenantProfileInfoById(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
  62 + checkParameter("tenantProfileId", strTenantProfileId);
  63 + try {
  64 + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
  65 + return checkNotNull(tenantProfileService.findTenantProfileInfoById(getTenantId(), tenantProfileId));
  66 + } catch (Exception e) {
  67 + throw handleException(e);
  68 + }
  69 + }
  70 +
  71 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  72 + @RequestMapping(value = "/tenantProfileInfo/default", method = RequestMethod.GET)
  73 + @ResponseBody
  74 + public EntityInfo getDefaultTenantProfileInfo() throws ThingsboardException {
  75 + try {
  76 + return checkNotNull(tenantProfileService.findDefaultTenantProfileInfo(getTenantId()));
  77 + } catch (Exception e) {
  78 + throw handleException(e);
  79 + }
  80 + }
  81 +
  82 + @PreAuthorize("hasAuthority('SYS_ADMIN')")
  83 + @RequestMapping(value = "/tenantProfile", method = RequestMethod.POST)
  84 + @ResponseBody
  85 + public TenantProfile saveTenantProfile(@RequestBody TenantProfile tenantProfile) throws ThingsboardException {
  86 + try {
  87 + boolean newTenantProfile = tenantProfile.getId() == null;
  88 + if (newTenantProfile) {
  89 + accessControlService
  90 + .checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE);
  91 + } else {
  92 + checkEntityId(tenantProfile.getId(), Operation.WRITE);
  93 + }
  94 +
  95 + tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile));
  96 + return tenantProfile;
  97 + } catch (Exception e) {
  98 + throw handleException(e);
  99 + }
  100 + }
  101 +
  102 + @PreAuthorize("hasAuthority('SYS_ADMIN')")
  103 + @RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.DELETE)
  104 + @ResponseStatus(value = HttpStatus.OK)
  105 + public void deleteTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
  106 + checkParameter("tenantProfileId", strTenantProfileId);
  107 + try {
  108 + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
  109 + checkTenantProfileId(tenantProfileId, Operation.DELETE);
  110 + tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId);
  111 + } catch (Exception e) {
  112 + throw handleException(e);
  113 + }
  114 + }
  115 +
  116 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
  117 + @RequestMapping(value = "/tenantProfile/{tenantProfileId}/default", method = RequestMethod.POST)
  118 + @ResponseBody
  119 + public TenantProfile setDefaultTenantProfile(@PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException {
  120 + checkParameter("tenantProfileId", strTenantProfileId);
  121 + try {
  122 + TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId));
  123 + TenantProfile tenantProfile = checkTenantProfileId(tenantProfileId, Operation.WRITE);
  124 + tenantProfileService.setDefaultTenantProfile(getTenantId(), tenantProfileId);
  125 + return tenantProfile;
  126 + } catch (Exception e) {
  127 + throw handleException(e);
  128 + }
  129 + }
  130 +
  131 + @PreAuthorize("hasAuthority('SYS_ADMIN')")
  132 + @RequestMapping(value = "/tenantProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
  133 + @ResponseBody
  134 + public PageData<TenantProfile> getTenantProfiles(@RequestParam int pageSize,
  135 + @RequestParam int page,
  136 + @RequestParam(required = false) String textSearch,
  137 + @RequestParam(required = false) String sortProperty,
  138 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  139 + try {
  140 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  141 + return checkNotNull(tenantProfileService.findTenantProfiles(getTenantId(), pageLink));
  142 + } catch (Exception e) {
  143 + throw handleException(e);
  144 + }
  145 + }
  146 +
  147 + @PreAuthorize("hasAuthority('SYS_ADMIN')")
  148 + @RequestMapping(value = "/tenantProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  149 + @ResponseBody
  150 + public PageData<EntityInfo> getTenantProfileInfos(@RequestParam int pageSize,
  151 + @RequestParam int page,
  152 + @RequestParam(required = false) String textSearch,
  153 + @RequestParam(required = false) String sortProperty,
  154 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  155 + try {
  156 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  157 + return checkNotNull(tenantProfileService.findTenantProfileInfos(getTenantId(), pageLink));
  158 + } catch (Exception e) {
  159 + throw handleException(e);
  160 + }
  161 + }
  162 +}
... ...
... ... @@ -175,6 +175,14 @@ public class ThingsboardInstallService {
175 175 case "3.1.0":
176 176 log.info("Upgrading ThingsBoard from version 3.1.0 to 3.1.1 ...");
177 177 databaseEntitiesUpgradeService.upgradeDatabase("3.1.0");
  178 + case "3.1.1":
  179 + log.info("Upgrading ThingsBoard from version 3.1.1 to 3.1.2 ...");
  180 + if (databaseTsUpgradeService != null) {
  181 + databaseTsUpgradeService.upgradeDatabase("3.1.1");
  182 + }
  183 + case "3.1.2":
  184 + log.info("Upgrading ThingsBoard from version 3.1.2 to 3.2.0 ...");
  185 + databaseEntitiesUpgradeService.upgradeDatabase("3.1.2");
178 186 log.info("Updating system data...");
179 187 systemDataLoaderService.updateSystemWidgets();
180 188 case "3.1.1":
... ... @@ -211,6 +219,7 @@ public class ThingsboardInstallService {
211 219 componentDiscoveryService.discoverComponents();
212 220
213 221 systemDataLoaderService.createSysAdmin();
  222 + systemDataLoaderService.createDefaultTenantProfiles();
214 223 systemDataLoaderService.createAdminSettings();
215 224 systemDataLoaderService.loadSystemWidgets();
216 225 systemDataLoaderService.createOAuth2Templates();
... ...
... ... @@ -49,6 +49,7 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
49 49 log.info("Schema updated.");
50 50 break;
51 51 case "2.5.0":
  52 + case "3.1.1":
52 53 break;
53 54 default:
54 55 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
... ...
... ... @@ -27,11 +27,15 @@ import org.thingsboard.server.common.data.AdminSettings;
27 27 import org.thingsboard.server.common.data.Customer;
28 28 import org.thingsboard.server.common.data.DataConstants;
29 29 import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.DeviceProfile;
30 31 import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.TenantProfile;
  33 +import org.thingsboard.server.common.data.TenantProfileData;
31 34 import org.thingsboard.server.common.data.User;
32 35 import org.thingsboard.server.common.data.asset.Asset;
33 36 import org.thingsboard.server.common.data.id.CustomerId;
34 37 import org.thingsboard.server.common.data.id.DeviceId;
  38 +import org.thingsboard.server.common.data.id.DeviceProfileId;
35 39 import org.thingsboard.server.common.data.id.TenantId;
36 40 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
37 41 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
... ... @@ -46,9 +50,12 @@ import org.thingsboard.server.dao.asset.AssetService;
46 50 import org.thingsboard.server.dao.attributes.AttributesService;
47 51 import org.thingsboard.server.dao.customer.CustomerService;
48 52 import org.thingsboard.server.dao.device.DeviceCredentialsService;
  53 +import org.thingsboard.server.dao.device.DeviceProfileService;
49 54 import org.thingsboard.server.dao.device.DeviceService;
  55 +import org.thingsboard.server.dao.exception.DataValidationException;
50 56 import org.thingsboard.server.dao.relation.RelationService;
51 57 import org.thingsboard.server.dao.settings.AdminSettingsService;
  58 +import org.thingsboard.server.dao.tenant.TenantProfileService;
52 59 import org.thingsboard.server.dao.tenant.TenantService;
53 60 import org.thingsboard.server.dao.user.UserService;
54 61 import org.thingsboard.server.dao.widget.WidgetsBundleService;
... ... @@ -83,6 +90,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
83 90 private TenantService tenantService;
84 91
85 92 @Autowired
  93 + private TenantProfileService tenantProfileService;
  94 +
  95 + @Autowired
86 96 private CustomerService customerService;
87 97
88 98 @Autowired
... ... @@ -95,6 +105,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
95 105 private DeviceService deviceService;
96 106
97 107 @Autowired
  108 + private DeviceProfileService deviceProfileService;
  109 +
  110 + @Autowired
98 111 private AttributesService attributesService;
99 112
100 113 @Autowired
... ... @@ -111,6 +124,50 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
111 124 }
112 125
113 126 @Override
  127 + public void createDefaultTenantProfiles() throws Exception {
  128 + tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID);
  129 +
  130 + TenantProfile isolatedTbCoreProfile = new TenantProfile();
  131 + isolatedTbCoreProfile.setDefault(false);
  132 + isolatedTbCoreProfile.setName("Isolated TB Core");
  133 + isolatedTbCoreProfile.setProfileData(new TenantProfileData());
  134 + isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile");
  135 + isolatedTbCoreProfile.setIsolatedTbCore(true);
  136 + isolatedTbCoreProfile.setIsolatedTbRuleEngine(false);
  137 + try {
  138 + tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile);
  139 + } catch (DataValidationException e) {
  140 + log.warn(e.getMessage());
  141 + }
  142 +
  143 + TenantProfile isolatedTbRuleEngineProfile = new TenantProfile();
  144 + isolatedTbRuleEngineProfile.setDefault(false);
  145 + isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine");
  146 + isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData());
  147 + isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile");
  148 + isolatedTbRuleEngineProfile.setIsolatedTbCore(false);
  149 + isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
  150 + try {
  151 + tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbRuleEngineProfile);
  152 + } catch (DataValidationException e) {
  153 + log.warn(e.getMessage());
  154 + }
  155 +
  156 + TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile();
  157 + isolatedTbCoreAndTbRuleEngineProfile.setDefault(false);
  158 + isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine");
  159 + isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData());
  160 + isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile");
  161 + isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true);
  162 + isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
  163 + try {
  164 + tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile);
  165 + } catch (DataValidationException e) {
  166 + log.warn(e.getMessage());
  167 + }
  168 + }
  169 +
  170 + @Override
114 171 public void createAdminSettings() throws Exception {
115 172 AdminSettings generalSettings = new AdminSettings();
116 173 generalSettings.setKey("general");
... ... @@ -167,16 +224,18 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
167 224 createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerB.getId(), "customerB@thingsboard.org", CUSTOMER_CRED);
168 225 createUser(Authority.CUSTOMER_USER, demoTenant.getId(), customerC.getId(), "customerC@thingsboard.org", CUSTOMER_CRED);
169 226
170   - createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A1", "A1_TEST_TOKEN", null);
171   - createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A2", "A2_TEST_TOKEN", null);
172   - createDevice(demoTenant.getId(), customerA.getId(), DEFAULT_DEVICE_TYPE, "Test Device A3", "A3_TEST_TOKEN", null);
173   - createDevice(demoTenant.getId(), customerB.getId(), DEFAULT_DEVICE_TYPE, "Test Device B1", "B1_TEST_TOKEN", null);
174   - createDevice(demoTenant.getId(), customerC.getId(), DEFAULT_DEVICE_TYPE, "Test Device C1", "C1_TEST_TOKEN", null);
  227 + DeviceProfile defaultDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), DEFAULT_DEVICE_TYPE);
  228 +
  229 + createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A1", "A1_TEST_TOKEN", null);
  230 + createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A2", "A2_TEST_TOKEN", null);
  231 + createDevice(demoTenant.getId(), customerA.getId(), defaultDeviceProfile.getId(), "Test Device A3", "A3_TEST_TOKEN", null);
  232 + createDevice(demoTenant.getId(), customerB.getId(), defaultDeviceProfile.getId(), "Test Device B1", "B1_TEST_TOKEN", null);
  233 + createDevice(demoTenant.getId(), customerC.getId(), defaultDeviceProfile.getId(), "Test Device C1", "C1_TEST_TOKEN", null);
175 234
176   - createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
  235 + createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "DHT11 Demo Device", "DHT11_DEMO_TOKEN", "Demo device that is used in sample " +
177 236 "applications that upload data from DHT11 temperature and humidity sensor");
178 237
179   - createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
  238 + createDevice(demoTenant.getId(), null, defaultDeviceProfile.getId(), "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
180 239 "Raspberry Pi GPIO control sample application");
181 240
182 241 Asset thermostatAlarms = new Asset();
... ... @@ -185,8 +244,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
185 244 thermostatAlarms.setType("AlarmPropagationAsset");
186 245 thermostatAlarms = assetService.saveAsset(thermostatAlarms);
187 246
188   - DeviceId t1Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
189   - DeviceId t2Id = createDevice(demoTenant.getId(), null, "thermostat", "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  247 + DeviceProfile thermostatDeviceProfile = this.deviceProfileService.findOrCreateDeviceProfile(demoTenant.getId(), "thermostat");
  248 +
  249 + DeviceId t1Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T1", "T1_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
  250 + DeviceId t2Id = createDevice(demoTenant.getId(), null, thermostatDeviceProfile.getId(), "Thermostat T2", "T2_TEST_TOKEN", "Demo device for Thermostats dashboard").getId();
190 251
191 252 relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t1Id, "ToAlarmPropagationAsset"));
192 253 relationService.saveRelation(thermostatAlarms.getTenantId(), new EntityRelation(thermostatAlarms.getId(), t2Id, "ToAlarmPropagationAsset"));
... ... @@ -262,14 +323,14 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
262 323
263 324 private Device createDevice(TenantId tenantId,
264 325 CustomerId customerId,
265   - String type,
  326 + DeviceProfileId deviceProfileId,
266 327 String name,
267 328 String accessToken,
268 329 String description) {
269 330 Device device = new Device();
270 331 device.setTenantId(tenantId);
271 332 device.setCustomerId(customerId);
272   - device.setType(type);
  333 + device.setDeviceProfileId(deviceProfileId);
273 334 device.setName(name);
274 335 if (description != null) {
275 336 ObjectNode additionalInfo = objectMapper.createObjectNode();
... ...
... ... @@ -59,6 +59,7 @@ public class InstallScripts {
59 59 public static final String JSON_DIR = "json";
60 60 public static final String SYSTEM_DIR = "system";
61 61 public static final String TENANT_DIR = "tenant";
  62 + public static final String DEVICE_PROFILE_DIR = "device_profile";
62 63 public static final String DEMO_DIR = "demo";
63 64 public static final String RULE_CHAINS_DIR = "rule_chains";
64 65 public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
... ... @@ -89,6 +90,10 @@ public class InstallScripts {
89 90 return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
90 91 }
91 92
  93 + public Path getDeviceProfileDefaultRuleChainTemplateFilePath() {
  94 + return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, DEVICE_PROFILE_DIR, "rule_chain_template.json");
  95 + }
  96 +
92 97 public String getDataDir() {
93 98 if (!StringUtils.isEmpty(dataDir)) {
94 99 if (!Paths.get(this.dataDir).toFile().isDirectory()) {
... ... @@ -116,15 +121,7 @@ public class InstallScripts {
116 121 dirStream.forEach(
117 122 path -> {
118 123 try {
119   - JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
120   - RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
121   - RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
122   -
123   - ruleChain.setTenantId(tenantId);
124   - ruleChain = ruleChainService.saveRuleChain(ruleChain);
125   -
126   - ruleChainMetaData.setRuleChainId(ruleChain.getId());
127   - ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
  124 + createRuleChainFromFile(tenantId, path, null);
128 125 } catch (Exception e) {
129 126 log.error("Unable to load rule chain from json: [{}]", path.toString());
130 127 throw new RuntimeException("Unable to load rule chain from json", e);
... ... @@ -134,6 +131,28 @@ public class InstallScripts {
134 131 }
135 132 }
136 133
  134 + public RuleChain createDefaultRuleChain(TenantId tenantId, String ruleChainName) throws IOException {
  135 + return createRuleChainFromFile(tenantId, getDeviceProfileDefaultRuleChainTemplateFilePath(), ruleChainName);
  136 + }
  137 +
  138 + public RuleChain createRuleChainFromFile(TenantId tenantId, Path templateFilePath, String newRuleChainName) throws IOException {
  139 + JsonNode ruleChainJson = objectMapper.readTree(templateFilePath.toFile());
  140 + RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
  141 + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
  142 +
  143 + ruleChain.setTenantId(tenantId);
  144 + if (!StringUtils.isEmpty(newRuleChainName)) {
  145 + ruleChain.setName(newRuleChainName);
  146 + }
  147 + ruleChain = ruleChainService.saveRuleChain(ruleChain);
  148 +
  149 + ruleChainMetaData.setRuleChainId(ruleChain.getId());
  150 + ruleChainService.saveRuleChainMetaData(new TenantId(EntityId.NULL_UUID), ruleChainMetaData);
  151 +
  152 + return ruleChain;
  153 + }
  154 +
  155 +
137 156 public void loadSystemWidgets() throws Exception {
138 157 Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
139 158 try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
... ...
... ... @@ -195,6 +195,14 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
195 195 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
196 196 }
197 197 break;
  198 + case "3.1.1":
  199 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  200 + log.info("Load TTL functions ...");
  201 + loadSql(conn, LOAD_TTL_FUNCTIONS_SQL);
  202 + log.info("Load Drop Partitions functions ...");
  203 + loadSql(conn, LOAD_DROP_PARTITIONS_FUNCTIONS_SQL);
  204 + }
  205 + break;
198 206 default:
199 207 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
200 208 }
... ... @@ -239,4 +247,4 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
239 247 log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage());
240 248 }
241 249 }
242   -}
\ No newline at end of file
  250 +}
... ...
... ... @@ -20,7 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.beans.factory.annotation.Value;
21 21 import org.springframework.context.annotation.Profile;
22 22 import org.springframework.stereotype.Service;
  23 +import org.thingsboard.server.common.data.EntitySubtype;
  24 +import org.thingsboard.server.common.data.Tenant;
  25 +import org.thingsboard.server.common.data.page.PageData;
  26 +import org.thingsboard.server.common.data.page.PageLink;
23 27 import org.thingsboard.server.dao.dashboard.DashboardService;
  28 +import org.thingsboard.server.dao.device.DeviceProfileService;
  29 +import org.thingsboard.server.dao.device.DeviceService;
  30 +import org.thingsboard.server.dao.tenant.TenantService;
24 31 import org.thingsboard.server.service.install.sql.SqlDbHelper;
25 32
26 33 import java.nio.charset.Charset;
... ... @@ -34,6 +41,7 @@ import java.sql.SQLException;
34 41 import java.sql.SQLSyntaxErrorException;
35 42 import java.sql.SQLWarning;
36 43 import java.sql.Statement;
  44 +import java.util.List;
37 45
38 46 import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
39 47 import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
... ... @@ -76,6 +84,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
76 84 @Autowired
77 85 private InstallScripts installScripts;
78 86
  87 + @Autowired
  88 + private SystemDataLoaderService systemDataLoaderService;
  89 +
  90 + @Autowired
  91 + private TenantService tenantService;
  92 +
  93 + @Autowired
  94 + private DeviceService deviceService;
  95 +
  96 + @Autowired
  97 + private DeviceProfileService deviceProfileService;
  98 +
  99 +
79 100 @Override
80 101 public void upgradeDatabase(String fromVersion) throws Exception {
81 102 switch (fromVersion) {
... ... @@ -303,11 +324,72 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
303 324 log.info("Schema updated.");
304 325 }
305 326 break;
306   - case "3.1.1":
  327 + case "3.1.2":
307 328 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
308 329 log.info("Updating schema ...");
309   - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update.sql");
310   - loadSql(schemaUpdateFile, conn);
  330 + if (isOldSchema(conn, 3001000)) {
  331 +
  332 + try {
  333 + conn.createStatement().execute("ALTER TABLE device ADD COLUMN device_profile_id uuid, ADD COLUMN device_data jsonb");
  334 + } catch (Exception e) {
  335 + }
  336 +
  337 + try {
  338 + conn.createStatement().execute("ALTER TABLE tenant ADD COLUMN tenant_profile_id uuid");
  339 + } catch (Exception e) {
  340 + }
  341 +
  342 + try {
  343 + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS rule_node_state (" +
  344 + " id uuid NOT NULL CONSTRAINT rule_node_state_pkey PRIMARY KEY," +
  345 + " created_time bigint NOT NULL," +
  346 + " rule_node_id uuid NOT NULL," +
  347 + " entity_type varchar(32) NOT NULL," +
  348 + " entity_id uuid NOT NULL," +
  349 + " state_data varchar(16384) NOT NULL," +
  350 + " CONSTRAINT rule_node_state_unq_key UNIQUE (rule_node_id, entity_id)," +
  351 + " CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE)");
  352 + } catch (Exception e) {
  353 + }
  354 +
  355 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.2", "schema_update_before.sql");
  356 + loadSql(schemaUpdateFile, conn);
  357 +
  358 + log.info("Creating default tenant profiles...");
  359 + systemDataLoaderService.createDefaultTenantProfiles();
  360 +
  361 + log.info("Updating tenant profiles...");
  362 + conn.createStatement().execute("call update_tenant_profiles()");
  363 +
  364 + log.info("Creating default device profiles...");
  365 + PageLink pageLink = new PageLink(100);
  366 + PageData<Tenant> pageData;
  367 + do {
  368 + pageData = tenantService.findTenants(pageLink);
  369 + for (Tenant tenant : pageData.getData()) {
  370 + List<EntitySubtype> deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get();
  371 + try {
  372 + deviceProfileService.createDefaultDeviceProfile(tenant.getId());
  373 + } catch (Exception e) {
  374 + }
  375 + for (EntitySubtype deviceType : deviceTypes) {
  376 + try {
  377 + deviceProfileService.findOrCreateDeviceProfile(tenant.getId(), deviceType.getType());
  378 + } catch (Exception e) {
  379 + }
  380 + }
  381 + }
  382 + pageLink = pageLink.nextPageLink();
  383 + } while (pageData.hasNext());
  384 +
  385 + log.info("Updating device profiles...");
  386 + conn.createStatement().execute("call update_device_profiles()");
  387 +
  388 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.2", "schema_update_after.sql");
  389 + loadSql(schemaUpdateFile, conn);
  390 +
  391 + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002000;");
  392 + }
311 393 log.info("Schema updated.");
312 394 } catch (Exception e) {
313 395 log.error("Failed updating schema!!!", e);
... ...
... ... @@ -19,6 +19,8 @@ public interface SystemDataLoaderService {
19 19
20 20 void createSysAdmin() throws Exception;
21 21
  22 + void createDefaultTenantProfiles() throws Exception;
  23 +
22 24 void createAdminSettings() throws Exception;
23 25
24 26 void createOAuth2Templates() throws Exception;
... ...
... ... @@ -177,6 +177,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
177 177 executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
178 178 }
179 179 break;
  180 + case "3.1.1":
  181 + break;
180 182 default:
181 183 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
182 184 }
... ... @@ -207,4 +209,4 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
207 209 log.info("Failed to load PostgreSQL upgrade functions due to: {}", e.getMessage());
208 210 }
209 211 }
210   -}
\ No newline at end of file
  212 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.profile;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.common.data.Device;
  21 +import org.thingsboard.server.common.data.DeviceProfile;
  22 +import org.thingsboard.server.common.data.id.DeviceId;
  23 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  24 +import org.thingsboard.server.common.data.id.TenantId;
  25 +import org.thingsboard.server.dao.device.DeviceProfileService;
  26 +import org.thingsboard.server.dao.device.DeviceService;
  27 +
  28 +import java.util.concurrent.ConcurrentHashMap;
  29 +import java.util.concurrent.ConcurrentMap;
  30 +import java.util.concurrent.locks.Lock;
  31 +import java.util.concurrent.locks.ReentrantLock;
  32 +
  33 +@Service
  34 +@Slf4j
  35 +public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
  36 +
  37 + private final Lock deviceProfileFetchLock = new ReentrantLock();
  38 + private final DeviceProfileService deviceProfileService;
  39 + private final DeviceService deviceService;
  40 +
  41 + private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfilesMap = new ConcurrentHashMap<>();
  42 + private final ConcurrentMap<DeviceId, DeviceProfileId> devicesMap = new ConcurrentHashMap<>();
  43 +
  44 + public DefaultTbDeviceProfileCache(DeviceProfileService deviceProfileService, DeviceService deviceService) {
  45 + this.deviceProfileService = deviceProfileService;
  46 + this.deviceService = deviceService;
  47 + }
  48 +
  49 + @Override
  50 + public DeviceProfile get(TenantId tenantId, DeviceProfileId deviceProfileId) {
  51 + DeviceProfile profile = deviceProfilesMap.get(deviceProfileId);
  52 + if (profile == null) {
  53 + deviceProfileFetchLock.lock();
  54 + profile = deviceProfilesMap.get(deviceProfileId);
  55 + if (profile == null) {
  56 + try {
  57 + profile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId);
  58 + if (profile != null) {
  59 + deviceProfilesMap.put(deviceProfileId, profile);
  60 + }
  61 + } finally {
  62 + deviceProfileFetchLock.unlock();
  63 + }
  64 + }
  65 + }
  66 + return profile;
  67 + }
  68 +
  69 + @Override
  70 + public DeviceProfile get(TenantId tenantId, DeviceId deviceId) {
  71 + DeviceProfileId profileId = devicesMap.get(deviceId);
  72 + if (profileId == null) {
  73 + Device device = deviceService.findDeviceById(tenantId, deviceId);
  74 + if (device != null) {
  75 + profileId = device.getDeviceProfileId();
  76 + devicesMap.put(deviceId, profileId);
  77 + }
  78 + }
  79 + return get(tenantId, profileId);
  80 + }
  81 +
  82 + @Override
  83 + public void put(DeviceProfile profile) {
  84 + if (profile.getId() != null) {
  85 + deviceProfilesMap.put(profile.getId(), profile);
  86 + }
  87 + }
  88 +
  89 + @Override
  90 + public void evict(DeviceProfileId profileId) {
  91 + deviceProfilesMap.remove(profileId);
  92 + }
  93 +
  94 + @Override
  95 + public void evict(DeviceId deviceId) {
  96 + devicesMap.remove(deviceId);
  97 + }
  98 +
  99 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.profile;
  17 +
  18 +import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
  19 +import org.thingsboard.server.common.data.DeviceProfile;
  20 +import org.thingsboard.server.common.data.id.DeviceId;
  21 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  22 +
  23 +public interface TbDeviceProfileCache extends RuleEngineDeviceProfileCache {
  24 +
  25 + void put(DeviceProfile profile);
  26 +
  27 + void evict(DeviceProfileId id);
  28 +
  29 + void evict(DeviceId id);
  30 +
  31 +}
... ...
... ... @@ -21,14 +21,20 @@ import org.springframework.beans.factory.annotation.Value;
21 21 import org.springframework.scheduling.annotation.Scheduled;
22 22 import org.springframework.stereotype.Service;
23 23 import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
  24 +import org.thingsboard.server.common.data.DeviceProfile;
24 25 import org.thingsboard.server.common.data.EntityType;
  26 +import org.thingsboard.server.common.data.id.DeviceId;
  27 +import org.thingsboard.server.common.data.id.DeviceProfileId;
25 28 import org.thingsboard.server.common.data.id.EntityId;
  29 +import org.thingsboard.server.common.data.id.RuleChainId;
26 30 import org.thingsboard.server.common.data.id.TenantId;
27 31 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
28 32 import org.thingsboard.server.common.msg.TbMsg;
29 33 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
30 34 import org.thingsboard.server.common.msg.queue.ServiceType;
31 35 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
  36 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  37 +import org.thingsboard.server.gen.transport.TransportProtos;
32 38 import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
33 39 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
34 40 import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
... ... @@ -40,7 +46,7 @@ import org.thingsboard.server.queue.TbQueueProducer;
40 46 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
41 47 import org.thingsboard.server.queue.discovery.PartitionService;
42 48 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
43   -import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
  49 +import org.thingsboard.server.service.profile.TbDeviceProfileCache;
44 50 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
45 51
46 52 import java.util.HashSet;
... ... @@ -64,11 +70,13 @@ public class DefaultTbClusterService implements TbClusterService {
64 70 private final TbQueueProducerProvider producerProvider;
65 71 private final PartitionService partitionService;
66 72 private final DataDecodingEncodingService encodingService;
  73 + private final TbDeviceProfileCache deviceProfileCache;
67 74
68   - public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService) {
  75 + public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService, TbDeviceProfileCache deviceProfileCache) {
69 76 this.producerProvider = producerProvider;
70 77 this.partitionService = partitionService;
71 78 this.encodingService = encodingService;
  79 + this.deviceProfileCache = deviceProfileCache;
72 80 }
73 81
74 82 @Override
... ... @@ -124,6 +132,12 @@ public class DefaultTbClusterService implements TbClusterService {
124 132 log.warn("[{}][{}] Received invalid message: {}", tenantId, entityId, tbMsg);
125 133 return;
126 134 }
  135 + } else {
  136 + if (entityId.getEntityType().equals(EntityType.DEVICE)) {
  137 + tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())));
  138 + } else if (entityId.getEntityType().equals(EntityType.DEVICE_PROFILE)) {
  139 + tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId())));
  140 + }
127 141 }
128 142 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
129 143 log.trace("PUSHING msg: {} to:{}", tbMsg, tpi);
... ... @@ -135,6 +149,16 @@ public class DefaultTbClusterService implements TbClusterService {
135 149 toRuleEngineMsgs.incrementAndGet();
136 150 }
137 151
  152 + private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) {
  153 + if (deviceProfile != null) {
  154 + RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId();
  155 + if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) {
  156 + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId);
  157 + }
  158 + }
  159 + return tbMsg;
  160 + }
  161 +
138 162 @Override
139 163 public void pushNotificationToRuleEngine(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) {
140 164 TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceId);
... ... @@ -163,6 +187,36 @@ public class DefaultTbClusterService implements TbClusterService {
163 187 broadcast(new ComponentLifecycleMsg(tenantId, entityId, state));
164 188 }
165 189
  190 + @Override
  191 + public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) {
  192 + log.trace("[{}][{}] Processing device profile [{}] change event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
  193 + TransportProtos.DeviceProfileUpdateMsg profileUpdateMsg = TransportProtos.DeviceProfileUpdateMsg.newBuilder()
  194 + .setData(ByteString.copyFrom(encodingService.encode(deviceProfile))).build();
  195 + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileUpdateMsg(profileUpdateMsg).build();
  196 + broadcast(transportMsg);
  197 + }
  198 +
  199 + @Override
  200 + public void onDeviceProfileDelete(DeviceProfile deviceProfile, TbQueueCallback callback) {
  201 + log.trace("[{}][{}] Processing device profile [{}] delete event", deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile.getName());
  202 + TransportProtos.DeviceProfileDeleteMsg profileDeleteMsg = TransportProtos.DeviceProfileDeleteMsg.newBuilder()
  203 + .setProfileIdMSB(deviceProfile.getId().getId().getMostSignificantBits())
  204 + .setProfileIdLSB(deviceProfile.getId().getId().getLeastSignificantBits())
  205 + .build();
  206 + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setDeviceProfileDeleteMsg(profileDeleteMsg).build();
  207 + broadcast(transportMsg);
  208 + }
  209 +
  210 + private void broadcast(ToTransportMsg transportMsg) {
  211 + TbQueueProducer<TbProtoQueueMsg<ToTransportMsg>> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer();
  212 + Set<String> tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT);
  213 + for (String transportServiceId : tbTransportServices) {
  214 + TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId);
  215 + toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null);
  216 + toTransportNfs.incrementAndGet();
  217 + }
  218 + }
  219 +
166 220 private void broadcast(ComponentLifecycleMsg msg) {
167 221 byte[] msgBytes = encodingService.encode(msg);
168 222 TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();
... ...
... ... @@ -21,10 +21,13 @@ import org.springframework.scheduling.annotation.Scheduled;
21 21 import org.springframework.stereotype.Service;
22 22 import org.thingsboard.rule.engine.api.RpcError;
23 23 import org.thingsboard.server.actors.ActorSystemContext;
  24 +import org.thingsboard.server.common.data.EntityType;
24 25 import org.thingsboard.server.common.data.alarm.Alarm;
  26 +import org.thingsboard.server.common.data.id.DeviceProfileId;
25 27 import org.thingsboard.server.common.data.id.TenantId;
26 28 import org.thingsboard.server.common.msg.MsgType;
27 29 import org.thingsboard.server.common.msg.TbActorMsg;
  30 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
28 31 import org.thingsboard.server.common.msg.queue.ServiceType;
29 32 import org.thingsboard.server.common.msg.queue.TbCallback;
30 33 import org.thingsboard.server.dao.util.mapping.JacksonUtil;
... ... @@ -47,7 +50,8 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
47 50 import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
48 51 import org.thingsboard.server.common.stats.StatsFactory;
49 52 import org.thingsboard.server.queue.util.TbCoreComponent;
50   -import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
  53 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  54 +import org.thingsboard.server.service.profile.TbDeviceProfileCache;
51 55 import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
52 56 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
53 57 import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
... ... @@ -92,8 +96,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
92 96 public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
93 97 DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService,
94 98 SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService,
95   - TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory) {
96   - super(actorContext, encodingService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
  99 + TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) {
  100 + super(actorContext, encodingService, deviceProfileCache, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer());
97 101 this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
98 102 this.stateService = stateService;
99 103 this.localSubscriptionService = localSubscriptionService;
... ... @@ -211,11 +215,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
211 215 log.trace("[{}] Forwarding message to RPC service {}", id, toCoreNotification.getFromDeviceRpcResponse());
212 216 forwardToCoreRpcService(toCoreNotification.getFromDeviceRpcResponse(), callback);
213 217 } else if (toCoreNotification.getComponentLifecycleMsg() != null && !toCoreNotification.getComponentLifecycleMsg().isEmpty()) {
214   - Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreNotification.getComponentLifecycleMsg().toByteArray());
215   - if (actorMsg.isPresent()) {
216   - log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
217   - actorContext.tellWithHighPriority(actorMsg.get());
218   - }
  218 + handleComponentLifecycleMsg(id, toCoreNotification.getComponentLifecycleMsg());
219 219 callback.onSuccess();
220 220 }
221 221 if (statsEnabled) {
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.service.queue;
17 17
  18 +import com.google.protobuf.ByteString;
18 19 import com.google.protobuf.ProtocolStringList;
19 20 import lombok.extern.slf4j.Slf4j;
20 21 import org.springframework.beans.factory.annotation.Value;
... ... @@ -37,7 +38,8 @@ import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory;
37 38 import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
38 39 import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration;
39 40 import org.thingsboard.server.queue.util.TbRuleEngineComponent;
40   -import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
  41 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  42 +import org.thingsboard.server.service.profile.TbDeviceProfileCache;
41 43 import org.thingsboard.server.service.queue.processing.*;
42 44 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
43 45 import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
... ... @@ -80,8 +82,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
80 82 TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService,
81 83 ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
82 84 TbRuleEngineDeviceRpcService tbDeviceRpcService,
83   - StatsFactory statsFactory) {
84   - super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
  85 + StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) {
  86 + super(actorContext, encodingService, deviceProfileCache, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
85 87 this.statisticsService = statisticsService;
86 88 this.ruleEngineSettings = ruleEngineSettings;
87 89 this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
... ... @@ -144,7 +146,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
144 146 submitStrategy.init(msgs);
145 147
146 148 while (!stopped) {
147   - TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy);
  149 + TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(configuration.getName(), submitStrategy);
148 150 submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> {
149 151 log.trace("[{}] Creating callback for message: {}", id, msg.getValue());
150 152 ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
... ... @@ -175,6 +177,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
175 177 if (!ctx.getFailedMap().isEmpty()) {
176 178 printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed");
177 179 }
  180 + ctx.printProfilerStats();
  181 +
178 182 TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
179 183 if (statsEnabled) {
180 184 stats.log(result, decision.isCommit());
... ... @@ -237,11 +241,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
237 241 protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) throws Exception {
238 242 ToRuleEngineNotificationMsg nfMsg = msg.getValue();
239 243 if (nfMsg.getComponentLifecycleMsg() != null && !nfMsg.getComponentLifecycleMsg().isEmpty()) {
240   - Optional<TbActorMsg> actorMsg = encodingService.decode(nfMsg.getComponentLifecycleMsg().toByteArray());
241   - if (actorMsg.isPresent()) {
242   - log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
243   - actorContext.tellWithHighPriority(actorMsg.get());
244   - }
  244 + handleComponentLifecycleMsg(id, nfMsg.getComponentLifecycleMsg());
245 245 callback.onSuccess();
246 246 } else if (nfMsg.hasFromDeviceRpcResponse()) {
247 247 TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse();
... ...
... ... @@ -19,7 +19,9 @@ import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
20 20 import org.springframework.stereotype.Service;
21 21 import org.thingsboard.server.common.data.Tenant;
  22 +import org.thingsboard.server.common.data.TenantProfile;
22 23 import org.thingsboard.server.common.data.id.TenantId;
  24 +import org.thingsboard.server.dao.tenant.TenantProfileService;
23 25 import org.thingsboard.server.dao.tenant.TenantService;
24 26 import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
25 27 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
... ... @@ -31,15 +33,20 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService
31 33
32 34 private final TenantService tenantService;
33 35
34   - public DefaultTenantRoutingInfoService(TenantService tenantService) {
  36 + private final TenantProfileService tenantProfileService;
  37 +
  38 + public DefaultTenantRoutingInfoService(TenantService tenantService, TenantProfileService tenantProfileService) {
35 39 this.tenantService = tenantService;
  40 + this.tenantProfileService = tenantProfileService;
36 41 }
37 42
38 43 @Override
39 44 public TenantRoutingInfo getRoutingInfo(TenantId tenantId) {
40 45 Tenant tenant = tenantService.findTenantById(tenantId);
41 46 if (tenant != null) {
42   - return new TenantRoutingInfo(tenantId, tenant.isIsolatedTbCore(), tenant.isIsolatedTbRuleEngine());
  47 + // TODO: Tenant Profile from cache
  48 + TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
  49 + return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbCore(), tenantProfile.isIsolatedTbRuleEngine());
43 50 } else {
44 51 throw new RuntimeException("Tenant not found!");
45 52 }
... ...
... ... @@ -16,6 +16,8 @@
16 16 package org.thingsboard.server.service.queue;
17 17
18 18 import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
  19 +import org.thingsboard.server.common.data.DeviceProfile;
  20 +import org.thingsboard.server.common.data.id.DeviceProfileId;
19 21 import org.thingsboard.server.common.data.id.EntityId;
20 22 import org.thingsboard.server.common.data.id.TenantId;
21 23 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
... ... @@ -49,4 +51,7 @@ public interface TbClusterService {
49 51
50 52 void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state);
51 53
  54 + void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback);
  55 +
  56 + void onDeviceProfileDelete(DeviceProfile deviceProfileId, TbQueueCallback callback);
52 57 }
... ...
... ... @@ -67,8 +67,14 @@ public class TbMsgPackCallback implements TbMsgCallback {
67 67 }
68 68
69 69 @Override
70   - public void visit(RuleNodeInfo ruleNodeInfo) {
71   - log.trace("[{}] ON PROCESS: {}", id, ruleNodeInfo);
72   - ctx.visit(id, ruleNodeInfo);
  70 + public void onProcessingStart(RuleNodeInfo ruleNodeInfo) {
  71 + log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo);
  72 + ctx.onProcessingStart(id, ruleNodeInfo);
  73 + }
  74 +
  75 + @Override
  76 + public void onProcessingEnd(RuleNodeId ruleNodeId) {
  77 + log.trace("[{}] ON PROCESSING END: {}", id, ruleNodeId);
  78 + ctx.onProcessingEnd(id, ruleNodeId);
73 79 }
74 80 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.service.queue;
17 17
18 18 import lombok.Getter;
  19 +import lombok.extern.slf4j.Slf4j;
19 20 import org.thingsboard.server.common.data.id.RuleNodeId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.msg.queue.RuleEngineException;
... ... @@ -24,6 +25,8 @@ import org.thingsboard.server.gen.transport.TransportProtos;
24 25 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
25 26 import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
26 27
  28 +import java.util.Comparator;
  29 +import java.util.Map;
27 30 import java.util.UUID;
28 31 import java.util.concurrent.ConcurrentHashMap;
29 32 import java.util.concurrent.ConcurrentMap;
... ... @@ -31,9 +34,13 @@ import java.util.concurrent.CountDownLatch;
31 34 import java.util.concurrent.TimeUnit;
32 35 import java.util.concurrent.atomic.AtomicInteger;
33 36
  37 +@Slf4j
34 38 public class TbMsgPackProcessingContext {
35 39
  40 + private final String queueName;
36 41 private final TbRuleEngineSubmitStrategy submitStrategy;
  42 + @Getter
  43 + private final boolean profilerEnabled;
37 44 private final AtomicInteger pendingCount;
38 45 private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
39 46 @Getter
... ... @@ -47,14 +54,20 @@ public class TbMsgPackProcessingContext {
47 54
48 55 private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
49 56
50   - public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
  57 + public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) {
  58 + this.queueName = queueName;
51 59 this.submitStrategy = submitStrategy;
  60 + this.profilerEnabled = log.isDebugEnabled();
52 61 this.pendingMap = submitStrategy.getPendingMap();
53 62 this.pendingCount = new AtomicInteger(pendingMap.size());
54 63 }
55 64
56 65 public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException {
57   - return processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
  66 + boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
  67 + if (!success && profilerEnabled) {
  68 + msgProfilerMap.values().forEach(this::onTimeout);
  69 + }
  70 + return success;
58 71 }
59 72
60 73 public void onSuccess(UUID id) {
... ... @@ -85,12 +98,53 @@ public class TbMsgPackProcessingContext {
85 98 }
86 99 }
87 100
88   - public void visit(UUID id, RuleNodeInfo ruleNodeInfo) {
  101 + private final ConcurrentHashMap<UUID, TbMsgProfilerInfo> msgProfilerMap = new ConcurrentHashMap<>();
  102 + private final ConcurrentHashMap<UUID, TbRuleNodeProfilerInfo> ruleNodeProfilerMap = new ConcurrentHashMap<>();
  103 +
  104 + public void onProcessingStart(UUID id, RuleNodeInfo ruleNodeInfo) {
89 105 lastRuleNodeMap.put(id, ruleNodeInfo);
  106 + if (profilerEnabled) {
  107 + msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onStart(ruleNodeInfo.getRuleNodeId());
  108 + ruleNodeProfilerMap.putIfAbsent(ruleNodeInfo.getRuleNodeId().getId(), new TbRuleNodeProfilerInfo(ruleNodeInfo));
  109 + }
  110 + }
  111 +
  112 + public void onProcessingEnd(UUID id, RuleNodeId ruleNodeId) {
  113 + if (profilerEnabled) {
  114 + long processingTime = msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onEnd(ruleNodeId);
  115 + if (processingTime > 0) {
  116 + ruleNodeProfilerMap.computeIfAbsent(ruleNodeId.getId(), TbRuleNodeProfilerInfo::new).record(processingTime);
  117 + }
  118 + }
  119 + }
  120 +
  121 + public void onTimeout(TbMsgProfilerInfo profilerInfo) {
  122 + Map.Entry<UUID, Long> ruleNodeInfo = profilerInfo.onTimeout();
  123 + if (ruleNodeInfo != null) {
  124 + ruleNodeProfilerMap.computeIfAbsent(ruleNodeInfo.getKey(), TbRuleNodeProfilerInfo::new).record(ruleNodeInfo.getValue());
  125 + }
90 126 }
91 127
92 128 public RuleNodeInfo getLastVisitedRuleNode(UUID id) {
93 129 return lastRuleNodeMap.get(id);
94 130 }
95 131
  132 + public void printProfilerStats() {
  133 + if (profilerEnabled) {
  134 + log.debug("Top Rule Nodes by max execution time:");
  135 + ruleNodeProfilerMap.values().stream()
  136 + .sorted(Comparator.comparingLong(TbRuleNodeProfilerInfo::getMaxExecutionTime).reversed()).limit(5)
  137 + .forEach(info -> log.debug("[{}][{}] max execution time: {}. {}", queueName, info.getRuleNodeId(), info.getMaxExecutionTime(), info.getLabel()));
  138 +
  139 + log.info("Top Rule Nodes by avg execution time:");
  140 + ruleNodeProfilerMap.values().stream()
  141 + .sorted(Comparator.comparingDouble(TbRuleNodeProfilerInfo::getAvgExecutionTime).reversed()).limit(5)
  142 + .forEach(info -> log.info("[{}][{}] avg execution time: {}. {}", queueName, info.getRuleNodeId(), info.getAvgExecutionTime(), info.getLabel()));
  143 +
  144 + log.info("Top Rule Nodes by execution count:");
  145 + ruleNodeProfilerMap.values().stream()
  146 + .sorted(Comparator.comparingInt(TbRuleNodeProfilerInfo::getExecutionCount).reversed()).limit(5)
  147 + .forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel()));
  148 + }
  149 + }
96 150 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.queue;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
  20 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
  21 +
  22 +import java.util.AbstractMap;
  23 +import java.util.Map;
  24 +import java.util.UUID;
  25 +import java.util.concurrent.atomic.AtomicLong;
  26 +import java.util.concurrent.locks.Lock;
  27 +import java.util.concurrent.locks.ReentrantLock;
  28 +
  29 +@Slf4j
  30 +public class TbMsgProfilerInfo {
  31 + private final UUID msgId;
  32 + private AtomicLong totalProcessingTime = new AtomicLong();
  33 + private Lock stateLock = new ReentrantLock();
  34 + private RuleNodeId currentRuleNodeId;
  35 + private long stateChangeTime;
  36 +
  37 + public TbMsgProfilerInfo(UUID msgId) {
  38 + this.msgId = msgId;
  39 + }
  40 +
  41 + public void onStart(RuleNodeId ruleNodeId) {
  42 + long currentTime = System.currentTimeMillis();
  43 + stateLock.lock();
  44 + try {
  45 + currentRuleNodeId = ruleNodeId;
  46 + stateChangeTime = currentTime;
  47 + } finally {
  48 + stateLock.unlock();
  49 + }
  50 + }
  51 +
  52 + public long onEnd(RuleNodeId ruleNodeId) {
  53 + long currentTime = System.currentTimeMillis();
  54 + stateLock.lock();
  55 + try {
  56 + if (ruleNodeId.equals(currentRuleNodeId)) {
  57 + long processingTime = currentTime - stateChangeTime;
  58 + stateChangeTime = currentTime;
  59 + totalProcessingTime.addAndGet(processingTime);
  60 + currentRuleNodeId = null;
  61 + return processingTime;
  62 + } else {
  63 + log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId);
  64 + return 0;
  65 + }
  66 + } finally {
  67 + stateLock.unlock();
  68 + }
  69 + }
  70 +
  71 + public Map.Entry<UUID, Long> onTimeout() {
  72 + long currentTime = System.currentTimeMillis();
  73 + stateLock.lock();
  74 + try {
  75 + if (currentRuleNodeId != null && stateChangeTime > 0) {
  76 + long timeoutTime = currentTime - stateChangeTime;
  77 + totalProcessingTime.addAndGet(timeoutTime);
  78 + return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime);
  79 + }
  80 + } finally {
  81 + stateLock.unlock();
  82 + }
  83 + return null;
  84 + }
  85 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.queue;
  17 +
  18 +import lombok.Getter;
  19 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
  20 +
  21 +import java.util.UUID;
  22 +import java.util.concurrent.atomic.AtomicInteger;
  23 +import java.util.concurrent.atomic.AtomicLong;
  24 +
  25 +public class TbRuleNodeProfilerInfo {
  26 + @Getter
  27 + private final UUID ruleNodeId;
  28 + @Getter
  29 + private final String label;
  30 + private AtomicInteger executionCount = new AtomicInteger(0);
  31 + private AtomicLong executionTime = new AtomicLong(0);
  32 + private AtomicLong maxExecutionTime = new AtomicLong(0);
  33 +
  34 + public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) {
  35 + this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId();
  36 + this.label = ruleNodeInfo.toString();
  37 + }
  38 +
  39 + public TbRuleNodeProfilerInfo(UUID ruleNodeId) {
  40 + this.ruleNodeId = ruleNodeId;
  41 + this.label = "";
  42 + }
  43 +
  44 + public void record(long processingTime) {
  45 + executionCount.incrementAndGet();
  46 + executionTime.addAndGet(processingTime);
  47 + while (true) {
  48 + long value = maxExecutionTime.get();
  49 + if (value >= processingTime) {
  50 + break;
  51 + }
  52 + if (maxExecutionTime.compareAndSet(value, processingTime)) {
  53 + break;
  54 + }
  55 + }
  56 + }
  57 +
  58 + int getExecutionCount() {
  59 + return executionCount.get();
  60 + }
  61 +
  62 + long getMaxExecutionTime() {
  63 + return maxExecutionTime.get();
  64 + }
  65 +
  66 + double getAvgExecutionTime() {
  67 + double executionCnt = (double) executionCount.get();
  68 + if (executionCnt > 0) {
  69 + return executionTime.get() / executionCnt;
  70 + } else {
  71 + return 0.0;
  72 + }
  73 + }
  74 +
  75 +}
\ No newline at end of file
... ...
... ... @@ -15,23 +15,31 @@
15 15 */
16 16 package org.thingsboard.server.service.queue.processing;
17 17
  18 +import com.google.protobuf.ByteString;
18 19 import lombok.extern.slf4j.Slf4j;
19 20 import org.springframework.boot.context.event.ApplicationReadyEvent;
20 21 import org.springframework.context.ApplicationListener;
21 22 import org.springframework.context.event.EventListener;
22 23 import org.thingsboard.common.util.ThingsBoardThreadFactory;
23 24 import org.thingsboard.server.actors.ActorSystemContext;
  25 +import org.thingsboard.server.common.data.EntityType;
  26 +import org.thingsboard.server.common.data.id.DeviceId;
  27 +import org.thingsboard.server.common.data.id.DeviceProfileId;
  28 +import org.thingsboard.server.common.msg.TbActorMsg;
  29 +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
24 30 import org.thingsboard.server.common.msg.queue.ServiceType;
25 31 import org.thingsboard.server.common.msg.queue.TbCallback;
26 32 import org.thingsboard.server.queue.TbQueueConsumer;
27 33 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
28 34 import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
29   -import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
  35 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  36 +import org.thingsboard.server.service.profile.TbDeviceProfileCache;
30 37 import org.thingsboard.server.service.queue.TbPackCallback;
31 38 import org.thingsboard.server.service.queue.TbPackProcessingContext;
32 39
33 40 import javax.annotation.PreDestroy;
34 41 import java.util.List;
  42 +import java.util.Optional;
35 43 import java.util.UUID;
36 44 import java.util.concurrent.ConcurrentHashMap;
37 45 import java.util.concurrent.ConcurrentMap;
... ... @@ -51,12 +59,15 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
51 59
52 60 protected final ActorSystemContext actorContext;
53 61 protected final DataDecodingEncodingService encodingService;
  62 + protected final TbDeviceProfileCache deviceProfileCache;
54 63
55 64 protected final TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer;
56 65
57   - public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
  66 + public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
  67 + TbDeviceProfileCache deviceProfileCache, TbQueueConsumer<TbProtoQueueMsg<N>> nfConsumer) {
58 68 this.actorContext = actorContext;
59 69 this.encodingService = encodingService;
  70 + this.deviceProfileCache = deviceProfileCache;
60 71 this.nfConsumer = nfConsumer;
61 72 }
62 73
... ... @@ -126,18 +137,32 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
126 137 });
127 138 }
128 139
  140 + protected void handleComponentLifecycleMsg(UUID id, ByteString nfMsg) {
  141 + Optional<TbActorMsg> actorMsgOpt = encodingService.decode(nfMsg.toByteArray());
  142 + if (actorMsgOpt.isPresent()) {
  143 + TbActorMsg actorMsg = actorMsgOpt.get();
  144 + if (actorMsg instanceof ComponentLifecycleMsg) {
  145 + ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
  146 + if (EntityType.DEVICE_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
  147 + deviceProfileCache.evict(new DeviceProfileId(componentLifecycleMsg.getEntityId().getId()));
  148 + } else if (EntityType.DEVICE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
  149 + deviceProfileCache.evict(new DeviceId(componentLifecycleMsg.getEntityId().getId()));
  150 + }
  151 + }
  152 + log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);
  153 + actorContext.tellWithHighPriority(actorMsg);
  154 + }
  155 + }
  156 +
129 157 protected abstract void handleNotification(UUID id, TbProtoQueueMsg<N> msg, TbCallback callback) throws Exception;
130 158
131 159 @PreDestroy
132 160 public void destroy() {
133 161 stopped = true;
134   -
135 162 stopMainConsumers();
136   -
137 163 if (nfConsumer != null) {
138 164 nfConsumer.unsubscribe();
139 165 }
140   -
141 166 if (consumersExecutor != null) {
142 167 consumersExecutor.shutdownNow();
143 168 }
... ...
... ... @@ -68,18 +68,20 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS
68 68 int listSize = orderedMsgList.size();
69 69 int startIdx = Math.min(packIdx.get() * batchSize, listSize);
70 70 int endIdx = Math.min(startIdx + batchSize, listSize);
  71 + Map<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> tmpPack;
71 72 synchronized (pendingPack) {
72 73 pendingPack.clear();
73 74 for (int i = startIdx; i < endIdx; i++) {
74 75 IdMsgPair pair = orderedMsgList.get(i);
75 76 pendingPack.put(pair.uuid, pair.msg);
76 77 }
  78 + tmpPack = new LinkedHashMap<>(pendingPack);
77 79 }
78 80 int submitSize = pendingPack.size();
79 81 if (log.isDebugEnabled() && submitSize > 0) {
80 82 log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize);
81 83 }
82   - pendingPack.forEach(msgConsumer);
  84 + tmpPack.forEach(msgConsumer);
83 85 }
84 86
85 87 }
... ...
... ... @@ -56,7 +56,9 @@ public class TbRuleEngineProcessingStrategyFactory {
56 56 private final boolean retryTimeout;
57 57 private final int maxRetries;
58 58 private final double maxAllowedFailurePercentage;
59   - private final long pauseBetweenRetries;
  59 + private final long maxPauseBetweenRetries;
  60 +
  61 + private long pauseBetweenRetries;
60 62
61 63 private int initialTotalCount;
62 64 private int retryCount;
... ... @@ -69,6 +71,7 @@ public class TbRuleEngineProcessingStrategyFactory {
69 71 this.maxRetries = configuration.getRetries();
70 72 this.maxAllowedFailurePercentage = configuration.getFailurePercentage();
71 73 this.pauseBetweenRetries = configuration.getPauseBetweenRetries();
  74 + this.maxPauseBetweenRetries = configuration.getMaxPauseBetweenRetries();
72 75 }
73 76
74 77 @Override
... ... @@ -108,6 +111,9 @@ public class TbRuleEngineProcessingStrategyFactory {
108 111 } catch (InterruptedException e) {
109 112 throw new RuntimeException(e);
110 113 }
  114 + if (maxPauseBetweenRetries > pauseBetweenRetries) {
  115 + pauseBetweenRetries = Math.min(maxPauseBetweenRetries, pauseBetweenRetries * 2);
  116 + }
111 117 }
112 118 return new TbRuleEngineProcessingDecision(false, toReprocess);
113 119 }
... ...
... ... @@ -27,6 +27,7 @@ import org.springframework.web.context.request.async.DeferredResult;
27 27 import org.thingsboard.common.util.ThingsBoardThreadFactory;
28 28 import org.thingsboard.server.common.data.Customer;
29 29 import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.DeviceProfile;
30 31 import org.thingsboard.server.common.data.EntityView;
31 32 import org.thingsboard.server.common.data.Tenant;
32 33 import org.thingsboard.server.common.data.User;
... ... @@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
35 36 import org.thingsboard.server.common.data.id.AssetId;
36 37 import org.thingsboard.server.common.data.id.CustomerId;
37 38 import org.thingsboard.server.common.data.id.DeviceId;
  39 +import org.thingsboard.server.common.data.id.DeviceProfileId;
38 40 import org.thingsboard.server.common.data.id.EntityId;
39 41 import org.thingsboard.server.common.data.id.EntityIdFactory;
40 42 import org.thingsboard.server.common.data.id.EntityViewId;
... ... @@ -48,6 +50,7 @@ import org.thingsboard.server.controller.HttpValidationCallback;
48 50 import org.thingsboard.server.dao.alarm.AlarmService;
49 51 import org.thingsboard.server.dao.asset.AssetService;
50 52 import org.thingsboard.server.dao.customer.CustomerService;
  53 +import org.thingsboard.server.dao.device.DeviceProfileService;
51 54 import org.thingsboard.server.dao.device.DeviceService;
52 55 import org.thingsboard.server.dao.entityview.EntityViewService;
53 56 import org.thingsboard.server.dao.rule.RuleChainService;
... ... @@ -72,6 +75,7 @@ import java.util.function.BiConsumer;
72 75 @Component
73 76 public class AccessValidator {
74 77
  78 + public static final String ONLY_SYSTEM_ADMINISTRATOR_IS_ALLOWED_TO_PERFORM_THIS_OPERATION = "Only system administrator is allowed to perform this operation!";
75 79 public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!";
76 80 public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
77 81 public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
... ... @@ -90,6 +94,9 @@ public class AccessValidator {
90 94 protected DeviceService deviceService;
91 95
92 96 @Autowired
  97 + protected DeviceProfileService deviceProfileService;
  98 +
  99 + @Autowired
93 100 protected AssetService assetService;
94 101
95 102 @Autowired
... ... @@ -162,6 +169,9 @@ public class AccessValidator {
162 169 case DEVICE:
163 170 validateDevice(currentUser, operation, entityId, callback);
164 171 return;
  172 + case DEVICE_PROFILE:
  173 + validateDeviceProfile(currentUser, operation, entityId, callback);
  174 + return;
165 175 case ASSET:
166 176 validateAsset(currentUser, operation, entityId, callback);
167 177 return;
... ... @@ -174,6 +184,9 @@ public class AccessValidator {
174 184 case TENANT:
175 185 validateTenant(currentUser, operation, entityId, callback);
176 186 return;
  187 + case TENANT_PROFILE:
  188 + validateTenantProfile(currentUser, operation, entityId, callback);
  189 + return;
177 190 case USER:
178 191 validateUser(currentUser, operation, entityId, callback);
179 192 return;
... ... @@ -206,6 +219,24 @@ public class AccessValidator {
206 219 }
207 220 }
208 221
  222 + private void validateDeviceProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
  223 + if (currentUser.isSystemAdmin()) {
  224 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  225 + } else {
  226 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(currentUser.getTenantId(), new DeviceProfileId(entityId.getId()));
  227 + if (deviceProfile == null) {
  228 + callback.onSuccess(ValidationResult.entityNotFound("Device profile with requested id wasn't found!"));
  229 + } else {
  230 + try {
  231 + accessControlService.checkPermission(currentUser, Resource.DEVICE_PROFILE, operation, entityId, deviceProfile);
  232 + } catch (ThingsboardException e) {
  233 + callback.onSuccess(ValidationResult.accessDenied(e.getMessage()));
  234 + }
  235 + callback.onSuccess(ValidationResult.ok(deviceProfile));
  236 + }
  237 + }
  238 + }
  239 +
209 240 private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
210 241 if (currentUser.isSystemAdmin()) {
211 242 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
... ... @@ -313,6 +344,14 @@ public class AccessValidator {
313 344 }
314 345 }
315 346
  347 + private void validateTenantProfile(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
  348 + if (currentUser.isSystemAdmin()) {
  349 + callback.onSuccess(ValidationResult.ok(null));
  350 + } else {
  351 + callback.onSuccess(ValidationResult.accessDenied(ONLY_SYSTEM_ADMINISTRATOR_IS_ALLOWED_TO_PERFORM_THIS_OPERATION));
  352 + }
  353 + }
  354 +
316 355 private void validateUser(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
317 356 ListenableFuture<User> userFuture = userService.findUserByIdAsync(currentUser.getTenantId(), new UserId(entityId.getId()));
318 357 Futures.addCallback(userFuture, getCallback(callback, user -> {
... ...
... ... @@ -34,7 +34,9 @@ public enum Resource {
34 34 WIDGET_TYPE(EntityType.WIDGET_TYPE),
35 35 OAUTH2_CONFIGURATION_INFO(EntityType.OAUTH2_CLIENT_REGISTRATION_INFO),
36 36 OAUTH2_CONFIGURATION_TEMPLATE(EntityType.OAUTH2_CLIENT_REGISTRATION_TEMPLATE),
37   - ;
  37 + WIDGET_TYPE(EntityType.WIDGET_TYPE),
  38 + TENANT_PROFILE(EntityType.TENANT_PROFILE),
  39 + DEVICE_PROFILE(EntityType.DEVICE_PROFILE);
38 40
39 41 private final EntityType entityType;
40 42
... ...
... ... @@ -37,6 +37,7 @@ public class SysAdminPermissions extends AbstractPermissions {
37 37 put(Resource.WIDGET_TYPE, systemEntityPermissionChecker);
38 38 put(Resource.OAUTH2_CONFIGURATION_INFO, PermissionChecker.allowAllPermissionChecker);
39 39 put(Resource.OAUTH2_CONFIGURATION_TEMPLATE, PermissionChecker.allowAllPermissionChecker);
  40 + put(Resource.TENANT_PROFILE, PermissionChecker.allowAllPermissionChecker);
40 41 }
41 42
42 43 private static final PermissionChecker systemEntityPermissionChecker = new PermissionChecker() {
... ...
... ... @@ -39,6 +39,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
39 39 put(Resource.USER, userPermissionChecker);
40 40 put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
41 41 put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
  42 + put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker);
42 43 }
43 44
44 45 public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() {
... ...
... ... @@ -226,6 +226,11 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
226 226
227 227 @Override
228 228 public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback) {
  229 + onAttributesUpdate(tenantId, entityId, scope, attributes, true, callback);
  230 + }
  231 +
  232 + @Override
  233 + public void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback) {
229 234 onLocalTelemetrySubUpdate(entityId,
230 235 s -> {
231 236 if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) {
... ... @@ -254,7 +259,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
254 259 deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L));
255 260 }
256 261 }
257   - } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope)) {
  262 + } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) {
258 263 clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
259 264 new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes))
260 265 , null);
... ...
... ... @@ -17,13 +17,12 @@ package org.thingsboard.server.service.subscription;
17 17
18 18 import org.springframework.context.ApplicationListener;
19 19 import org.thingsboard.server.common.data.alarm.Alarm;
20   -import org.thingsboard.server.common.data.id.AlarmId;
21 20 import org.thingsboard.server.common.data.id.EntityId;
22 21 import org.thingsboard.server.common.data.id.TenantId;
23 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
24 23 import org.thingsboard.server.common.data.kv.TsKvEntry;
25   -import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
26 24 import org.thingsboard.server.common.msg.queue.TbCallback;
  25 +import org.thingsboard.server.queue.discovery.PartitionChangeEvent;
27 26
28 27 import java.util.List;
29 28
... ... @@ -37,9 +36,13 @@ public interface SubscriptionManagerService extends ApplicationListener<Partitio
37 36
38 37 void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, TbCallback callback);
39 38
  39 + void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, TbCallback callback);
  40 +
40 41 void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List<String> keys, TbCallback empty);
41 42
42 43 void onAlarmUpdate(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
43 44
44 45 void onAlarmDeleted(TenantId tenantId, EntityId entityId, Alarm alarm, TbCallback callback);
  46 +
  47 +
45 48 }
... ...
... ... @@ -171,9 +171,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
171 171
172 172 @Override
173 173 public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, FutureCallback<Void> callback) {
  174 + saveAndNotify(tenantId, entityId, scope, attributes, true, callback);
  175 + }
  176 +
  177 + @Override
  178 + public void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice, FutureCallback<Void> callback) {
174 179 ListenableFuture<List<Void>> saveFuture = attrService.save(tenantId, entityId, scope, attributes);
175 180 addMainCallback(saveFuture, callback);
176   - addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes));
  181 + addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice));
177 182 }
178 183
179 184 @Override
... ... @@ -236,11 +241,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer
236 241 , System.currentTimeMillis())), callback);
237 242 }
238 243
239   - private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
  244 + private void onAttributesUpdate(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes, boolean notifyDevice) {
240 245 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
241 246 if (currentPartitions.contains(tpi)) {
242 247 if (subscriptionManagerService.isPresent()) {
243   - subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, TbCallback.EMPTY);
  248 + subscriptionManagerService.get().onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice, TbCallback.EMPTY);
244 249 } else {
245 250 log.warn("Possible misconfiguration because subscriptionManagerService is null!");
246 251 }
... ...
... ... @@ -21,26 +21,36 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
21 21 import com.google.common.util.concurrent.Futures;
22 22 import com.google.common.util.concurrent.ListenableFuture;
23 23 import com.google.common.util.concurrent.MoreExecutors;
  24 +import com.google.protobuf.ByteString;
24 25 import lombok.extern.slf4j.Slf4j;
25   -import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
26 27 import org.springframework.stereotype.Service;
27 28 import org.springframework.util.StringUtils;
28 29 import org.thingsboard.server.common.data.DataConstants;
29 30 import org.thingsboard.server.common.data.Device;
30   -import org.thingsboard.server.common.data.Tenant;
  31 +import org.thingsboard.server.common.data.DeviceProfile;
  32 +import org.thingsboard.server.common.data.TenantProfile;
  33 +import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
31 34 import org.thingsboard.server.common.data.id.CustomerId;
32 35 import org.thingsboard.server.common.data.id.DeviceId;
  36 +import org.thingsboard.server.common.data.id.DeviceProfileId;
33 37 import org.thingsboard.server.common.data.id.TenantId;
34 38 import org.thingsboard.server.common.data.relation.EntityRelation;
35 39 import org.thingsboard.server.common.data.security.DeviceCredentials;
36 40 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  41 +import org.thingsboard.server.common.msg.EncryptionUtil;
37 42 import org.thingsboard.server.common.msg.TbMsg;
38 43 import org.thingsboard.server.common.msg.TbMsgDataType;
39 44 import org.thingsboard.server.common.msg.TbMsgMetaData;
  45 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
40 46 import org.thingsboard.server.dao.device.DeviceCredentialsService;
  47 +import org.thingsboard.server.dao.device.DeviceProfileService;
41 48 import org.thingsboard.server.dao.device.DeviceService;
42 49 import org.thingsboard.server.dao.relation.RelationService;
  50 +import org.thingsboard.server.dao.tenant.TenantProfileService;
43 51 import org.thingsboard.server.dao.tenant.TenantService;
  52 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  53 +import org.thingsboard.server.gen.transport.TransportProtos;
44 54 import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
45 55 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
46 56 import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg;
... ... @@ -74,44 +84,64 @@ public class DefaultTransportApiService implements TransportApiService {
74 84 private static final ObjectMapper mapper = new ObjectMapper();
75 85
76 86 //TODO: Constructor dependencies;
77   - @Autowired
78   - private TenantService tenantService;
  87 + private final DeviceProfileService deviceProfileService;
  88 + private final TenantService tenantService;
  89 + private final TenantProfileService tenantProfileService;
  90 + private final DeviceService deviceService;
  91 + private final RelationService relationService;
  92 + private final DeviceCredentialsService deviceCredentialsService;
  93 + private final DeviceStateService deviceStateService;
  94 + private final DbCallbackExecutorService dbCallbackExecutorService;
  95 + private final TbClusterService tbClusterService;
  96 + private final DataDecodingEncodingService dataDecodingEncodingService;
79 97
80   - @Autowired
81   - private DeviceService deviceService;
82   -
83   - @Autowired
84   - private RelationService relationService;
85   -
86   - @Autowired
87   - private DeviceCredentialsService deviceCredentialsService;
88   -
89   - @Autowired
90   - private DeviceStateService deviceStateService;
91   -
92   - @Autowired
93   - private DbCallbackExecutorService dbCallbackExecutorService;
94   -
95   - @Autowired
96   - protected TbClusterService tbClusterService;
97 98
98 99 private final ConcurrentMap<String, ReentrantLock> deviceCreationLocks = new ConcurrentHashMap<>();
99 100
  101 + public DefaultTransportApiService(DeviceProfileService deviceProfileService, TenantService tenantService,
  102 + TenantProfileService tenantProfileService, DeviceService deviceService,
  103 + RelationService relationService, DeviceCredentialsService deviceCredentialsService,
  104 + DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService,
  105 + TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService) {
  106 + this.deviceProfileService = deviceProfileService;
  107 + this.tenantService = tenantService;
  108 + this.tenantProfileService = tenantProfileService;
  109 + this.deviceService = deviceService;
  110 + this.relationService = relationService;
  111 + this.deviceCredentialsService = deviceCredentialsService;
  112 + this.deviceStateService = deviceStateService;
  113 + this.dbCallbackExecutorService = dbCallbackExecutorService;
  114 + this.tbClusterService = tbClusterService;
  115 + this.dataDecodingEncodingService = dataDecodingEncodingService;
  116 + }
  117 +
100 118 @Override
101 119 public ListenableFuture<TbProtoQueueMsg<TransportApiResponseMsg>> handle(TbProtoQueueMsg<TransportApiRequestMsg> tbProtoQueueMsg) {
102 120 TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue();
103 121 if (transportApiRequestMsg.hasValidateTokenRequestMsg()) {
104 122 ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg();
105   - return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  123 + return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN),
  124 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  125 + } else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) {
  126 + TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg();
  127 + return Futures.transform(validateCredentials(msg),
  128 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
106 129 } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) {
107 130 ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg();
108   - return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  131 + return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE),
  132 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
109 133 } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) {
110   - return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  134 + return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()),
  135 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
111 136 } else if (transportApiRequestMsg.hasGetTenantRoutingInfoRequestMsg()) {
112   - return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  137 + return Futures.transform(handle(transportApiRequestMsg.getGetTenantRoutingInfoRequestMsg()),
  138 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  139 + } else if (transportApiRequestMsg.hasGetDeviceProfileRequestMsg()) {
  140 + return Futures.transform(handle(transportApiRequestMsg.getGetDeviceProfileRequestMsg()),
  141 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
113 142 }
114   - return Futures.transform(getEmptyTransportApiResponseFuture(), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
  143 + return Futures.transform(getEmptyTransportApiResponseFuture(),
  144 + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor());
115 145 }
116 146
117 147 private ListenableFuture<TransportApiResponseMsg> validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) {
... ... @@ -124,6 +154,62 @@ public class DefaultTransportApiService implements TransportApiService {
124 154 }
125 155 }
126 156
  157 + private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
  158 + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
  159 + if (credentials != null) {
  160 + if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
  161 + return getDeviceInfo(credentials.getDeviceId(), credentials);
  162 + } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
  163 + if (!checkMqttCredentials(mqtt, credentials)) {
  164 + credentials = null;
  165 + }
  166 + }
  167 + }
  168 + if (credentials == null) {
  169 + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
  170 + if (credentials == null) {
  171 + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
  172 + }
  173 + }
  174 + if (credentials != null) {
  175 + return getDeviceInfo(credentials.getDeviceId(), credentials);
  176 + } else {
  177 + return getEmptyTransportApiResponseFuture();
  178 + }
  179 + }
  180 +
  181 + private DeviceCredentials checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, String credId) {
  182 + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credId);
  183 + if (deviceCredentials != null && deviceCredentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
  184 + if (!checkMqttCredentials(clientCred, deviceCredentials)) {
  185 + return null;
  186 + } else {
  187 + return deviceCredentials;
  188 + }
  189 + }
  190 + return null;
  191 + }
  192 +
  193 + private boolean checkMqttCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg clientCred, DeviceCredentials deviceCredentials) {
  194 + BasicMqttCredentials dbCred = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials.class);
  195 + if (!StringUtils.isEmpty(dbCred.getClientId()) && !dbCred.getClientId().equals(clientCred.getClientId())) {
  196 + return false;
  197 + }
  198 + if (!StringUtils.isEmpty(dbCred.getUserName()) && !dbCred.getUserName().equals(clientCred.getUserName())) {
  199 + return false;
  200 + }
  201 + if (!StringUtils.isEmpty(dbCred.getPassword())) {
  202 + if (StringUtils.isEmpty(clientCred.getPassword())) {
  203 + return false;
  204 + } else {
  205 + if (!dbCred.getPassword().equals(clientCred.getPassword())) {
  206 + return false;
  207 + }
  208 + }
  209 + }
  210 + return true;
  211 + }
  212 +
127 213 private ListenableFuture<TransportApiResponseMsg> handle(GetOrCreateDeviceFromGatewayRequestMsg requestMsg) {
128 214 DeviceId gatewayId = new DeviceId(new UUID(requestMsg.getGatewayIdMSB(), requestMsg.getGatewayIdLSB()));
129 215 ListenableFuture<Device> gatewayFuture = deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, gatewayId);
... ... @@ -139,6 +225,8 @@ public class DefaultTransportApiService implements TransportApiService {
139 225 device.setName(requestMsg.getDeviceName());
140 226 device.setType(requestMsg.getDeviceType());
141 227 device.setCustomerId(gateway.getCustomerId());
  228 + DeviceProfile deviceProfile = deviceProfileService.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
  229 + device.setDeviceProfileId(deviceProfile.getId());
142 230 device = deviceService.saveDevice(device);
143 231 relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
144 232 deviceStateService.onDeviceAdded(device);
... ... @@ -155,10 +243,19 @@ public class DefaultTransportApiService implements TransportApiService {
155 243 TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, mapper.writeValueAsString(entityNode));
156 244 tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null);
157 245 }
  246 + GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder()
  247 + .setDeviceInfo(getDeviceInfoProto(device));
  248 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
  249 + if (deviceProfile != null) {
  250 + builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
  251 + } else {
  252 + log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId());
  253 + }
158 254 return TransportApiResponseMsg.newBuilder()
159   - .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
  255 + .setGetOrCreateDeviceResponseMsg(builder.build())
  256 + .build();
160 257 } catch (JsonProcessingException e) {
161   - log.warn("[{}] Failed to lookup device by gateway id and name", gatewayId, requestMsg.getDeviceName(), e);
  258 + log.warn("[{}] Failed to lookup device by gateway id and name: [{}]", gatewayId, requestMsg.getDeviceName(), e);
162 259 throw new RuntimeException(e);
163 260 } finally {
164 261 deviceCreationLock.unlock();
... ... @@ -168,10 +265,23 @@ public class DefaultTransportApiService implements TransportApiService {
168 265
169 266 private ListenableFuture<TransportApiResponseMsg> handle(GetTenantRoutingInfoRequestMsg requestMsg) {
170 267 TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB()));
171   - ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(TenantId.SYS_TENANT_ID, tenantId);
172   - return Futures.transform(tenantFuture, tenant -> TransportApiResponseMsg.newBuilder()
173   - .setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenant.isIsolatedTbCore())
174   - .setIsolatedTbRuleEngine(tenant.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService);
  268 + // TODO: Tenant Profile from cache
  269 + ListenableFuture<TenantProfile> tenantProfileFuture =
  270 + Futures.transform(tenantService.findTenantByIdAsync(TenantId.SYS_TENANT_ID, tenantId), tenant ->
  271 + tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, tenant.getTenantProfileId()), dbCallbackExecutorService);
  272 + return Futures.transform(tenantProfileFuture, tenantProfile -> TransportApiResponseMsg.newBuilder()
  273 + .setGetTenantRoutingInfoResponseMsg(GetTenantRoutingInfoResponseMsg.newBuilder().setIsolatedTbCore(tenantProfile.isIsolatedTbCore())
  274 + .setIsolatedTbRuleEngine(tenantProfile.isIsolatedTbRuleEngine()).build()).build(), dbCallbackExecutorService);
  275 + }
  276 +
  277 + private ListenableFuture<TransportApiResponseMsg> handle(TransportProtos.GetDeviceProfileRequestMsg requestMsg) {
  278 + DeviceProfileId profileId = new DeviceProfileId(new UUID(requestMsg.getProfileIdMSB(), requestMsg.getProfileIdLSB()));
  279 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(TenantId.SYS_TENANT_ID, profileId);
  280 + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder()
  281 + .setGetDeviceProfileResponseMsg(
  282 + TransportProtos.GetDeviceProfileResponseMsg.newBuilder()
  283 + .setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)))
  284 + .build()).build());
175 285 }
176 286
177 287 private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
... ... @@ -183,11 +293,17 @@ public class DefaultTransportApiService implements TransportApiService {
183 293 try {
184 294 ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder();
185 295 builder.setDeviceInfo(getDeviceInfoProto(device));
  296 + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId());
  297 + if (deviceProfile != null) {
  298 + builder.setProfileBody(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)));
  299 + } else {
  300 + log.warn("[{}] Failed to find device profile [{}] for device. ", device.getId(), device.getDeviceProfileId());
  301 + }
186 302 if (!StringUtils.isEmpty(credentials.getCredentialsValue())) {
187 303 builder.setCredentialsBody(credentials.getCredentialsValue());
188 304 }
189 305 return TransportApiResponseMsg.newBuilder()
190   - .setValidateTokenResponseMsg(builder.build()).build();
  306 + .setValidateCredResponseMsg(builder.build()).build();
191 307 } catch (JsonProcessingException e) {
192 308 log.warn("[{}] Failed to lookup device by id", deviceId, e);
193 309 return getEmptyTransportApiResponse();
... ... @@ -203,6 +319,8 @@ public class DefaultTransportApiService implements TransportApiService {
203 319 .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits())
204 320 .setDeviceName(device.getName())
205 321 .setDeviceType(device.getType())
  322 + .setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits())
  323 + .setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits())
206 324 .setAdditionalInfo(mapper.writeValueAsString(device.getAdditionalInfo()))
207 325 .build();
208 326 }
... ... @@ -213,6 +331,6 @@ public class DefaultTransportApiService implements TransportApiService {
213 331
214 332 private TransportApiResponseMsg getEmptyTransportApiResponse() {
215 333 return TransportApiResponseMsg.newBuilder()
216   - .setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build();
  334 + .setValidateCredResponseMsg(ValidateDeviceCredentialsResponseMsg.getDefaultInstance()).build();
217 335 }
218 336 }
... ...
... ... @@ -38,19 +38,14 @@ public abstract class AbstractCleanUpService {
38 38 @Value("${spring.datasource.password}")
39 39 protected String dbPassword;
40 40
41   - protected long executeQuery(Connection conn, String query) {
42   - long removed = 0L;
43   - try {
44   - Statement statement = conn.createStatement();
45   - ResultSet resultSet = statement.executeQuery(query);
46   - getWarnings(statement);
  41 + protected long executeQuery(Connection conn, String query) throws SQLException {
  42 + try (Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(query)) {
  43 + if (log.isDebugEnabled()) {
  44 + getWarnings(statement);
  45 + }
47 46 resultSet.next();
48   - removed = resultSet.getLong(1);
49   - log.debug("Successfully executed query: {}", query);
50   - } catch (SQLException e) {
51   - log.debug("Failed to execute query: {} due to: {}", query, e.getMessage());
  47 + return resultSet.getLong(1);
52 48 }
53   - return removed;
54 49 }
55 50
56 51 protected void getWarnings(Statement statement) throws SQLException {
... ... @@ -65,6 +60,6 @@ public abstract class AbstractCleanUpService {
65 60 }
66 61 }
67 62
68   - protected abstract void doCleanUp(Connection connection);
  63 + protected abstract void doCleanUp(Connection connection) throws SQLException;
69 64
70 65 }
... ...
... ... @@ -52,7 +52,7 @@ public class EventsCleanUpService extends AbstractCleanUpService {
52 52 }
53 53
54 54 @Override
55   - protected void doCleanUp(Connection connection) {
  55 + protected void doCleanUp(Connection connection) throws SQLException {
56 56 long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);");
57 57 log.info("Total events removed by TTL: [{}]", totalEventsRemoved);
58 58 }
... ...
... ... @@ -23,6 +23,7 @@ import org.thingsboard.server.dao.util.PsqlDao;
23 23 import org.thingsboard.server.dao.util.SqlTsDao;
24 24
25 25 import java.sql.Connection;
  26 +import java.sql.SQLException;
26 27
27 28 @SqlTsDao
28 29 @PsqlDao
... ... @@ -34,10 +35,10 @@ public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpServi
34 35 private String partitionType;
35 36
36 37 @Override
37   - protected void doCleanUp(Connection connection) {
38   - long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);");
39   - log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved);
40   - long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);");
41   - log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
  38 + protected void doCleanUp(Connection connection) throws SQLException {
  39 + long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);");
  40 + log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved);
  41 + long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);");
  42 + log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
42 43 }
43 44 }
\ No newline at end of file
... ...
... ... @@ -21,6 +21,7 @@ import org.thingsboard.server.dao.model.ModelConstants;
21 21 import org.thingsboard.server.dao.util.TimescaleDBTsDao;
22 22
23 23 import java.sql.Connection;
  24 +import java.sql.SQLException;
24 25
25 26 @TimescaleDBTsDao
26 27 @Service
... ... @@ -28,8 +29,8 @@ import java.sql.Connection;
28 29 public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService {
29 30
30 31 @Override
31   - protected void doCleanUp(Connection connection) {
  32 + protected void doCleanUp(Connection connection) throws SQLException {
32 33 long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);");
33 34 log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved);
34 35 }
35   -}
\ No newline at end of file
  36 +}
... ...
... ... @@ -29,6 +29,8 @@
29 29
30 30 <!-- <logger name="org.thingsboard.server.service.queue" level="TRACE" />-->
31 31 <!-- <logger name="org.thingsboard.server.service.transport" level="TRACE" />-->
  32 +<!-- <logger name="org.thingsboard.server.queue.memory.InMemoryStorage" level="DEBUG" />-->
  33 +
32 34
33 35 <!-- <logger name="org.thingsboard.server.service.subscription" level="TRACE"/>-->
34 36 <!-- <logger name="org.thingsboard.server.service.telemetry" level="TRACE"/>-->
... ...
... ... @@ -140,8 +140,21 @@ cassandra:
140 140 url: "${CASSANDRA_URL:127.0.0.1:9042}"
141 141 # Specify local datacenter name
142 142 local_datacenter: "${CASSANDRA_LOCAL_DATACENTER:datacenter1}"
143   - # Enable/disable secure connection
144   - ssl: "${CASSANDRA_USE_SSL:false}"
  143 + ssl:
  144 + # Enable/disable secure connection
  145 + enabled: "${CASSANDRA_USE_SSL:false}"
  146 + # Enable/disable validation of Cassandra server hostname
  147 + # If enabled, hostname of Cassandra server must match CN of server certificate
  148 + hostname_validation: "${CASSANDRA_SSL_HOSTNAME_VALIDATION:true}"
  149 + # Set trust store for client authentication of server (optional, uses trust store from default SSLContext if not set)
  150 + trust_store: "${CASSANDRA_SSL_TRUST_STORE:}"
  151 + trust_store_password: "${CASSANDRA_SSL_TRUST_STORE_PASSWORD:}"
  152 + # Set key store for server authentication of client (optional, uses key store from default SSLContext if not set)
  153 + # A key store is only needed if the Cassandra server requires client authentication
  154 + key_store: "${CASSANDRA_SSL_KEY_STORE:}"
  155 + key_store_password: "${CASSANDRA_SSL_KEY_STORE_PASSWORD:}"
  156 + # Comma separated list of cipher suites (optional, uses Java default cipher suites if not set)
  157 + cipher_suites: "${CASSANDRA_SSL_CIPHER_SUITES:}"
145 158 # Enable/disable JMX
146 159 jmx: "${CASSANDRA_USE_JMX:false}"
147 160 # Enable/disable metrics collection.
... ... @@ -217,6 +230,8 @@ sql:
217 230 batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}"
218 231 stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
219 232 batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}"
  233 + # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
  234 + batch_sort: "${SQL_BATCH_SORT:false}"
220 235 # Specify whether to remove null characters from strValue of attributes and timeseries before insert
221 236 remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}"
222 237 # Specify whether to log database queries and their parameters generated by entity query repository
... ... @@ -318,6 +333,12 @@ caffeine:
318 333 securitySettings:
319 334 timeToLiveInMinutes: 1440
320 335 maxSize: 0
  336 + tenantProfiles:
  337 + timeToLiveInMinutes: 1440
  338 + maxSize: 0
  339 + deviceProfiles:
  340 + timeToLiveInMinutes: 1440
  341 + maxSize: 0
321 342
322 343 redis:
323 344 # standalone or cluster
... ... @@ -403,7 +424,7 @@ spring:
403 424 database-platform: "${SPRING_JPA_DATABASE_PLATFORM:org.hibernate.dialect.PostgreSQLDialect}"
404 425 datasource:
405 426 driverClassName: "${SPRING_DRIVER_CLASS_NAME:org.postgresql.Driver}"
406   - url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard}"
  427 + url: "${SPRING_DATASOURCE_URL:jdbc:postgresql://localhost:5432/thingsboard_32}"
407 428 username: "${SPRING_DATASOURCE_USERNAME:postgres}"
408 429 password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
409 430 hikari:
... ... @@ -429,6 +450,7 @@ audit-log:
429 450 "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
430 451 "alarm": "${AUDIT_LOG_MASK_ALARM:W}"
431 452 "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}"
  453 + "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}"
432 454 sink:
433 455 # Type of external sink. possible options: none, elasticsearch
434 456 type: "${AUDIT_LOG_SINK_TYPE:none}"
... ... @@ -461,7 +483,7 @@ js:
461 483 # Specify thread pool size for JavaScript sandbox resource monitor
462 484 monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
463 485 # Maximum CPU time in milliseconds allowed for script execution
464   - max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:10000}"
  486 + max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:8000}"
465 487 # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
466 488 max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}"
467 489 # JS Eval max request timeout. 0 - no timeout
... ... @@ -554,6 +576,10 @@ swagger:
554 576
555 577 queue:
556 578 type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
  579 + in_memory:
  580 + stats:
  581 + # For debug lvl
  582 + print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}"
557 583 kafka:
558 584 bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}"
559 585 acks: "${TB_KAFKA_ACKS:all}"
... ... @@ -573,11 +599,11 @@ queue:
573 599 security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}"
574 600 other:
575 601 topic-properties:
576   - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
577   - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
578   - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
579   - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000}"
580   - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600}"
  602 + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  603 + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  604 + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  605 + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}"
  606 + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}"
581 607 aws_sqs:
582 608 use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}"
583 609 access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}"
... ... @@ -685,6 +711,7 @@ queue:
685 711 retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
686 712 failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
687 713 pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries;
  714 + max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries.
688 715 - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}"
689 716 topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
690 717 poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
... ... @@ -700,6 +727,7 @@ queue:
700 727 retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited
701 728 failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
702 729 pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  730 + max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
703 731 - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}"
704 732 topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
705 733 poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
... ... @@ -715,6 +743,7 @@ queue:
715 743 retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited
716 744 failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages;
717 745 pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries;
  746 + max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries.
718 747 transport:
719 748 # For high priority notifications that require minimum latency and processing time
720 749 notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
... ... @@ -739,4 +768,4 @@ management:
739 768 web:
740 769 exposure:
741 770 # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
742   - include: '${METRICS_ENDPOINTS_EXPOSE:info}'
\ No newline at end of file
  771 + include: '${METRICS_ENDPOINTS_EXPOSE:info}'
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.datastax.oss.driver.api.core.uuid.Uuids;
18 19 import com.fasterxml.jackson.core.type.TypeReference;
19 20 import com.fasterxml.jackson.databind.JsonNode;
20 21 import com.fasterxml.jackson.databind.ObjectMapper;
... ... @@ -59,8 +60,16 @@ import org.springframework.util.MultiValueMap;
59 60 import org.springframework.web.context.WebApplicationContext;
60 61 import org.thingsboard.server.common.data.BaseData;
61 62 import org.thingsboard.server.common.data.Customer;
  63 +import org.thingsboard.server.common.data.DeviceProfile;
  64 +import org.thingsboard.server.common.data.DeviceProfileType;
  65 +import org.thingsboard.server.common.data.DeviceTransportType;
62 66 import org.thingsboard.server.common.data.Tenant;
63 67 import org.thingsboard.server.common.data.User;
  68 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  69 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
  70 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  71 +import org.thingsboard.server.common.data.id.HasId;
  72 +import org.thingsboard.server.common.data.id.RuleChainId;
64 73 import org.thingsboard.server.common.data.id.TenantId;
65 74 import org.thingsboard.server.common.data.id.UUIDBased;
66 75 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -217,6 +226,10 @@ public abstract class AbstractWebTest {
217 226 login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
218 227 }
219 228
  229 + protected void loginUser(String userName, String password) throws Exception {
  230 + login(userName, password);
  231 + }
  232 +
220 233 private Tenant savedDifferentTenant;
221 234
222 235 protected void loginDifferentTenant() throws Exception {
... ... @@ -242,15 +255,27 @@ public abstract class AbstractWebTest {
242 255 protected User createUserAndLogin(User user, String password) throws Exception {
243 256 User savedUser = doPost("/api/user", user, User.class);
244 257 logout();
  258 + JsonNode activateRequest = getActivateRequest(password);
  259 + JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
  260 + validateAndSetJwtToken(tokenInfo, user.getEmail());
  261 + return savedUser;
  262 + }
  263 +
  264 + protected User createUser(User user, String password) throws Exception {
  265 + User savedUser = doPost("/api/user", user, User.class);
  266 + JsonNode activateRequest = getActivateRequest(password);
  267 + ResultActions resultActions = doPost("/api/noauth/activate", activateRequest);
  268 + resultActions.andExpect(status().isOk());
  269 + return savedUser;
  270 + }
  271 +
  272 + private JsonNode getActivateRequest(String password) throws Exception {
245 273 doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
246 274 .andExpect(status().isSeeOther())
247 275 .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
248   - JsonNode activateRequest = new ObjectMapper().createObjectNode()
  276 + return new ObjectMapper().createObjectNode()
249 277 .put("activateToken", TestMailService.currentActivateToken)
250 278 .put("password", password);
251   - JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
252   - validateAndSetJwtToken(tokenInfo, user.getEmail());
253   - return savedUser;
254 279 }
255 280
256 281 protected void login(String username, String password) throws Exception {
... ... @@ -304,6 +329,23 @@ public abstract class AbstractWebTest {
304 329 }
305 330 }
306 331
  332 + protected DeviceProfile createDeviceProfile(String name) {
  333 + DeviceProfile deviceProfile = new DeviceProfile();
  334 + deviceProfile.setName(name);
  335 + deviceProfile.setType(DeviceProfileType.DEFAULT);
  336 + deviceProfile.setTransportType(DeviceTransportType.DEFAULT);
  337 + deviceProfile.setDescription(name + " Test");
  338 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  339 + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
  340 + DefaultDeviceProfileTransportConfiguration transportConfiguration = new DefaultDeviceProfileTransportConfiguration();
  341 + deviceProfileData.setConfiguration(configuration);
  342 + deviceProfileData.setTransportConfiguration(transportConfiguration);
  343 + deviceProfile.setProfileData(deviceProfileData);
  344 + deviceProfile.setDefault(false);
  345 + deviceProfile.setDefaultRuleChainId(null);
  346 + return deviceProfile;
  347 + }
  348 +
307 349 protected ResultActions doGet(String urlTemplate, Object... urlVariables) throws Exception {
308 350 MockHttpServletRequestBuilder getRequest = get(urlTemplate, urlVariables);
309 351 setJwtToken(getRequest);
... ... @@ -416,6 +458,10 @@ public abstract class AbstractWebTest {
416 458 return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass);
417 459 }
418 460
  461 + protected <T> T doPostClaimAsync(String urlTemplate, Object content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
  462 + return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
  463 + }
  464 +
419 465 protected <T> T doDelete(String urlTemplate, Class<T> responseClass, String... params) throws Exception {
420 466 return readResponse(doDelete(urlTemplate, params).andExpect(status().isOk()), responseClass);
421 467 }
... ... @@ -486,7 +532,7 @@ public abstract class AbstractWebTest {
486 532 return mapper.readerFor(type).readValue(content);
487 533 }
488 534
489   - public class IdComparator<D extends BaseData<? extends UUIDBased>> implements Comparator<D> {
  535 + public class IdComparator<D extends HasId> implements Comparator<D> {
490 536 @Override
491 537 public int compare(D o1, D o2) {
492 538 return o1.getId().getId().compareTo(o2.getId().getId());
... ...
... ... @@ -24,6 +24,7 @@ import org.junit.Before;
24 24 import org.junit.Test;
25 25 import org.thingsboard.server.common.data.Customer;
26 26 import org.thingsboard.server.common.data.Device;
  27 +import org.thingsboard.server.common.data.DeviceProfile;
27 28 import org.thingsboard.server.common.data.EntitySubtype;
28 29 import org.thingsboard.server.common.data.Tenant;
29 30 import org.thingsboard.server.common.data.User;
... ... @@ -185,9 +186,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
185 186 public void testSaveDeviceWithEmptyType() throws Exception {
186 187 Device device = new Device();
187 188 device.setName("My device");
188   - doPost("/api/device", device)
189   - .andExpect(status().isBadRequest())
190   - .andExpect(statusReason(containsString("Device type should be specified")));
  189 + Device savedDevice = doPost("/api/device", device, Device.class);
  190 + Assert.assertEquals("default", savedDevice.getType());
191 191 }
192 192
193 193 @Test
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.junit.After;
  20 +import org.junit.Assert;
  21 +import org.junit.Before;
  22 +import org.junit.Ignore;
  23 +import org.junit.Test;
  24 +import org.thingsboard.server.common.data.Device;
  25 +import org.thingsboard.server.common.data.DeviceProfile;
  26 +import org.thingsboard.server.common.data.DeviceProfileInfo;
  27 +import org.thingsboard.server.common.data.DeviceProfileType;
  28 +import org.thingsboard.server.common.data.DeviceTransportType;
  29 +import org.thingsboard.server.common.data.Tenant;
  30 +import org.thingsboard.server.common.data.User;
  31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.common.data.security.Authority;
  34 +
  35 +import java.util.ArrayList;
  36 +import java.util.Collections;
  37 +import java.util.List;
  38 +import java.util.stream.Collectors;
  39 +
  40 +import static org.hamcrest.Matchers.containsString;
  41 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  42 +
  43 +public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest {
  44 +
  45 + private IdComparator<DeviceProfile> idComparator = new IdComparator<>();
  46 + private IdComparator<DeviceProfileInfo> deviceProfileInfoIdComparator = new IdComparator<>();
  47 +
  48 + private Tenant savedTenant;
  49 + private User tenantAdmin;
  50 +
  51 + @Before
  52 + public void beforeTest() throws Exception {
  53 + loginSysAdmin();
  54 +
  55 + Tenant tenant = new Tenant();
  56 + tenant.setTitle("My tenant");
  57 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  58 + Assert.assertNotNull(savedTenant);
  59 +
  60 + tenantAdmin = new User();
  61 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  62 + tenantAdmin.setTenantId(savedTenant.getId());
  63 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  64 + tenantAdmin.setFirstName("Joe");
  65 + tenantAdmin.setLastName("Downs");
  66 +
  67 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  68 + }
  69 +
  70 + @After
  71 + public void afterTest() throws Exception {
  72 + loginSysAdmin();
  73 +
  74 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  75 + .andExpect(status().isOk());
  76 + }
  77 +
  78 + @Test
  79 + public void testSaveDeviceProfile() throws Exception {
  80 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  81 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  82 + Assert.assertNotNull(savedDeviceProfile);
  83 + Assert.assertNotNull(savedDeviceProfile.getId());
  84 + Assert.assertTrue(savedDeviceProfile.getCreatedTime() > 0);
  85 + Assert.assertEquals(deviceProfile.getName(), savedDeviceProfile.getName());
  86 + Assert.assertEquals(deviceProfile.getDescription(), savedDeviceProfile.getDescription());
  87 + Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData());
  88 + Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault());
  89 + Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId());
  90 + savedDeviceProfile.setName("New device profile");
  91 + doPost("/api/deviceProfile", savedDeviceProfile, DeviceProfile.class);
  92 + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
  93 + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName());
  94 + }
  95 +
  96 + @Test
  97 + public void testFindDeviceProfileById() throws Exception {
  98 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  99 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  100 + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class);
  101 + Assert.assertNotNull(foundDeviceProfile);
  102 + Assert.assertEquals(savedDeviceProfile, foundDeviceProfile);
  103 + }
  104 +
  105 + @Test
  106 + public void testFindDeviceProfileInfoById() throws Exception {
  107 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  108 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  109 + DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class);
  110 + Assert.assertNotNull(foundDeviceProfileInfo);
  111 + Assert.assertEquals(savedDeviceProfile.getId(), foundDeviceProfileInfo.getId());
  112 + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfileInfo.getName());
  113 + Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType());
  114 + }
  115 +
  116 + @Test
  117 + public void testFindDefaultDeviceProfileInfo() throws Exception {
  118 + DeviceProfileInfo foundDefaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class);
  119 + Assert.assertNotNull(foundDefaultDeviceProfileInfo);
  120 + Assert.assertNotNull(foundDefaultDeviceProfileInfo.getId());
  121 + Assert.assertNotNull(foundDefaultDeviceProfileInfo.getName());
  122 + Assert.assertNotNull(foundDefaultDeviceProfileInfo.getType());
  123 + Assert.assertEquals(DeviceProfileType.DEFAULT, foundDefaultDeviceProfileInfo.getType());
  124 + Assert.assertEquals("default", foundDefaultDeviceProfileInfo.getName());
  125 + }
  126 +
  127 + @Test
  128 + public void testSetDefaultDeviceProfile() throws Exception {
  129 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1");
  130 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  131 + DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class);
  132 + Assert.assertNotNull(defaultDeviceProfile);
  133 + DeviceProfileInfo foundDefaultDeviceProfile = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class);
  134 + Assert.assertNotNull(foundDefaultDeviceProfile);
  135 + Assert.assertEquals(savedDeviceProfile.getName(), foundDefaultDeviceProfile.getName());
  136 + Assert.assertEquals(savedDeviceProfile.getId(), foundDefaultDeviceProfile.getId());
  137 + Assert.assertEquals(savedDeviceProfile.getType(), foundDefaultDeviceProfile.getType());
  138 + }
  139 +
  140 + @Test
  141 + public void testSaveDeviceProfileWithEmptyName() throws Exception {
  142 + DeviceProfile deviceProfile = new DeviceProfile();
  143 + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest())
  144 + .andExpect(statusReason(containsString("Device profile name should be specified")));
  145 + }
  146 +
  147 + @Test
  148 + public void testSaveDeviceProfileWithSameName() throws Exception {
  149 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  150 + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk());
  151 + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile");
  152 + doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest())
  153 + .andExpect(statusReason(containsString("Device profile with such name already exists")));
  154 + }
  155 +
  156 + @Ignore
  157 + @Test
  158 + public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception {
  159 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  160 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  161 + Device device = new Device();
  162 + device.setName("Test device");
  163 + device.setType("default");
  164 + device.setDeviceProfileId(savedDeviceProfile.getId());
  165 + doPost("/api/device", device, Device.class);
  166 + //TODO uncomment once we have other device types;
  167 + //savedDeviceProfile.setType(DeviceProfileType.LWM2M);
  168 + doPost("/api/deviceProfile", savedDeviceProfile).andExpect(status().isBadRequest())
  169 + .andExpect(statusReason(containsString("Can't change device profile type because devices referenced it")));
  170 + }
  171 +
  172 + @Test
  173 + public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception {
  174 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  175 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  176 + Device device = new Device();
  177 + device.setName("Test device");
  178 + device.setType("default");
  179 + device.setDeviceProfileId(savedDeviceProfile.getId());
  180 + doPost("/api/device", device, Device.class);
  181 + savedDeviceProfile.setTransportType(DeviceTransportType.MQTT);
  182 + doPost("/api/deviceProfile", savedDeviceProfile).andExpect(status().isBadRequest())
  183 + .andExpect(statusReason(containsString("Can't change device profile transport type because devices referenced it")));
  184 + }
  185 +
  186 + @Test
  187 + public void testDeleteDeviceProfileWithExistingDevice() throws Exception {
  188 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  189 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  190 +
  191 + Device device = new Device();
  192 + device.setName("Test device");
  193 + device.setType("default");
  194 + device.setDeviceProfileId(savedDeviceProfile.getId());
  195 +
  196 + Device savedDevice = doPost("/api/device", device, Device.class);
  197 +
  198 + doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
  199 + .andExpect(status().isBadRequest())
  200 + .andExpect(statusReason(containsString("The device profile referenced by the devices cannot be deleted")));
  201 + }
  202 +
  203 + @Test
  204 + public void testDeleteDeviceProfile() throws Exception {
  205 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile");
  206 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
  207 +
  208 + doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
  209 + .andExpect(status().isOk());
  210 +
  211 + doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString())
  212 + .andExpect(status().isNotFound());
  213 + }
  214 +
  215 + @Test
  216 + public void testFindDeviceProfiles() throws Exception {
  217 + List<DeviceProfile> deviceProfiles = new ArrayList<>();
  218 + PageLink pageLink = new PageLink(17);
  219 + PageData<DeviceProfile> pageData = doGetTypedWithPageLink("/api/deviceProfiles?",
  220 + new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
  221 + Assert.assertFalse(pageData.hasNext());
  222 + Assert.assertEquals(1, pageData.getTotalElements());
  223 + deviceProfiles.addAll(pageData.getData());
  224 +
  225 + for (int i=0;i<28;i++) {
  226 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i);
  227 + deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class));
  228 + }
  229 +
  230 + List<DeviceProfile> loadedDeviceProfiles = new ArrayList<>();
  231 + pageLink = new PageLink(17);
  232 + do {
  233 + pageData = doGetTypedWithPageLink("/api/deviceProfiles?",
  234 + new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
  235 + loadedDeviceProfiles.addAll(pageData.getData());
  236 + if (pageData.hasNext()) {
  237 + pageLink = pageLink.nextPageLink();
  238 + }
  239 + } while (pageData.hasNext());
  240 +
  241 + Collections.sort(deviceProfiles, idComparator);
  242 + Collections.sort(loadedDeviceProfiles, idComparator);
  243 +
  244 + Assert.assertEquals(deviceProfiles, loadedDeviceProfiles);
  245 +
  246 + for (DeviceProfile deviceProfile : loadedDeviceProfiles) {
  247 + if (!deviceProfile.isDefault()) {
  248 + doDelete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
  249 + .andExpect(status().isOk());
  250 + }
  251 + }
  252 +
  253 + pageLink = new PageLink(17);
  254 + pageData = doGetTypedWithPageLink("/api/deviceProfiles?",
  255 + new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
  256 + Assert.assertFalse(pageData.hasNext());
  257 + Assert.assertEquals(1, pageData.getTotalElements());
  258 + }
  259 +
  260 + @Test
  261 + public void testFindDeviceProfileInfos() throws Exception {
  262 + List<DeviceProfile> deviceProfiles = new ArrayList<>();
  263 + PageLink pageLink = new PageLink(17);
  264 + PageData<DeviceProfile> deviceProfilePageData = doGetTypedWithPageLink("/api/deviceProfiles?",
  265 + new TypeReference<PageData<DeviceProfile>>(){}, pageLink);
  266 + Assert.assertFalse(deviceProfilePageData.hasNext());
  267 + Assert.assertEquals(1, deviceProfilePageData.getTotalElements());
  268 + deviceProfiles.addAll(deviceProfilePageData.getData());
  269 +
  270 + for (int i=0;i<28;i++) {
  271 + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i);
  272 + deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class));
  273 + }
  274 +
  275 + List<DeviceProfileInfo> loadedDeviceProfileInfos = new ArrayList<>();
  276 + pageLink = new PageLink(17);
  277 + PageData<DeviceProfileInfo> pageData;
  278 + do {
  279 + pageData = doGetTypedWithPageLink("/api/deviceProfileInfos?",
  280 + new TypeReference<PageData<DeviceProfileInfo>>(){}, pageLink);
  281 + loadedDeviceProfileInfos.addAll(pageData.getData());
  282 + if (pageData.hasNext()) {
  283 + pageLink = pageLink.nextPageLink();
  284 + }
  285 + } while (pageData.hasNext());
  286 +
  287 + Collections.sort(deviceProfiles, idComparator);
  288 + Collections.sort(loadedDeviceProfileInfos, deviceProfileInfoIdComparator);
  289 +
  290 + List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream().map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(),
  291 + deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList());
  292 +
  293 + Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos);
  294 +
  295 + for (DeviceProfile deviceProfile : deviceProfiles) {
  296 + if (!deviceProfile.isDefault()) {
  297 + doDelete("/api/deviceProfile/" + deviceProfile.getId().getId().toString())
  298 + .andExpect(status().isOk());
  299 + }
  300 + }
  301 +
  302 + pageLink = new PageLink(17);
  303 + pageData = doGetTypedWithPageLink("/api/deviceProfileInfos?",
  304 + new TypeReference<PageData<DeviceProfileInfo>>(){}, pageLink);
  305 + Assert.assertFalse(pageData.hasNext());
  306 + Assert.assertEquals(1, pageData.getTotalElements());
  307 + }
  308 +
  309 +}
... ...
... ... @@ -22,6 +22,7 @@ import org.apache.commons.lang3.RandomStringUtils;
22 22 import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
23 23 import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
24 24 import org.eclipse.paho.client.mqttv3.MqttMessage;
  25 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
25 26 import org.junit.After;
26 27 import org.junit.Assert;
27 28 import org.junit.Before;
... ... @@ -424,7 +425,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
424 425 assertNotNull(accessToken);
425 426
426 427 String clientId = MqttAsyncClient.generateClientId();
427   - MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
  428 + MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
428 429
429 430 MqttConnectOptions options = new MqttConnectOptions();
430 431 options.setUserName(accessToken);
... ... @@ -466,7 +467,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
466 467 assertNotNull(accessToken);
467 468
468 469 String clientId = MqttAsyncClient.generateClientId();
469   - MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId);
  470 + MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
470 471
471 472 MqttConnectOptions options = new MqttConnectOptions();
472 473 options.setUserName(accessToken);
... ...
... ... @@ -15,21 +15,21 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
18   -import static org.hamcrest.Matchers.containsString;
19   -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
20   -
21   -import java.util.ArrayList;
22   -import java.util.Collections;
23   -import java.util.List;
24   -
  18 +import com.fasterxml.jackson.core.type.TypeReference;
25 19 import org.apache.commons.lang3.RandomStringUtils;
  20 +import org.junit.Assert;
  21 +import org.junit.Test;
26 22 import org.thingsboard.server.common.data.Tenant;
  23 +import org.thingsboard.server.common.data.TenantInfo;
27 24 import org.thingsboard.server.common.data.page.PageData;
28 25 import org.thingsboard.server.common.data.page.PageLink;
29   -import org.junit.Assert;
30   -import org.junit.Test;
31 26
32   -import com.fasterxml.jackson.core.type.TypeReference;
  27 +import java.util.ArrayList;
  28 +import java.util.Collections;
  29 +import java.util.List;
  30 +
  31 +import static org.hamcrest.Matchers.containsString;
  32 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
33 33
34 34 public abstract class BaseTenantControllerTest extends AbstractControllerTest {
35 35
... ... @@ -65,6 +65,19 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
65 65 doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
66 66 .andExpect(status().isOk());
67 67 }
  68 +
  69 + @Test
  70 + public void testFindTenantInfoById() throws Exception {
  71 + loginSysAdmin();
  72 + Tenant tenant = new Tenant();
  73 + tenant.setTitle("My tenant");
  74 + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  75 + TenantInfo foundTenant = doGet("/api/tenant/info/"+savedTenant.getId().getId().toString(), TenantInfo.class);
  76 + Assert.assertNotNull(foundTenant);
  77 + Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant);
  78 + doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
  79 + .andExpect(status().isOk());
  80 + }
68 81
69 82 @Test
70 83 public void testSaveTenantWithEmptyTitle() throws Exception {
... ... @@ -217,4 +230,48 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
217 230 Assert.assertFalse(pageData.hasNext());
218 231 Assert.assertEquals(0, pageData.getData().size());
219 232 }
  233 +
  234 + @Test
  235 + public void testFindTenantInfos() throws Exception {
  236 + loginSysAdmin();
  237 + List<TenantInfo> tenants = new ArrayList<>();
  238 + PageLink pageLink = new PageLink(17);
  239 + PageData<TenantInfo> pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink);
  240 + Assert.assertFalse(pageData.hasNext());
  241 + Assert.assertEquals(1, pageData.getData().size());
  242 + tenants.addAll(pageData.getData());
  243 +
  244 + for (int i=0;i<56;i++) {
  245 + Tenant tenant = new Tenant();
  246 + tenant.setTitle("Tenant"+i);
  247 + tenants.add(new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default"));
  248 + }
  249 +
  250 + List<TenantInfo> loadedTenants = new ArrayList<>();
  251 + pageLink = new PageLink(17);
  252 + do {
  253 + pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink);
  254 + loadedTenants.addAll(pageData.getData());
  255 + if (pageData.hasNext()) {
  256 + pageLink = pageLink.nextPageLink();
  257 + }
  258 + } while (pageData.hasNext());
  259 +
  260 + Collections.sort(tenants, idComparator);
  261 + Collections.sort(loadedTenants, idComparator);
  262 +
  263 + Assert.assertEquals(tenants, loadedTenants);
  264 +
  265 + for (TenantInfo tenant : loadedTenants) {
  266 + if (!tenant.getTitle().equals(TEST_TENANT_NAME)) {
  267 + doDelete("/api/tenant/"+tenant.getId().getId().toString())
  268 + .andExpect(status().isOk());
  269 + }
  270 + }
  271 +
  272 + pageLink = new PageLink(17);
  273 + pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink);
  274 + Assert.assertFalse(pageData.hasNext());
  275 + Assert.assertEquals(1, pageData.getData().size());
  276 + }
220 277 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.fasterxml.jackson.core.type.TypeReference;
  19 +import org.junit.After;
  20 +import org.junit.Assert;
  21 +import org.junit.Test;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.thingsboard.server.common.data.EntityInfo;
  24 +import org.thingsboard.server.common.data.Tenant;
  25 +import org.thingsboard.server.common.data.TenantProfile;
  26 +import org.thingsboard.server.common.data.TenantProfileData;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.page.PageData;
  29 +import org.thingsboard.server.common.data.page.PageLink;
  30 +import org.thingsboard.server.dao.tenant.TenantProfileService;
  31 +
  32 +import java.util.ArrayList;
  33 +import java.util.Collections;
  34 +import java.util.List;
  35 +import java.util.stream.Collectors;
  36 +
  37 +import static org.hamcrest.Matchers.containsString;
  38 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  39 +
  40 +public abstract class BaseTenantProfileControllerTest extends AbstractControllerTest {
  41 +
  42 + private IdComparator<TenantProfile> idComparator = new IdComparator<>();
  43 + private IdComparator<EntityInfo> tenantProfileInfoIdComparator = new IdComparator<>();
  44 +
  45 + @Autowired
  46 + private TenantProfileService tenantProfileService;
  47 +
  48 + @After
  49 + @Override
  50 + public void teardown() throws Exception {
  51 + super.teardown();
  52 + tenantProfileService.deleteTenantProfiles(TenantId.SYS_TENANT_ID);
  53 + }
  54 +
  55 + @Test
  56 + public void testSaveTenantProfile() throws Exception {
  57 + loginSysAdmin();
  58 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  59 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  60 + Assert.assertNotNull(savedTenantProfile);
  61 + Assert.assertNotNull(savedTenantProfile.getId());
  62 + Assert.assertTrue(savedTenantProfile.getCreatedTime() > 0);
  63 + Assert.assertEquals(tenantProfile.getName(), savedTenantProfile.getName());
  64 + Assert.assertEquals(tenantProfile.getDescription(), savedTenantProfile.getDescription());
  65 + Assert.assertEquals(tenantProfile.getProfileData(), savedTenantProfile.getProfileData());
  66 + Assert.assertEquals(tenantProfile.isDefault(), savedTenantProfile.isDefault());
  67 + Assert.assertEquals(tenantProfile.isIsolatedTbCore(), savedTenantProfile.isIsolatedTbCore());
  68 + Assert.assertEquals(tenantProfile.isIsolatedTbRuleEngine(), savedTenantProfile.isIsolatedTbRuleEngine());
  69 +
  70 + savedTenantProfile.setName("New tenant profile");
  71 + doPost("/api/tenantProfile", savedTenantProfile, TenantProfile.class);
  72 + TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
  73 + Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName());
  74 + }
  75 +
  76 + @Test
  77 + public void testFindTenantProfileById() throws Exception {
  78 + loginSysAdmin();
  79 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  80 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  81 + TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class);
  82 + Assert.assertNotNull(foundTenantProfile);
  83 + Assert.assertEquals(savedTenantProfile, foundTenantProfile);
  84 + }
  85 +
  86 + @Test
  87 + public void testFindTenantProfileInfoById() throws Exception {
  88 + loginSysAdmin();
  89 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  90 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  91 + EntityInfo foundTenantProfileInfo = doGet("/api/tenantProfileInfo/"+savedTenantProfile.getId().getId().toString(), EntityInfo.class);
  92 + Assert.assertNotNull(foundTenantProfileInfo);
  93 + Assert.assertEquals(savedTenantProfile.getId(), foundTenantProfileInfo.getId());
  94 + Assert.assertEquals(savedTenantProfile.getName(), foundTenantProfileInfo.getName());
  95 + }
  96 +
  97 + @Test
  98 + public void testFindDefaultTenantProfileInfo() throws Exception {
  99 + loginSysAdmin();
  100 + EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
  101 + Assert.assertNotNull(foundDefaultTenantProfile);
  102 + Assert.assertEquals("Default", foundDefaultTenantProfile.getName());
  103 + }
  104 +
  105 + @Test
  106 + public void testSetDefaultTenantProfile() throws Exception {
  107 + loginSysAdmin();
  108 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile 1");
  109 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  110 + TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString()+"/default", null, TenantProfile.class);
  111 + Assert.assertNotNull(defaultTenantProfile);
  112 + EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
  113 + Assert.assertNotNull(foundDefaultTenantProfile);
  114 + Assert.assertEquals(savedTenantProfile.getName(), foundDefaultTenantProfile.getName());
  115 + Assert.assertEquals(savedTenantProfile.getId(), foundDefaultTenantProfile.getId());
  116 + }
  117 +
  118 + @Test
  119 + public void testSaveTenantProfileWithEmptyName() throws Exception {
  120 + loginSysAdmin();
  121 + TenantProfile tenantProfile = new TenantProfile();
  122 + doPost("/api/tenantProfile", tenantProfile).andExpect(status().isBadRequest())
  123 + .andExpect(statusReason(containsString("Tenant profile name should be specified")));
  124 + }
  125 +
  126 + @Test
  127 + public void testSaveTenantProfileWithSameName() throws Exception {
  128 + loginSysAdmin();
  129 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  130 + doPost("/api/tenantProfile", tenantProfile).andExpect(status().isOk());
  131 + TenantProfile tenantProfile2 = this.createTenantProfile("Tenant Profile");
  132 + doPost("/api/tenantProfile", tenantProfile2).andExpect(status().isBadRequest())
  133 + .andExpect(statusReason(containsString("Tenant profile with such name already exists")));
  134 + }
  135 +
  136 + @Test
  137 + public void testSaveSameTenantProfileWithDifferentIsolatedTbRuleEngine() throws Exception {
  138 + loginSysAdmin();
  139 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  140 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  141 + savedTenantProfile.setIsolatedTbRuleEngine(true);
  142 + doPost("/api/tenantProfile", savedTenantProfile).andExpect(status().isBadRequest())
  143 + .andExpect(statusReason(containsString("Can't update isolatedTbRuleEngine property")));
  144 + }
  145 +
  146 + @Test
  147 + public void testSaveSameTenantProfileWithDifferentIsolatedTbCore() throws Exception {
  148 + loginSysAdmin();
  149 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  150 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  151 + savedTenantProfile.setIsolatedTbCore(true);
  152 + doPost("/api/tenantProfile", savedTenantProfile).andExpect(status().isBadRequest())
  153 + .andExpect(statusReason(containsString("Can't update isolatedTbCore property")));
  154 + }
  155 +
  156 + @Test
  157 + public void testDeleteTenantProfileWithExistingTenant() throws Exception {
  158 + loginSysAdmin();
  159 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  160 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  161 +
  162 + Tenant tenant = new Tenant();
  163 + tenant.setTitle("My tenant with tenant profile");
  164 + tenant.setTenantProfileId(savedTenantProfile.getId());
  165 + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  166 +
  167 + doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
  168 + .andExpect(status().isBadRequest())
  169 + .andExpect(statusReason(containsString("The tenant profile referenced by the tenants cannot be deleted")));
  170 +
  171 + doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
  172 + .andExpect(status().isOk());
  173 + }
  174 +
  175 + @Test
  176 + public void testDeleteTenantProfile() throws Exception {
  177 + loginSysAdmin();
  178 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile");
  179 + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
  180 +
  181 + doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
  182 + .andExpect(status().isOk());
  183 +
  184 + doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString())
  185 + .andExpect(status().isNotFound());
  186 + }
  187 +
  188 + @Test
  189 + public void testFindTenantProfiles() throws Exception {
  190 + loginSysAdmin();
  191 + List<TenantProfile> tenantProfiles = new ArrayList<>();
  192 + PageLink pageLink = new PageLink(17);
  193 + PageData<TenantProfile> pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
  194 + new TypeReference<PageData<TenantProfile>>(){}, pageLink);
  195 + Assert.assertFalse(pageData.hasNext());
  196 + Assert.assertEquals(1, pageData.getTotalElements());
  197 + tenantProfiles.addAll(pageData.getData());
  198 +
  199 + for (int i=0;i<28;i++) {
  200 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
  201 + tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
  202 + }
  203 +
  204 + List<TenantProfile> loadedTenantProfiles = new ArrayList<>();
  205 + pageLink = new PageLink(17);
  206 + do {
  207 + pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
  208 + new TypeReference<PageData<TenantProfile>>(){}, pageLink);
  209 + loadedTenantProfiles.addAll(pageData.getData());
  210 + if (pageData.hasNext()) {
  211 + pageLink = pageLink.nextPageLink();
  212 + }
  213 + } while (pageData.hasNext());
  214 +
  215 + Collections.sort(tenantProfiles, idComparator);
  216 + Collections.sort(loadedTenantProfiles, idComparator);
  217 +
  218 + Assert.assertEquals(tenantProfiles, loadedTenantProfiles);
  219 +
  220 + for (TenantProfile tenantProfile : loadedTenantProfiles) {
  221 + if (!tenantProfile.isDefault()) {
  222 + doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString())
  223 + .andExpect(status().isOk());
  224 + }
  225 + }
  226 +
  227 + pageLink = new PageLink(17);
  228 + pageData = doGetTypedWithPageLink("/api/tenantProfiles?",
  229 + new TypeReference<PageData<TenantProfile>>(){}, pageLink);
  230 + Assert.assertFalse(pageData.hasNext());
  231 + Assert.assertEquals(1, pageData.getTotalElements());
  232 + }
  233 +
  234 + @Test
  235 + public void testFindTenantProfileInfos() throws Exception {
  236 + loginSysAdmin();
  237 + List<TenantProfile> tenantProfiles = new ArrayList<>();
  238 + PageLink pageLink = new PageLink(17);
  239 + PageData<TenantProfile> tenantProfilePageData = doGetTypedWithPageLink("/api/tenantProfiles?",
  240 + new TypeReference<PageData<TenantProfile>>(){}, pageLink);
  241 + Assert.assertFalse(tenantProfilePageData.hasNext());
  242 + Assert.assertEquals(1, tenantProfilePageData.getTotalElements());
  243 + tenantProfiles.addAll(tenantProfilePageData.getData());
  244 +
  245 + for (int i=0;i<28;i++) {
  246 + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i);
  247 + tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class));
  248 + }
  249 +
  250 + List<EntityInfo> loadedTenantProfileInfos = new ArrayList<>();
  251 + pageLink = new PageLink(17);
  252 + PageData<EntityInfo> pageData;
  253 + do {
  254 + pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
  255 + new TypeReference<PageData<EntityInfo>>(){}, pageLink);
  256 + loadedTenantProfileInfos.addAll(pageData.getData());
  257 + if (pageData.hasNext()) {
  258 + pageLink = pageLink.nextPageLink();
  259 + }
  260 + } while (pageData.hasNext());
  261 +
  262 + Collections.sort(tenantProfiles, idComparator);
  263 + Collections.sort(loadedTenantProfileInfos, tenantProfileInfoIdComparator);
  264 +
  265 + List<EntityInfo> tenantProfileInfos = tenantProfiles.stream().map(tenantProfile -> new EntityInfo(tenantProfile.getId(),
  266 + tenantProfile.getName())).collect(Collectors.toList());
  267 +
  268 + Assert.assertEquals(tenantProfileInfos, loadedTenantProfileInfos);
  269 +
  270 + for (TenantProfile tenantProfile : tenantProfiles) {
  271 + if (!tenantProfile.isDefault()) {
  272 + doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString())
  273 + .andExpect(status().isOk());
  274 + }
  275 + }
  276 +
  277 + pageLink = new PageLink(17);
  278 + pageData = doGetTypedWithPageLink("/api/tenantProfileInfos?",
  279 + new TypeReference<PageData<EntityInfo>>(){}, pageLink);
  280 + Assert.assertFalse(pageData.hasNext());
  281 + Assert.assertEquals(1, pageData.getTotalElements());
  282 + }
  283 +
  284 + private TenantProfile createTenantProfile(String name) {
  285 + TenantProfile tenantProfile = new TenantProfile();
  286 + tenantProfile.setName(name);
  287 + tenantProfile.setDescription(name + " Test");
  288 + tenantProfile.setProfileData(new TenantProfileData());
  289 + tenantProfile.setDefault(false);
  290 + tenantProfile.setIsolatedTbCore(false);
  291 + tenantProfile.setIsolatedTbRuleEngine(false);
  292 + return tenantProfile;
  293 + }
  294 +}
... ...
... ... @@ -34,7 +34,7 @@ public class ControllerSqlTestSuite {
34 34
35 35 @ClassRule
36 36 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
37   - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
  37 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"),
38 38 "sql/hsql/drop-all-tables.sql",
39 39 "sql-test.properties");
40 40
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.sql;
  17 +
  18 +import org.thingsboard.server.controller.BaseDeviceProfileControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class DeviceProfileControllerSqlTest extends BaseDeviceProfileControllerTest {
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.sql;
  17 +
  18 +import org.thingsboard.server.controller.BaseTenantProfileControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class TenantProfileControllerSqlTest extends BaseTenantProfileControllerTest {
  23 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.mqtt;
  17 +
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  21 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  22 +import org.eclipse.paho.client.mqttv3.MqttException;
  23 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  24 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  25 +import org.junit.Assert;
  26 +import org.springframework.util.StringUtils;
  27 +import org.thingsboard.server.common.data.Device;
  28 +import org.thingsboard.server.common.data.DeviceProfile;
  29 +import org.thingsboard.server.common.data.DeviceProfileType;
  30 +import org.thingsboard.server.common.data.DeviceTransportType;
  31 +import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.TransportPayloadType;
  33 +import org.thingsboard.server.common.data.User;
  34 +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
  35 +import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
  36 +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
  37 +import org.thingsboard.server.common.data.security.Authority;
  38 +import org.thingsboard.server.common.data.security.DeviceCredentials;
  39 +import org.thingsboard.server.controller.AbstractControllerTest;
  40 +import org.thingsboard.server.gen.transport.TransportProtos;
  41 +
  42 +import java.util.ArrayList;
  43 +import java.util.List;
  44 +import java.util.concurrent.atomic.AtomicInteger;
  45 +
  46 +import static org.junit.Assert.assertEquals;
  47 +import static org.junit.Assert.assertNotNull;
  48 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  49 +
  50 +@Slf4j
  51 +public abstract class AbstractMqttIntegrationTest extends AbstractControllerTest {
  52 +
  53 + protected static final String MQTT_URL = "tcp://localhost:1883";
  54 +
  55 + private static final AtomicInteger atomicInteger = new AtomicInteger(2);
  56 +
  57 + protected Tenant savedTenant;
  58 + protected User tenantAdmin;
  59 +
  60 + protected Device savedDevice;
  61 + protected String accessToken;
  62 +
  63 + protected Device savedGateway;
  64 + protected String gatewayAccessToken;
  65 +
  66 + protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  67 + loginSysAdmin();
  68 +
  69 + Tenant tenant = new Tenant();
  70 + tenant.setTitle("My tenant");
  71 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  72 + Assert.assertNotNull(savedTenant);
  73 +
  74 + tenantAdmin = new User();
  75 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  76 + tenantAdmin.setTenantId(savedTenant.getId());
  77 + tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org");
  78 + tenantAdmin.setFirstName("Joe");
  79 + tenantAdmin.setLastName("Downs");
  80 +
  81 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  82 +
  83 + Device device = new Device();
  84 + device.setName(deviceName);
  85 + device.setType("default");
  86 +
  87 + Device gateway = new Device();
  88 + gateway.setName(gatewayName);
  89 + gateway.setType("default");
  90 + ObjectNode additionalInfo = mapper.createObjectNode();
  91 + additionalInfo.put("gateway", true);
  92 + gateway.setAdditionalInfo(additionalInfo);
  93 +
  94 + if (payloadType != null) {
  95 + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic);
  96 + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class);
  97 + device.setType(savedDeviceProfile.getName());
  98 + device.setDeviceProfileId(savedDeviceProfile.getId());
  99 + gateway.setType(savedDeviceProfile.getName());
  100 + gateway.setDeviceProfileId(savedDeviceProfile.getId());
  101 + }
  102 +
  103 + savedDevice = doPost("/api/device", device, Device.class);
  104 +
  105 + DeviceCredentials deviceCredentials =
  106 + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  107 +
  108 + savedGateway = doPost("/api/device", gateway, Device.class);
  109 +
  110 + DeviceCredentials gatewayCredentials =
  111 + doGet("/api/device/" + savedGateway.getId().getId().toString() + "/credentials", DeviceCredentials.class);
  112 +
  113 + assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
  114 + accessToken = deviceCredentials.getCredentialsId();
  115 + assertNotNull(accessToken);
  116 +
  117 + assertEquals(savedGateway.getId(), gatewayCredentials.getDeviceId());
  118 + gatewayAccessToken = gatewayCredentials.getCredentialsId();
  119 + assertNotNull(gatewayAccessToken);
  120 +
  121 + }
  122 +
  123 + protected void processAfterTest() throws Exception {
  124 + loginSysAdmin();
  125 + if (savedTenant != null) {
  126 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk());
  127 + }
  128 + }
  129 +
  130 + protected MqttAsyncClient getMqttAsyncClient(String accessToken) throws MqttException {
  131 + String clientId = MqttAsyncClient.generateClientId();
  132 + MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, clientId, new MemoryPersistence());
  133 +
  134 + MqttConnectOptions options = new MqttConnectOptions();
  135 + options.setUserName(accessToken);
  136 + client.connect(options).waitForCompletion();
  137 + return client;
  138 + }
  139 +
  140 + protected void publishMqttMsg(MqttAsyncClient client, byte[] payload, String topic) throws MqttException {
  141 + MqttMessage message = new MqttMessage();
  142 + message.setPayload(payload);
  143 + client.publish(topic, message);
  144 + }
  145 +
  146 + protected List<TransportProtos.KeyValueProto> getKvProtos(List<String> expectedKeys) {
  147 + List<TransportProtos.KeyValueProto> keyValueProtos = new ArrayList<>();
  148 + TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V);
  149 + TransportProtos.KeyValueProto boolKeyValueProto = getKeyValueProto(expectedKeys.get(1), "true", TransportProtos.KeyValueType.BOOLEAN_V);
  150 + TransportProtos.KeyValueProto dblKeyValueProto = getKeyValueProto(expectedKeys.get(2), "3.0", TransportProtos.KeyValueType.DOUBLE_V);
  151 + TransportProtos.KeyValueProto longKeyValueProto = getKeyValueProto(expectedKeys.get(3), "4", TransportProtos.KeyValueType.LONG_V);
  152 + TransportProtos.KeyValueProto jsonKeyValueProto = getKeyValueProto(expectedKeys.get(4), "{\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}", TransportProtos.KeyValueType.JSON_V);
  153 + keyValueProtos.add(strKeyValueProto);
  154 + keyValueProtos.add(boolKeyValueProto);
  155 + keyValueProtos.add(dblKeyValueProto);
  156 + keyValueProtos.add(longKeyValueProto);
  157 + keyValueProtos.add(jsonKeyValueProto);
  158 + return keyValueProtos;
  159 + }
  160 +
  161 + protected TransportProtos.KeyValueProto getKeyValueProto(String key, String strValue, TransportProtos.KeyValueType type) {
  162 + TransportProtos.KeyValueProto.Builder keyValueProtoBuilder = TransportProtos.KeyValueProto.newBuilder();
  163 + keyValueProtoBuilder.setKey(key);
  164 + keyValueProtoBuilder.setType(type);
  165 + switch (type) {
  166 + case BOOLEAN_V:
  167 + keyValueProtoBuilder.setBoolV(Boolean.parseBoolean(strValue));
  168 + break;
  169 + case LONG_V:
  170 + keyValueProtoBuilder.setLongV(Long.parseLong(strValue));
  171 + break;
  172 + case DOUBLE_V:
  173 + keyValueProtoBuilder.setDoubleV(Double.parseDouble(strValue));
  174 + break;
  175 + case STRING_V:
  176 + keyValueProtoBuilder.setStringV(strValue);
  177 + break;
  178 + case JSON_V:
  179 + keyValueProtoBuilder.setJsonV(strValue);
  180 + break;
  181 + }
  182 + return keyValueProtoBuilder.build();
  183 + }
  184 +
  185 + protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic) {
  186 + DeviceProfile deviceProfile = new DeviceProfile();
  187 + deviceProfile.setName(transportPayloadType.name());
  188 + deviceProfile.setType(DeviceProfileType.DEFAULT);
  189 + deviceProfile.setTransportType(DeviceTransportType.MQTT);
  190 + deviceProfile.setDescription(transportPayloadType.name() + " Test");
  191 + DeviceProfileData deviceProfileData = new DeviceProfileData();
  192 + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
  193 + MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration();
  194 + transportConfiguration.setTransportPayloadType(transportPayloadType);
  195 + if (!StringUtils.isEmpty(telemetryTopic)) {
  196 + transportConfiguration.setDeviceTelemetryTopic(telemetryTopic);
  197 + }
  198 + if (!StringUtils.isEmpty(attributesTopic)) {
  199 + transportConfiguration.setDeviceAttributesTopic(attributesTopic);
  200 + }
  201 + deviceProfileData.setTransportConfiguration(transportConfiguration);
  202 + deviceProfileData.setConfiguration(configuration);
  203 + deviceProfile.setProfileData(deviceProfileData);
  204 + deviceProfile.setDefault(false);
  205 + deviceProfile.setDefaultRuleChainId(null);
  206 + return deviceProfile;
  207 + }
  208 +
  209 + protected TransportProtos.PostAttributeMsg getPostAttributeMsg(List<String> expectedKeys) {
  210 + List<TransportProtos.KeyValueProto> kvProtos = getKvProtos(expectedKeys);
  211 + TransportProtos.PostAttributeMsg.Builder builder = TransportProtos.PostAttributeMsg.newBuilder();
  212 + builder.addAllKv(kvProtos);
  213 + return builder.build();
  214 + }
  215 +
  216 +}
... ...
... ... @@ -33,7 +33,7 @@ public class MqttNoSqlTestSuite {
33 33
34 34 @ClassRule
35 35 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
36   - Arrays.asList("sql/schema-entities-hsql.sql", "sql/system-data.sql"),
  36 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
37 37 "sql/hsql/drop-all-tables.sql",
38 38 "nosql-test.properties");
39 39
... ...
... ... @@ -27,13 +27,17 @@ import java.util.Arrays;
27 27 @RunWith(ClasspathSuite.class)
28 28 @ClasspathSuite.ClassnameFilters({
29 29 "org.thingsboard.server.mqtt.rpc.sql.*Test",
30   - "org.thingsboard.server.mqtt.telemetry.sql.*Test"
  30 + "org.thingsboard.server.mqtt.telemetry.timeseries.sql.*Test",
  31 + "org.thingsboard.server.mqtt.telemetry.attributes.sql.*Test",
  32 + "org.thingsboard.server.mqtt.attributes.updates.sql.*Test",
  33 + "org.thingsboard.server.mqtt.attributes.request.sql.*Test",
  34 + "org.thingsboard.server.mqtt.claim.sql.*Test"
31 35 })
32 36 public class MqttSqlTestSuite {
33 37
34 38 @ClassRule
35 39 public static CustomSqlUnit sqlUnit = new CustomSqlUnit(
36   - Arrays.asList("sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
  40 + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/system-data.sql"),
37 41 "sql/hsql/drop-all-tables.sql",
38 42 "sql-test.properties");
39 43
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.mqtt.attributes;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  20 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  21 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  22 +import org.thingsboard.server.common.data.TransportPayloadType;
  23 +import org.thingsboard.server.gen.transport.TransportProtos;
  24 +import org.thingsboard.server.mqtt.AbstractMqttIntegrationTest;
  25 +
  26 +import java.util.ArrayList;
  27 +import java.util.List;
  28 +import java.util.concurrent.CountDownLatch;
  29 +
  30 +@Slf4j
  31 +public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest {
  32 +
  33 + protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," +
  34 + "\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}";
  35 +
  36 + protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception {
  37 + super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic);
  38 + }
  39 +
  40 + protected void processAfterTest() throws Exception {
  41 + super.processAfterTest();
  42 + }
  43 +
  44 + protected List<TransportProtos.TsKvProto> getTsKvProtoList() {
  45 + TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V);
  46 + TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V);
  47 + TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V);
  48 + TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V);
  49 + TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V);
  50 + List<TransportProtos.TsKvProto> tsKvProtoList = new ArrayList<>();
  51 + tsKvProtoList.add(tsKvProtoAttribute1);
  52 + tsKvProtoList.add(tsKvProtoAttribute2);
  53 + tsKvProtoList.add(tsKvProtoAttribute3);
  54 + tsKvProtoList.add(tsKvProtoAttribute4);
  55 + tsKvProtoList.add(tsKvProtoAttribute5);
  56 + return tsKvProtoList;
  57 + }
  58 +
  59 +
  60 + protected TransportProtos.TsKvProto getTsKvProto(String key, String value, TransportProtos.KeyValueType keyValueType) {
  61 + TransportProtos.TsKvProto.Builder tsKvProtoBuilder = TransportProtos.TsKvProto.newBuilder();
  62 + TransportProtos.KeyValueProto keyValueProto = getKeyValueProto(key, value, keyValueType);
  63 + tsKvProtoBuilder.setKv(keyValueProto);
  64 + return tsKvProtoBuilder.build();
  65 + }
  66 +
  67 + protected TestMqttCallback getTestMqttCallback() {
  68 + CountDownLatch latch = new CountDownLatch(1);
  69 + return new TestMqttCallback(latch);
  70 + }
  71 +
  72 + protected static class TestMqttCallback implements MqttCallback {
  73 +
  74 + private final CountDownLatch latch;
  75 + private Integer qoS;
  76 + private byte[] payloadBytes;
  77 +
  78 + TestMqttCallback(CountDownLatch latch) {
  79 + this.latch = latch;
  80 + }
  81 +
  82 + public int getQoS() {
  83 + return qoS;
  84 + }
  85 +
  86 + public byte[] getPayloadBytes() {
  87 + return payloadBytes;
  88 + }
  89 +
  90 + public CountDownLatch getLatch() {
  91 + return latch;
  92 + }
  93 +
  94 + @Override
  95 + public void connectionLost(Throwable throwable) {
  96 + }
  97 +
  98 + @Override
  99 + public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception {
  100 + qoS = mqttMessage.getQos();
  101 + payloadBytes = mqttMessage.getPayload();
  102 + latch.countDown();
  103 + }
  104 +
  105 + @Override
  106 + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
  107 +
  108 + }
  109 + }
  110 +
  111 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.mqtt.attributes.request;
  17 +
  18 +import com.google.protobuf.InvalidProtocolBufferException;
  19 +import io.netty.handler.codec.mqtt.MqttQoS;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  22 +import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
  23 +import org.eclipse.paho.client.mqttv3.MqttCallback;
  24 +import org.eclipse.paho.client.mqttv3.MqttException;
  25 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  26 +import org.junit.After;
  27 +import org.junit.Before;
  28 +import org.junit.Test;
  29 +import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.device.profile.MqttTopics;
  31 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  32 +import org.thingsboard.server.mqtt.attributes.AbstractMqttAttributesIntegrationTest;
  33 +
  34 +import java.nio.charset.StandardCharsets;
  35 +import java.util.concurrent.CountDownLatch;
  36 +import java.util.concurrent.TimeUnit;
  37 +
  38 +import static org.junit.Assert.assertEquals;
  39 +import static org.junit.Assert.assertFalse;
  40 +import static org.junit.Assert.assertNotNull;
  41 +import static org.junit.Assert.assertTrue;
  42 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  43 +
  44 +@Slf4j
  45 +public abstract class AbstractMqttAttributesRequestIntegrationTest extends AbstractMqttAttributesIntegrationTest {
  46 +
  47 + @Before
  48 + public void beforeTest() throws Exception {
  49 + processBeforeTest("Test Request attribute values from the server", "Gateway Test Request attribute values from the server", null, null, null);
  50 + }
  51 +
  52 + @After
  53 + public void afterTest() throws Exception {
  54 + processAfterTest();
  55 + }
  56 +
  57 + @Test
  58 + public void testRequestAttributesValuesFromTheServer() throws Exception {
  59 + processTestRequestAttributesValuesFromTheServer();
  60 + }
  61 +
  62 + @Test
  63 + public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
  64 + processTestGatewayRequestAttributesValuesFromTheServer();
  65 + }
  66 +
  67 + protected void processTestRequestAttributesValuesFromTheServer() throws Exception {
  68 +
  69 + MqttAsyncClient client = getMqttAsyncClient(accessToken);
  70 +
  71 + postAttributesAndSubscribeToTopic(savedDevice, client);
  72 +
  73 + Thread.sleep(1000);
  74 +
  75 + TestMqttCallback callback = getTestMqttCallback();
  76 + client.setCallback(callback);
  77 +
  78 + validateResponse(client, callback.getLatch(), callback);
  79 + }
  80 +
  81 + protected void processTestGatewayRequestAttributesValuesFromTheServer() throws Exception {
  82 +
  83 + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken);
  84 +
  85 + postGatewayDeviceClientAttributes(client);
  86 +
  87 + Thread.sleep(1000);
  88 +
  89 + Device savedDevice = doGet("/api/tenant/devices?deviceName=" + "Gateway Device Request Attributes", Device.class);
  90 + assertNotNull(savedDevice);
  91 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  92 +
  93 + Thread.sleep(1000);
  94 +
  95 + client.subscribe(MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC, MqttQoS.AT_LEAST_ONCE.value());
  96 +
  97 + TestMqttCallback clientAttributesCallback = getTestMqttCallback();
  98 + client.setCallback(clientAttributesCallback);
  99 + validateClientResponseGateway(client, clientAttributesCallback);
  100 +
  101 + TestMqttCallback sharedAttributesCallback = getTestMqttCallback();
  102 + client.setCallback(sharedAttributesCallback);
  103 + validateSharedResponseGateway(client, sharedAttributesCallback);
  104 + }
  105 +
  106 + protected void postAttributesAndSubscribeToTopic(Device savedDevice, MqttAsyncClient client) throws Exception {
  107 + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
  108 + client.publish(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, new MqttMessage(POST_ATTRIBUTES_PAYLOAD.getBytes()));
  109 + client.subscribe(MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttQoS.AT_MOST_ONCE.value());
  110 + }
  111 +
  112 + protected void postGatewayDeviceClientAttributes(MqttAsyncClient client) throws Exception {
  113 + String postClientAttributes = "{\"" + "Gateway Device Request Attributes" + "\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  114 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_TOPIC, new MqttMessage(postClientAttributes.getBytes()));
  115 + }
  116 +
  117 + protected void validateResponse(MqttAsyncClient client, CountDownLatch latch, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  118 + String keys = "attribute1,attribute2,attribute3,attribute4,attribute5";
  119 + String payloadStr = "{\"clientKeys\":\"" + keys + "\", \"sharedKeys\":\"" + keys + "\"}";
  120 + MqttMessage mqttMessage = new MqttMessage();
  121 + mqttMessage.setPayload(payloadStr.getBytes());
  122 + client.publish(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX + "1", mqttMessage);
  123 + latch.await(3, TimeUnit.SECONDS);
  124 + assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
  125 + String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  126 + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
  127 + }
  128 +
  129 + protected void validateClientResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  130 + String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": true, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
  131 + MqttMessage mqttMessage = new MqttMessage();
  132 + mqttMessage.setPayload(payloadStr.getBytes());
  133 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
  134 + callback.getLatch().await(3, TimeUnit.SECONDS);
  135 + assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
  136 + String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  137 + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
  138 + }
  139 +
  140 + protected void validateSharedResponseGateway(MqttAsyncClient client, TestMqttCallback callback) throws MqttException, InterruptedException, InvalidProtocolBufferException {
  141 + String payloadStr = "{\"id\": 1, \"device\": \"" + "Gateway Device Request Attributes" + "\", \"client\": false, \"keys\": [\"attribute1\", \"attribute2\", \"attribute3\", \"attribute4\", \"attribute5\"]}";
  142 + MqttMessage mqttMessage = new MqttMessage();
  143 + mqttMessage.setPayload(payloadStr.getBytes());
  144 + client.publish(MqttTopics.GATEWAY_ATTRIBUTES_REQUEST_TOPIC, mqttMessage);
  145 + callback.getLatch().await(3, TimeUnit.SECONDS);
  146 + assertEquals(MqttQoS.AT_LEAST_ONCE.value(), callback.getQoS());
  147 + String expectedRequestPayload = "{\"id\":1,\"device\":\"" + "Gateway Device Request Attributes" + "\",\"values\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}";
  148 + assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(callback.getPayloadBytes(), StandardCharsets.UTF_8)));
  149 + }
  150 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.mqtt.attributes.request;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.junit.After;
  20 +import org.junit.Before;
  21 +import org.junit.Ignore;
  22 +import org.junit.Test;
  23 +import org.thingsboard.server.common.data.TransportPayloadType;
  24 +
  25 +import static org.junit.Assert.assertEquals;
  26 +import static org.junit.Assert.assertNotNull;
  27 +import static org.junit.Assert.assertTrue;
  28 +
  29 +@Slf4j
  30 +public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest {
  31 +
  32 + @Before
  33 + public void beforeTest() throws Exception {
  34 + processBeforeTest("Test Request attribute values from the server json", "Gateway Test Request attribute values from the server json", TransportPayloadType.JSON, null, null);
  35 + }
  36 +
  37 + @After
  38 + public void afterTest() throws Exception {
  39 + processAfterTest();
  40 + }
  41 +
  42 + @Test
  43 + public void testRequestAttributesValuesFromTheServer() throws Exception {
  44 + processTestRequestAttributesValuesFromTheServer();
  45 + }
  46 +
  47 + @Test
  48 + public void testRequestAttributesValuesFromTheServerGateway() throws Exception {
  49 + processTestGatewayRequestAttributesValuesFromTheServer();
  50 + }
  51 +}
... ...