Browse Source

Merge remote-tracking branch 'origin/master' into feature/housekeeper

pull/10201/head
ViacheslavKlimov 3 years ago
parent
commit
00f8fd2c35
  1. 2
      .github/ISSUE_TEMPLATE/---bug-report.md
  2. 2
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 2
      .github/ISSUE_TEMPLATE/question.md
  4. 2
      application/pom.xml
  5. 111
      application/src/main/data/json/demo/dashboards/rule_engine_statistics.json
  6. 32
      application/src/main/data/upgrade/3.6.3/schema_update.sql
  7. 6
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  8. 3
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  9. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  10. 402
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  11. 5
      application/src/main/java/org/thingsboard/server/config/WebConfig.java
  12. 6
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  13. 9
      application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java
  14. 31
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  15. 41
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  16. 24
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  17. 12
      application/src/main/java/org/thingsboard/server/controller/AuditLogController.java
  18. 8
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  19. 2
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  20. 63
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  21. 11
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  22. 27
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  23. 53
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  24. 2
      application/src/main/java/org/thingsboard/server/controller/EdgeEventController.java
  25. 1
      application/src/main/java/org/thingsboard/server/controller/EntityQueryController.java
  26. 25
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  27. 18
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  28. 7
      application/src/main/java/org/thingsboard/server/controller/EventController.java
  29. 3
      application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java
  30. 19
      application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
  31. 8
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  32. 21
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  33. 55
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  34. 9
      application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
  35. 7
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  36. 8
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  37. 75
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  38. 41
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  39. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  40. 32
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/oauth2/OAuth2MsgConstructor.java
  41. 19
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java
  42. 18
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java
  43. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java
  44. 56
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java
  45. 248
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  46. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java
  47. 20
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java
  48. 63
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java
  49. 27
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java
  50. 1
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java
  51. 14
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
  52. 11
      application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java
  53. 2
      application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsDeletionTaskProcessor.java
  54. 42
      application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java
  55. 8
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  56. 10
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  57. 21
      application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerStats.java
  58. 4
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  59. 47
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  60. 135
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  61. 6
      application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
  62. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  63. 76
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  64. 1
      application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java
  65. 2
      application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java
  66. 6
      application/src/main/resources/thingsboard.yml
  67. 53
      application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
  68. 21
      application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java
  69. 38
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  70. 51
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  71. 2
      application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java
  72. 3
      application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
  73. 15
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  74. 113
      application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java
  75. 6
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  76. 25
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java
  77. 40
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java
  78. 80
      application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
  79. 5
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java
  80. 4
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java
  81. 3
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java
  82. 4
      application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java
  83. 3
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java
  84. 3
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java
  85. 3
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java
  86. 2
      application/src/test/resources/logback-test.xml
  87. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  88. 1
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java
  89. 35
      common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java
  90. 3
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  91. 11
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java
  92. 3
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  93. 4
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java
  94. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  95. 43
      common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java
  96. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java
  97. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java
  98. 15
      common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java
  99. 2
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java
  100. 2
      common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Params.java

2
.github/ISSUE_TEMPLATE/---bug-report.md

@ -3,7 +3,7 @@ name: "\U0001F41E Bug report"
about: Create a report to help us improve about: Create a report to help us improve
title: "Your title here" title: "Your title here"
labels: ['bug', 'unconfirmed'] labels: ['bug', 'unconfirmed']
assignees: AndriichnekoDm assignees: Ultrazombie
--- ---

2
.github/ISSUE_TEMPLATE/feature_request.md

@ -3,7 +3,7 @@ name: Feature request
about: Suggest an idea for this project about: Suggest an idea for this project
title: "Your title here" title: "Your title here"
labels: ['feature'] labels: ['feature']
assignees: 'AndriichnekoDm' assignees: 'Ultrazombie'
--- ---

2
.github/ISSUE_TEMPLATE/question.md

@ -3,7 +3,7 @@ name: Question
about: Describe your questions in detail about: Describe your questions in detail
title: "Your title here" title: "Your title here"
labels: ['question'] labels: ['question']
assignees: 'AndriichnekoDm' assignees: 'Ultrazombie'
--- ---

2
application/pom.xml

@ -262,7 +262,7 @@
<artifactId>opensmpp-core</artifactId> <artifactId>opensmpp-core</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springdoc</groupId> <groupId>org.thingsboard</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency> </dependency>
<dependency> <dependency>

111
application/src/main/data/json/demo/dashboards/rule_engine_statistics.json

@ -13,6 +13,7 @@
"datasources": [ "datasources": [
{ {
"type": "entity", "type": "entity",
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
"dataKeys": [ "dataKeys": [
{ {
"name": "ruleEngineException", "name": "ruleEngineException",
@ -51,7 +52,59 @@
"_hash": 0.7255162989552142 "_hash": 0.7255162989552142
} }
], ],
"entityAliasId": "140f23dd-e3a0-ed98-6189-03c49d2d8018" "alarmFilterConfig": {
"statusList": [
"ACTIVE"
]
},
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#ffc107",
"settings": {
"show": false,
"order": null,
"useCellStyleFunction": false,
"cellStyleFunction": "",
"useCellContentFunction": false,
"cellContentFunction": "",
"defaultColumnVisibility": "visible",
"columnSelectionToDisplay": "enabled"
},
"_hash": 0.8104572478982748,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#607d8b",
"settings": {
"show": false,
"order": null,
"useCellStyleFunction": false,
"cellStyleFunction": "",
"useCellContentFunction": false,
"cellContentFunction": "",
"defaultColumnVisibility": "visible",
"columnSelectionToDisplay": "enabled"
},
"_hash": 0.38329217099945034,
"aggregationType": null,
"units": null,
"decimals": null,
"funcBody": null,
"usePostProcessing": null,
"postFuncBody": null
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -71,7 +124,9 @@
"settings": { "settings": {
"showTimestamp": true, "showTimestamp": true,
"displayPagination": true, "displayPagination": true,
"defaultPageSize": 10 "defaultPageSize": 10,
"enableSearch": true,
"enableSelectColumnDisplay": true
}, },
"title": "Exceptions", "title": "Exceptions",
"dropShadow": true, "dropShadow": true,
@ -89,7 +144,10 @@
"iconColor": "rgba(0, 0, 0, 0.87)", "iconColor": "rgba(0, 0, 0, 0.87)",
"iconSize": "24px", "iconSize": "24px",
"titleTooltip": "", "titleTooltip": "",
"displayTimewindow": true "displayTimewindow": true,
"configMode": "basic",
"titleFont": null,
"titleColor": null
}, },
"id": "5eb79712-5c24-3060-7e4f-6af36b8f842d", "id": "5eb79712-5c24-3060-7e4f-6af36b8f842d",
"typeFullFqn": "system.cards.timeseries_table" "typeFullFqn": "system.cards.timeseries_table"
@ -329,7 +387,25 @@
"statusList": [ "statusList": [
"ACTIVE" "ACTIVE"
] ]
} },
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#ffc107",
"settings": {},
"_hash": 0.8012481564934415
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#607d8b",
"settings": {},
"_hash": 0.0724871638610094
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -724,7 +800,25 @@
"statusList": [ "statusList": [
"ACTIVE" "ACTIVE"
] ]
} },
"latestDataKeys": [
{
"name": "queueName",
"type": "entityField",
"label": "Queue name",
"color": "#f44336",
"settings": {},
"_hash": 0.7242351292118758
},
{
"name": "serviceId",
"type": "entityField",
"label": "Service Id",
"color": "#ffc107",
"settings": {},
"_hash": 0.3347262075244206
}
]
} }
], ],
"timewindow": { "timewindow": {
@ -1004,12 +1098,9 @@
"id": "140f23dd-e3a0-ed98-6189-03c49d2d8018", "id": "140f23dd-e3a0-ed98-6189-03c49d2d8018",
"alias": "TbServiceQueues", "alias": "TbServiceQueues",
"filter": { "filter": {
"type": "assetType", "type": "entityType",
"resolveMultiple": true, "resolveMultiple": true,
"assetNameFilter": "", "entityType": "QUEUE_STATS"
"assetTypes": [
"TbServiceQueue"
]
} }
} }
}, },

32
application/src/main/data/upgrade/3.6.3/schema_update.sql

@ -14,7 +14,6 @@
-- limitations under the License. -- limitations under the License.
-- --
-- create new attribute_kv table schema -- create new attribute_kv table schema
DO DO
$$ $$
@ -105,4 +104,33 @@ EXCEPTION
ROLLBACK; ROLLBACK;
RAISE EXCEPTION 'Error during COPY: %', SQLERRM; RAISE EXCEPTION 'Error during COPY: %', SQLERRM;
END END
$$; $$;
-- OAUTH2 PARAMS ALTER TABLE START
ALTER TABLE oauth2_params
ADD COLUMN IF NOT EXISTS edge_enabled boolean DEFAULT false;
-- OAUTH2 PARAMS ALTER TABLE END
-- QUEUE STATS UPDATE START
CREATE TABLE IF NOT EXISTS queue_stats (
id uuid NOT NULL CONSTRAINT queue_stats_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
queue_name varchar(255) NOT NULL,
service_id varchar(255) NOT NULL,
CONSTRAINT queue_stats_name_unq_key UNIQUE (tenant_id, queue_name, service_id)
);
INSERT INTO queue_stats
SELECT id, created_time, tenant_id, substring(name FROM 1 FOR position('_' IN name) - 1) AS queue_name,
substring(name FROM position('_' IN name) + 1) AS service_id
FROM asset
WHERE type = 'TbServiceQueue' and name LIKE '%\_%';
DELETE FROM asset WHERE type='TbServiceQueue';
DELETE FROM asset_profile WHERE name ='TbServiceQueue';
-- QUEUE STATS UPDATE END

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

@ -79,6 +79,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
@ -447,6 +448,11 @@ public class ActorSystemContext {
@Getter @Getter
private QueueService queueService; private QueueService queueService;
@Lazy
@Autowired(required = false)
@Getter
private QueueStatsService queueStatsService;
@Lazy @Lazy
@Autowired(required = false) @Autowired(required = false)
@Getter @Getter

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

@ -21,6 +21,7 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.Nullable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
@ -68,7 +69,6 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceEdgeUpdateMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.common.util.KvProtoUtil; import org.thingsboard.server.common.util.KvProtoUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
@ -97,7 +97,6 @@ import org.thingsboard.server.service.rpc.RpcSubmitStrategy;
import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DefaultDeviceStateService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import jakarta.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;

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

@ -96,6 +96,7 @@ import org.thingsboard.server.dao.notification.NotificationTargetService;
import org.thingsboard.server.dao.notification.NotificationTemplateService; import org.thingsboard.server.dao.notification.NotificationTemplateService;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
@ -768,6 +769,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getQueueService(); return mainCtx.getQueueService();
} }
@Override
public QueueStatsService getQueueStatsService() {
return mainCtx.getQueueStatsService();
}
@Override @Override
public EventLoopGroup getSharedEventLoop() { public EventLoopGroup getSharedEventLoop() {
return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup(); return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();

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

@ -15,12 +15,17 @@
*/ */
package org.thingsboard.server.config; package org.thingsboard.server.config;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.v3.core.converter.AnnotatedType; import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverter;
import io.swagger.v3.core.converter.ModelConverters; import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.PathItem; import io.swagger.v3.oas.models.PathItem;
import io.swagger.v3.oas.models.Paths;
import io.swagger.v3.oas.models.examples.Example; import io.swagger.v3.oas.models.examples.Example;
import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.Info;
@ -33,29 +38,42 @@ import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses; import io.swagger.v3.oas.models.responses.ApiResponses;
import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme; import io.swagger.v3.oas.models.security.SecurityScheme;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.oas.models.tags.Tag; import io.swagger.v3.oas.models.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.customizers.OpenApiCustomizer; import org.springdoc.core.customizers.OpenApiCustomizer;
import org.springdoc.core.customizers.OperationCustomizer;
import org.springdoc.core.customizers.RouterOperationCustomizer;
import org.springdoc.core.discoverer.SpringDocParameterNameDiscoverer;
import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.properties.SpringDocConfigProperties;
import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.properties.SwaggerUiConfigProperties;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestParam;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse; import org.thingsboard.server.exception.ThingsboardCredentialsExpiredResponse;
import org.thingsboard.server.exception.ThingsboardErrorResponse; import org.thingsboard.server.exception.ThingsboardErrorResponse;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import org.thingsboard.server.service.security.auth.rest.LoginResponse; import org.thingsboard.server.service.security.auth.rest.LoginResponse;
import java.util.Collections; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
@ -67,8 +85,14 @@ public class SwaggerConfiguration {
public static final String LOGIN_ENDPOINT = "/api/auth/login"; public static final String LOGIN_ENDPOINT = "/api/auth/login";
private static final ApiResponses loginResponses = loginResponses();
private static final ApiResponses defaultErrorResponses = defaultErrorResponses(false);
private static final ApiResponses defaultPostErrorResponses = defaultErrorResponses(true);
@Value("${swagger.api_path:/api/**}") @Value("${swagger.api_path:/api/**}")
private String apiPath; private String apiPath;
@Value("${swagger.security_path_regex}")
private String securityPathRegex;
@Value("${swagger.non_security_path_regex}") @Value("${swagger.non_security_path_regex}")
private String nonSecurityPathRegex; private String nonSecurityPathRegex;
@Value("${swagger.title}") @Value("${swagger.title}")
@ -90,6 +114,7 @@ public class SwaggerConfiguration {
@Value("${app.version:unknown}") @Value("${app.version:unknown}")
private String appVersion; private String appVersion;
@Bean @Bean
public OpenAPI thingsboardApi() { public OpenAPI thingsboardApi() {
Contact contact = new Contact() Contact contact = new Contact()
@ -115,93 +140,24 @@ public class SwaggerConfiguration {
SecurityScheme securityScheme = new SecurityScheme() SecurityScheme securityScheme = new SecurityScheme()
.type(SecurityScheme.Type.HTTP) .type(SecurityScheme.Type.HTTP)
.scheme("bearer") .description("Enter Username / Password")
.bearerFormat("JWT") .scheme("loginPassword")
.in(SecurityScheme.In.HEADER) .bearerFormat("/api/auth/login|X-Authorization");
.description("Enter Username / Password");
var openApi = new OpenAPI() var openApi = new OpenAPI()
.addServersItem(new Server().url("/").description("Default Server URL"))
.components(new Components().addSecuritySchemes("HTTP login form", securityScheme)) .components(new Components().addSecuritySchemes("HTTP login form", securityScheme))
.info(info); .info(info);
addDefaultSchemas(openApi);
addLoginOperation(openApi); addLoginOperation(openApi);
return openApi; return openApi;
} }
public void addLoginOperation(OpenAPI openAPI) {
openAPI.getComponents()
.addSchemas("LoginRequest", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginRequest.class)).schema)
.addSchemas("LoginResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginResponse.class)).schema)
.addSchemas("ThingsboardErrorResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardErrorResponse.class)).schema)
.addSchemas("ThingsboardCredentialsExpiredResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardCredentialsExpiredResponse.class)).schema);
var operation = new Operation();
operation.summary("Login method to get user JWT token data");
operation.description("Login method used to authenticate user and get JWT token data.\n\nValue of the response **token** " +
"field can be used as **X-Authorization** header value:\n\n`X-Authorization: Bearer $JWT_TOKEN_VALUE`.");
var requestBody = new RequestBody().content(new Content().addMediaType(APPLICATION_JSON_VALUE,
new MediaType().schema(new Schema<LoginRequest>().$ref("#/components/schemas/LoginRequest"))));
operation.requestBody(requestBody);
operation.responses(getResponses());
operation.addTagsItem("login-endpoint");
var pathItem = new PathItem().post(operation);
openAPI.path(LOGIN_ENDPOINT, pathItem);
}
private ApiResponses getResponses() {
ApiResponses apiResponses = new ApiResponses();
apiResponses.addApiResponse("200", new ApiResponse().description("OK")
.content(new Content().addMediaType(APPLICATION_JSON_VALUE,
new MediaType().schema(new Schema<LoginResponse>().$ref("#/components/schemas/LoginResponse")))));
ApiResponse unauthorizedResponse = new ApiResponse().description("Unauthorized");
Content content = new Content();
MediaType mediaType = new MediaType().schema(new Schema<ThingsboardErrorResponse>().$ref("#/components/schemas/ThingsboardErrorResponse"));
Map<String, Example> examples = Map.of(
"bad-credentials", errorExample("Bad credentials",
ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
"token-expired", errorExample("JWT token expired",
ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
"account-disabled", errorExample("Disabled account",
ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
"account-locked", errorExample("Locked account",
ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
"authentication-failed", errorExample("General authentication error",
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
);
mediaType.setExamples(examples);
content.addMediaType(APPLICATION_JSON_VALUE, mediaType);
unauthorizedResponse.setContent(content);
apiResponses.addApiResponse("401", unauthorizedResponse);
ApiResponse expiredCredentialsResponse = new ApiResponse().description("Unauthorized (**Expired credentials**)");
Content expiredContent = new Content();
MediaType expiredMediaType = new MediaType().schema(new Schema<ThingsboardCredentialsExpiredResponse>().$ref("#/components/schemas/ThingsboardCredentialsExpiredResponse"));
expiredMediaType.addExamples("credentials-expired", errorExample("Expired credentials",
ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30))));
expiredContent.addMediaType(APPLICATION_JSON_VALUE, expiredMediaType);
expiredCredentialsResponse.setContent(expiredContent);
apiResponses.addApiResponse("401 ", expiredCredentialsResponse);
return apiResponses;
}
private Example errorExample(String summary, ThingsboardErrorResponse example) {
return new Example()
.summary(summary)
.value(example);
}
@Bean @Bean
public GroupedOpenApi groupedApi() { @Primary
return GroupedOpenApi.builder() public SpringDocConfigProperties springDocConfig(SpringDocConfigProperties springDocProperties) {
.group("thingsboard") springDocProperties.getApiDocs().setVersion(SpringDocConfigProperties.ApiDocs.OpenApiVersion.OPENAPI_3_1);
.pathsToMatch(apiPath) springDocProperties.setRemoveBrokenReferenceDefinitions(false);
.addOpenApiCustomizer(customOpenApiCustomizer()) return springDocProperties;
.build();
} }
@Bean @Bean
@ -232,35 +188,166 @@ public class SwaggerConfiguration {
return uiProperties; return uiProperties;
} }
public OpenApiCustomizer customOpenApiCustomizer() { private void addLoginOperation(OpenAPI openAPI) {
var loginForm = new SecurityRequirement().addList("HTTP login form"); var operation = new Operation();
return openAPI -> openAPI.getPaths().entrySet().stream().peek(entry -> { operation.summary("Login method to get user JWT token data");
securityCustomization(loginForm, entry); operation.description("""
if (!entry.getKey().equals(LOGIN_ENDPOINT)) { Login method used to authenticate user and get JWT token data.
defaultErrorResponsesCustomization(entry.getValue());
Value of the response **token** field can be used as **X-Authorization** header value:
`X-Authorization: Bearer $JWT_TOKEN_VALUE`.""");
var requestBody = new RequestBody().description("Login request")
.content(new Content().addMediaType(APPLICATION_JSON_VALUE,
new MediaType().schema(new Schema<LoginRequest>().$ref("#/components/schemas/LoginRequest"))));
operation.requestBody(requestBody);
operation.responses(loginResponses);
operation.addTagsItem("login-endpoint");
var pathItem = new PathItem().post(operation);
openAPI.path(LOGIN_ENDPOINT, pathItem);
}
@Bean
public GroupedOpenApi groupedApi(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
return GroupedOpenApi.builder()
.group("thingsboard")
.pathsToMatch(apiPath)
.addRouterOperationCustomizer(routerOperationCustomizer(localSpringDocParameterNameDiscoverer))
.addOperationCustomizer(operationCustomizer())
.addOpenApiCustomizer(customOpenApiCustomizer())
.build();
}
@Bean
@Lazy(false)
ModelConverter mapAwareConverter() {
return (type, context, chain) -> {
if (chain.hasNext()) {
Schema schema = chain.next().resolve(type, context, chain);
JavaType javaType = Json.mapper().constructType(type.getType());
if (javaType != null) {
Class<?> cls = javaType.getRawClass();
if (Map.class.isAssignableFrom(cls)) {
if (schema != null && schema.getProperties() != null) {
schema.getProperties().remove("empty");
if (schema.getProperties().isEmpty()) {
schema.setProperties(null);
}
}
}
}
return schema;
} else {
return null;
} }
}).map(this::tagsCustomization).forEach(openAPI::addTagsItem); };
} }
private Tag tagsCustomization(Map.Entry<String, PathItem> entry) { private void addDefaultSchemas(OpenAPI openAPI) {
var operations = entry.getValue().readOperationsMap().values(); var jsonNodeSchema = ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(JsonNode.class)).schema;
var tagItem = operations.stream().findAny().get().getTags().get(0); jsonNodeSchema.setType("any");
return tagFromTagItem(tagItem); //noinspection unchecked
jsonNodeSchema.setExamples(List.of(JacksonUtil.newObjectNode()));
jsonNodeSchema.setDescription("A value representing the any type (object or primitive)");
openAPI.getComponents()
.addSchemas("JsonNode", jsonNodeSchema)
.addSchemas("LoginRequest", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginRequest.class)).schema)
.addSchemas("LoginResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(LoginResponse.class)).schema)
.addSchemas("ThingsboardErrorResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardErrorResponse.class)).schema)
.addSchemas("ThingsboardCredentialsExpiredResponse", ModelConverters.getInstance().readAllAsResolvedSchema(new AnnotatedType().type(ThingsboardCredentialsExpiredResponse.class)).schema);
} }
private void defaultErrorResponsesCustomization(PathItem pathItem) { private RouterOperationCustomizer routerOperationCustomizer(SpringDocParameterNameDiscoverer localSpringDocParameterNameDiscoverer) {
pathItem.readOperationsMap().forEach(((httpMethod, operation) -> { return (routerOperation, handlerMethod) -> {
operation.setResponses(getResponses(operation.getResponses(), httpMethod.equals(PathItem.HttpMethod.POST))); String[] pNames = localSpringDocParameterNameDiscoverer.getParameterNames(handlerMethod.getMethod());
})); String[] reflectionParametersNames = Arrays.stream(handlerMethod.getMethod().getParameters()).map(java.lang.reflect.Parameter::getName).toArray(String[]::new);
if (pNames == null || Arrays.stream(pNames).anyMatch(Objects::isNull))
pNames = reflectionParametersNames;
MethodParameter[] parameters = handlerMethod.getMethodParameters();
List<String> requestParams = new ArrayList<>();
for (var i = 0; i < parameters.length; i++) {
var methodParameter = parameters[i];
RequestParam requestParam = methodParameter.getParameterAnnotation(RequestParam.class);
if (requestParam != null) {
String pName = StringUtils.isNotBlank(requestParam.value()) ? requestParam.value() :
pNames[i];
if (StringUtils.isNotBlank(pName)) {
requestParams.add(pName);
}
}
}
if (!requestParams.isEmpty()) {
var path = routerOperation.getPath() + "{?" + String.join(",", requestParams) + "}";
routerOperation.setPath(path);
}
return routerOperation;
};
} }
private void securityCustomization(SecurityRequirement loginForm, Map.Entry<String, PathItem> entry) { private OperationCustomizer operationCustomizer() {
if (!(entry.getKey().matches(nonSecurityPathRegex) || entry.getKey().equals(LOGIN_ENDPOINT))) { return (operation, handlerMethod) -> {
entry.getValue() if (StringUtils.isBlank(operation.getSummary())) {
.readOperationsMap() operation.setSummary(operation.getOperationId());
.values() }
.forEach(operation -> operation.addSecurityItem(loginForm)); return operation;
};
}
private OpenApiCustomizer customOpenApiCustomizer() {
var loginForm = new SecurityRequirement().addList("HTTP login form", Arrays.asList(
Authority.SYS_ADMIN.name(),
Authority.TENANT_ADMIN.name(),
Authority.CUSTOMER_USER.name()
));
return openAPI -> {
var paths = openAPI.getPaths();
paths.entrySet().stream().peek(entry -> {
securityCustomization(loginForm, entry);
if (!entry.getKey().equals(LOGIN_ENDPOINT)) {
defaultErrorResponsesCustomization(entry.getValue());
}
}).map(this::tagsCustomization).filter(Objects::nonNull).distinct().sorted(Comparator.comparing(Tag::getName)).forEach(openAPI::addTagsItem);
var pathItemsByTags = new TreeMap<String, Map<String, PathItem>>();
paths.forEach((k, v) -> {
var tagItem = tagItemFromPathItem(v);
if (tagItem != null) {
var pathItemMap = pathItemsByTags.computeIfAbsent(tagItem, k1 -> new TreeMap<>());
pathItemMap.put(k, v);
}
});
var sortedPaths = new Paths();
pathItemsByTags.forEach((tagItem, pathItemMap) -> {
pathItemMap.forEach(sortedPaths::addPathItem);
});
sortedPaths.setExtensions(paths.getExtensions());
openAPI.setPaths(sortedPaths);
var sortedSchemas = new TreeMap<>(openAPI.getComponents().getSchemas());
openAPI.getComponents().setSchemas(new LinkedHashMap<>(sortedSchemas));
};
}
private Tag tagsCustomization(Map.Entry<String, PathItem> entry) {
var tagItem = tagItemFromPathItem(entry.getValue());
if (tagItem != null) {
return tagFromTagItem(tagItem);
} }
return null;
}
private String tagItemFromPathItem(PathItem item) {
var operations = item.readOperationsMap().values();
var operation = operations.stream().findAny();
if (operation.isPresent()) {
var tags = operation.get().getTags();
if (tags != null && !tags.isEmpty()) {
return tags.get(0);
}
}
return null;
} }
private Tag tagFromTagItem(String tagItem) { private Tag tagFromTagItem(String tagItem) {
@ -276,32 +363,113 @@ public class SwaggerConfiguration {
return new Tag().name(tagItem).description(sb.toString().trim()); return new Tag().name(tagItem).description(sb.toString().trim());
} }
private ApiResponses getResponses(ApiResponses apiResponses, boolean isPost) { private void defaultErrorResponsesCustomization(PathItem pathItem) {
if (apiResponses == null) { pathItem.readOperationsMap().forEach(((httpMethod, operation) -> {
apiResponses = new ApiResponses(); var errorResponses = httpMethod.equals(PathItem.HttpMethod.POST) ? defaultPostErrorResponses : defaultErrorResponses;
var responses = operation.getResponses();
if (responses == null) {
responses = errorResponses;
} else {
ApiResponses updated = responses;
errorResponses.forEach((key, apiResponse) -> {
if (!updated.containsKey(key)) {
updated.put(key, apiResponse);
}
});
}
operation.setResponses(responses);
}));
}
private void securityCustomization(SecurityRequirement loginForm, Map.Entry<String, PathItem> entry) {
var path = entry.getKey();
if (path.matches(securityPathRegex) && !path.matches(nonSecurityPathRegex) && !path.equals(LOGIN_ENDPOINT)) {
entry.getValue()
.readOperationsMap()
.values()
.forEach(operation -> operation.addSecurityItem(loginForm));
} }
}
apiResponses.addApiResponse("400", new ApiResponse().description("Bad Request") private static ApiResponses loginResponses() {
.content(getErrorContent(ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)))); ApiResponses apiResponses = new ApiResponses();
apiResponses.addApiResponse("200", new ApiResponse().description("OK")
.content(new Content().addMediaType(APPLICATION_JSON_VALUE,
new MediaType().schema(new Schema<LoginResponse>().$ref("#/components/schemas/LoginResponse")))));
apiResponses.putAll(loginErrorResponses());
return apiResponses;
}
private static ApiResponses defaultErrorResponses(boolean isPost) {
ApiResponses apiResponses = new ApiResponses();
apiResponses.addApiResponse("400", errorResponse("400", "Bad Request",
ThingsboardErrorResponse.of(isPost ? "Invalid request body" : "Invalid UUID string: 123", ThingsboardErrorCode.BAD_REQUEST_PARAMS, HttpStatus.BAD_REQUEST)));
apiResponses.addApiResponse("401", new ApiResponse().description("Unauthorized") apiResponses.addApiResponse("401", errorResponse("401", "Unauthorized",
.content(getErrorContent(ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)))); ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)));
apiResponses.addApiResponse("403", new ApiResponse().description("Forbidden") apiResponses.addApiResponse("403", errorResponse("403", "Forbidden",
.content(getErrorContent(ThingsboardErrorResponse.of("You don't have permission to perform this operation!", ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)))); ThingsboardErrorResponse.of("You don't have permission to perform this operation!", ThingsboardErrorCode.PERMISSION_DENIED, HttpStatus.FORBIDDEN)));
apiResponses.addApiResponse("404", new ApiResponse().description("Not Found") apiResponses.addApiResponse("404", errorResponse("404", "Not Found",
.content(getErrorContent(ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)))); ThingsboardErrorResponse.of("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND, HttpStatus.NOT_FOUND)));
apiResponses.addApiResponse("429", new ApiResponse().description("Too Many Requests") apiResponses.addApiResponse("429", errorResponse("429", "Too Many Requests",
.content(getErrorContent(ThingsboardErrorResponse.of("Too many requests for current tenant!", ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS)))); ThingsboardErrorResponse.of("Too many requests for current tenant!", ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS)));
return apiResponses; return apiResponses;
} }
private Content getErrorContent(ThingsboardErrorResponse errorResponse) { private static ApiResponses loginErrorResponses() {
return new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, ApiResponses apiResponses = new ApiResponses();
new MediaType().schema(new Schema<ThingsboardErrorResponse>().example(errorResponse)));
apiResponses.addApiResponse("401", errorResponse("Unauthorized",
Map.of(
"bad-credentials", errorExample("Bad credentials",
ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
"token-expired", errorExample("JWT token expired",
ThingsboardErrorResponse.of("Token has expired", ThingsboardErrorCode.JWT_TOKEN_EXPIRED, HttpStatus.UNAUTHORIZED)),
"account-disabled", errorExample("Disabled account",
ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
"account-locked", errorExample("Locked account",
ThingsboardErrorResponse.of("User account is locked due to security policy", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)),
"authentication-failed", errorExample("General authentication error",
ThingsboardErrorResponse.of("Authentication failed", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED))
)
));
var credentialsExpiredSchema = new Schema<ThingsboardCredentialsExpiredResponse>();
credentialsExpiredSchema.$ref("#/components/schemas/ThingsboardCredentialsExpiredResponse");
apiResponses.addApiResponse("401 ", errorResponse("Unauthorized (**Expired credentials**)",
Map.of(
"credentials-expired", errorExample("Expired credentials",
ThingsboardCredentialsExpiredResponse.of("User password expired!", StringUtils.randomAlphanumeric(30)))
),
credentialsExpiredSchema
));
return apiResponses;
}
private static ApiResponse errorResponse(String code, String description, ThingsboardErrorResponse example) {
return errorResponse(description, Map.of("error-code-" + code, errorExample(description, example)));
}
private static ApiResponse errorResponse(String description, Map<String, Example> examples) {
var schema = new Schema<ThingsboardErrorResponse>();
schema.$ref("#/components/schemas/ThingsboardErrorResponse");
return errorResponse(description, examples, schema);
}
private static ApiResponse errorResponse(String description, Map<String, Example> examples, Schema<? extends ThingsboardErrorResponse> errorResponseSchema) {
MediaType mediaType = new MediaType().schema(errorResponseSchema);
mediaType.setExamples(examples);
Content content = new Content().addMediaType(org.springframework.http.MediaType.APPLICATION_JSON_VALUE, mediaType);
return new ApiResponse().description(description).content(content);
}
private static Example errorExample(String summary, ThingsboardErrorResponse example) {
return new Example()
.summary(summary)
.value(example);
} }
} }

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

@ -37,4 +37,9 @@ public class WebConfig {
response.sendRedirect(baseUrl + "/swagger-ui/"); response.sendRedirect(baseUrl + "/swagger-ui/");
} }
@RequestMapping("/swagger-ui/")
public String redirectSwaggerIndex() throws IOException {
return "forward:/swagger-ui/index.html";
}
} }

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

@ -184,8 +184,7 @@ public class AdminController extends BaseController {
} }
@ApiOperation(value = "Get the JWT Settings object (getJwtSettings)", @ApiOperation(value = "Get the JWT Settings object (getJwtSettings)",
notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH, notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.GET) @RequestMapping(value = "/jwtSettings", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -195,8 +194,7 @@ public class AdminController extends BaseController {
} }
@ApiOperation(value = "Update JWT Settings (saveJwtSettings)", @ApiOperation(value = "Update JWT Settings (saveJwtSettings)",
notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH, notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.POST) @RequestMapping(value = "/jwtSettings", method = RequestMethod.POST)
@ResponseBody @ResponseBody

9
application/src/main/java/org/thingsboard/server/controller/AlarmCommentController.java

@ -69,13 +69,12 @@ public class AlarmCommentController extends BaseController {
"Referencing non-existing Alarm Comment Id will cause 'Not Found' error. " + "Referencing non-existing Alarm Comment Id will cause 'Not Found' error. " +
"\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " + "\n\n To create new Alarm comment entity it is enough to specify 'comment' json element with 'text' node, for example: {\"comment\": { \"text\": \"my comment\"}}. " +
"\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." + "\n\n If comment type is not specified the default value 'OTHER' will be saved. If 'alarmId' or 'userId' specified in body it will be ignored." +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST) @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public AlarmComment saveAlarmComment(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION) public AlarmComment saveAlarmComment(@Parameter(description = ALARM_ID_PARAM_DESCRIPTION)
@PathVariable(ALARM_ID) String strAlarmId, @Parameter(description = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException { @PathVariable(ALARM_ID) String strAlarmId, @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the comment.") @RequestBody AlarmComment alarmComment) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId); checkParameter(ALARM_ID, strAlarmId);
AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
Alarm alarm = checkAlarmInfoId(alarmId, Operation.WRITE); Alarm alarm = checkAlarmInfoId(alarmId, Operation.WRITE);
@ -84,7 +83,7 @@ public class AlarmCommentController extends BaseController {
} }
@ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)", @ApiOperation(value = "Delete Alarm comment (deleteAlarmComment)",
notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "Deletes the Alarm comment. Referencing non-existing Alarm comment Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE) @RequestMapping(value = "/alarm/{alarmId}/comment/{commentId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -100,7 +99,7 @@ public class AlarmCommentController extends BaseController {
@ApiOperation(value = "Get Alarm comments (getAlarmComments)", @ApiOperation(value = "Get Alarm comments (getAlarmComments)",
notes = "Returns a page of alarm comments for specified alarm. " + notes = "Returns a page of alarm comments for specified alarm. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET) @RequestMapping(value = "/alarm/{alarmId}/comment", method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -104,7 +104,7 @@ public class AlarmController extends BaseController {
"filled in the AlarmInfo object field: 'originatorName' or will returns as null."; "filled in the AlarmInfo object field: 'originatorName' or will returns as null.";
@ApiOperation(value = "Get Alarm (getAlarmById)", @ApiOperation(value = "Get Alarm (getAlarmById)",
notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "Fetch the Alarm object based on the provided Alarm Id. " + ALARM_SECURITY_CHECK)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET) @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -117,7 +117,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Alarm Info (getAlarmInfoById)", @ApiOperation(value = "Get Alarm Info (getAlarmInfoById)",
notes = "Fetch the Alarm Info object based on the provided Alarm Id. " + notes = "Fetch the Alarm Info object based on the provided Alarm Id. " +
ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) ALARM_SECURITY_CHECK + ALARM_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET) @RequestMapping(value = "/alarm/info/{alarmId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -139,11 +139,11 @@ public class AlarmController extends BaseController {
"If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " + "If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Alarm entity. " + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Alarm entity. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm", method = RequestMethod.POST) @RequestMapping(value = "/alarm", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Alarm saveAlarm(@Parameter(description = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { public Alarm saveAlarm(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException {
alarm.setTenantId(getTenantId()); alarm.setTenantId(getTenantId());
checkNotNull(alarm.getOriginator()); checkNotNull(alarm.getOriginator());
checkEntity(alarm.getId(), alarm, Resource.ALARM); checkEntity(alarm.getId(), alarm, Resource.ALARM);
@ -155,7 +155,7 @@ public class AlarmController extends BaseController {
} }
@ApiOperation(value = "Delete Alarm (deleteAlarm)", @ApiOperation(value = "Delete Alarm (deleteAlarm)",
notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "Deletes the Alarm. Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE) @RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -169,7 +169,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Acknowledge Alarm (ackAlarm)", @ApiOperation(value = "Acknowledge Alarm (ackAlarm)",
notes = "Acknowledge the Alarm. " + notes = "Acknowledge the Alarm. " +
"Once acknowledged, the 'ack_ts' field will be set to current timestamp and special rule chain event 'ALARM_ACK' will be generated. " + "Once acknowledged, the 'ack_ts' field will be set to current timestamp and special rule chain event 'ALARM_ACK' will be generated. " +
"Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -184,7 +184,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Clear Alarm (clearAlarm)", @ApiOperation(value = "Clear Alarm (clearAlarm)",
notes = "Clear the Alarm. " + notes = "Clear the Alarm. " +
"Once cleared, the 'clear_ts' field will be set to current timestamp and special rule chain event 'ALARM_CLEAR' will be generated. " + "Once cleared, the 'clear_ts' field will be set to current timestamp and special rule chain event 'ALARM_CLEAR' will be generated. " +
"Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -200,7 +200,7 @@ public class AlarmController extends BaseController {
notes = "Assign the Alarm. " + notes = "Assign the Alarm. " +
"Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " + "Once assigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_ASSIGNED' " +
"(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " + "(or ALARM_REASSIGNED in case of assigning already assigned alarm) will be generated. " +
"Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST) @RequestMapping(value = "/alarm/{alarmId}/assign/{assigneeId}", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -221,7 +221,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Unassign Alarm (unassignAlarm)", @ApiOperation(value = "Unassign Alarm (unassignAlarm)",
notes = "Unassign the Alarm. " + notes = "Unassign the Alarm. " +
"Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " + "Once unassigned, the 'assign_ts' field will be set to current timestamp and special rule chain event 'ALARM_UNASSIGNED' will be generated. " +
"Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "Referencing non-existing Alarm Id will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE) @RequestMapping(value = "/alarm/{alarmId}/assign", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -236,7 +236,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Alarms (getAlarms)", @ApiOperation(value = "Get Alarms (getAlarms)",
notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + notes = "Returns a page of alarms for the selected entity. Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -292,7 +292,7 @@ public class AlarmController extends BaseController {
"If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " + "If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " +
"If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " + "If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " +
"Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " + "Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarms", method = RequestMethod.GET) @RequestMapping(value = "/alarms", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -341,7 +341,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Alarms (getAlarmsV2)", @ApiOperation(value = "Get Alarms (getAlarmsV2)",
notes = "Returns a page of alarms for the selected entity. " + notes = "Returns a page of alarms for the selected entity. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/v2/alarm/{entityType}/{entityId}", method = RequestMethod.GET) @RequestMapping(value = "/v2/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -407,7 +407,7 @@ public class AlarmController extends BaseController {
notes = "Returns a page of alarms that belongs to the current user owner. " + notes = "Returns a page of alarms that belongs to the current user owner. " +
"If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " + "If the user has the authority of 'Tenant Administrator', the server returns alarms that belongs to the tenant of current user. " +
"If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " + "If the user has the authority of 'Customer User', the server returns alarms that belongs to the customer of current user. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/v2/alarms", method = RequestMethod.GET) @RequestMapping(value = "/v2/alarms", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -468,7 +468,7 @@ public class AlarmController extends BaseController {
@ApiOperation(value = "Get Highest Alarm Severity (getHighestAlarmSeverity)", @ApiOperation(value = "Get Highest Alarm Severity (getHighestAlarmSeverity)",
notes = "Search the alarms by originator ('entityType' and entityId') and optional 'status' or 'searchStatus' filters and returns the highest AlarmSeverity(CRITICAL, MAJOR, MINOR, WARNING or INDETERMINATE). " + notes = "Search the alarms by originator ('entityType' and entityId') and optional 'status' or 'searchStatus' filters and returns the highest AlarmSeverity(CRITICAL, MAJOR, MINOR, WARNING or INDETERMINATE). " +
"Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH "Specifying both parameters 'searchStatus' and 'status' at the same time will cause an error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -499,8 +499,7 @@ public class AlarmController extends BaseController {
} }
@ApiOperation(value = "Get Alarm Types (getAlarmTypes)", @ApiOperation(value = "Get Alarm Types (getAlarmTypes)",
notes = "Returns a set of unique alarm types based on alarms that are either owned by the tenant or assigned to the customer which user is performing the request.", notes = "Returns a set of unique alarm types based on alarms that are either owned by the tenant or assigned to the customer which user is performing the request.")
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/types", method = RequestMethod.GET) @RequestMapping(value = "/alarm/types", method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -103,7 +103,7 @@ public class AssetController extends BaseController {
notes = "Fetch the Asset object based on the provided Asset Id. " + notes = "Fetch the Asset object based on the provided Asset Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " + "If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH "If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET) @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -118,7 +118,7 @@ public class AssetController extends BaseController {
notes = "Fetch the Asset Info object based on the provided Asset Id. " + notes = "Fetch the Asset Info object based on the provided Asset Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " + "If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer. " "If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer. "
+ ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) + ASSET_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET) @RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -135,11 +135,11 @@ public class AssetController extends BaseController {
"Specify existing Asset id to update the asset. " + "Specify existing Asset id to update the asset. " +
"Referencing non-existing Asset Id will cause 'Not Found' error. " + "Referencing non-existing Asset Id will cause 'Not Found' error. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Asset entity. " "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Asset entity. "
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset", method = RequestMethod.POST) @RequestMapping(value = "/asset", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Asset saveAsset(@Parameter(description = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception { public Asset saveAsset(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception {
asset.setTenantId(getTenantId()); asset.setTenantId(getTenantId());
checkEntity(asset.getId(), asset, Resource.ASSET); checkEntity(asset.getId(), asset, Resource.ASSET);
return tbAssetService.save(asset, getCurrentUser()); return tbAssetService.save(asset, getCurrentUser());
@ -158,7 +158,7 @@ public class AssetController extends BaseController {
} }
@ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)", @ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)",
notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -174,7 +174,7 @@ public class AssetController extends BaseController {
} }
@ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)", @ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)",
notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -192,7 +192,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)", @ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)",
notes = "Asset will be available for non-authorized (not logged-in) users. " + notes = "Asset will be available for non-authorized (not logged-in) users. " +
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
"However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "However, users that are logged-in and belong to different tenant will not be able to access the asset." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -205,7 +205,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Tenant Assets (getTenantAssets)", @ApiOperation(value = "Get Tenant Assets (getTenantAssets)",
notes = "Returns a page of assets owned by tenant. " + notes = "Returns a page of assets owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -233,7 +233,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Tenant Asset Infos (getTenantAssetInfos)", @ApiOperation(value = "Get Tenant Asset Infos (getTenantAssetInfos)",
notes = "Returns a page of assets info objects owned by tenant. " + notes = "Returns a page of assets info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -266,7 +266,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Tenant Asset (getTenantAsset)", @ApiOperation(value = "Get Tenant Asset (getTenantAsset)",
notes = "Requested asset must be owned by tenant that the user belongs to. " + notes = "Requested asset must be owned by tenant that the user belongs to. " +
"Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "Asset name is an unique property of asset. So it can be used to identify the asset." + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -279,7 +279,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Customer Assets (getCustomerAssets)", @ApiOperation(value = "Get Customer Assets (getCustomerAssets)",
notes = "Returns a page of assets objects assigned to customer. " + notes = "Returns a page of assets objects assigned to customer. " +
PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -312,7 +312,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get Customer Asset Infos (getCustomerAssetInfos)", @ApiOperation(value = "Get Customer Asset Infos (getCustomerAssetInfos)",
notes = "Returns a page of assets info objects assigned to customer. " + notes = "Returns a page of assets info objects assigned to customer. " +
PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -349,7 +349,7 @@ public class AssetController extends BaseController {
} }
@ApiOperation(value = "Get Assets By Ids (getAssetsByIds)", @ApiOperation(value = "Get Assets By Ids (getAssetsByIds)",
notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET) @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -376,7 +376,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Find related assets (findByQuery)", @ApiOperation(value = "Find related assets (findByQuery)",
notes = "Returns all assets that are related to the specific entity. " + notes = "Returns all assets that are related to the specific entity. " +
"The entity id, relation type, asset types, depth of the search, and other query parameters defined using complex 'AssetSearchQuery' object. " + "The entity id, relation type, asset types, depth of the search, and other query parameters defined using complex 'AssetSearchQuery' object. " +
"See 'Model' tab of the Parameters for more info.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "See 'Model' tab of the Parameters for more info.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", method = RequestMethod.POST) @RequestMapping(value = "/assets", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -398,8 +398,7 @@ public class AssetController extends BaseController {
} }
@ApiOperation(value = "Get Asset Types (getAssetTypes)", @ApiOperation(value = "Get Asset Types (getAssetTypes)",
notes = "Deprecated. See 'getAssetProfileNames' API from Asset Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, notes = "Deprecated. See 'getAssetProfileNames' API from Asset Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/types", method = RequestMethod.GET) @RequestMapping(value = "/asset/types", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -416,8 +415,7 @@ public class AssetController extends BaseController {
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment asset " + "Second, remote edge service will receive a copy of assignment asset " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.", "Third, once asset will be delivered to edge service, it's going to be available for usage on remote edge instance.")
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -440,8 +438,7 @@ public class AssetController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove asset " + "Second, remote edge service will receive an 'unassign' command to remove asset " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.", "Third, once 'unassign' command will be delivered to edge service, it's going to remove asset locally.")
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -460,7 +457,7 @@ public class AssetController extends BaseController {
@ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)", @ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)",
notes = "Returns a page of assets assigned to edge. " + notes = "Returns a page of assets assigned to edge. " +
PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -510,7 +507,7 @@ public class AssetController extends BaseController {
} }
@ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)", @ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)",
notes = "There's an ability to import the bulk of assets using the only .csv file.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) notes = "There's an ability to import the bulk of assets using the only .csv file.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PostMapping("/asset/bulk_import") @PostMapping("/asset/bulk_import")
public BulkImportResult<Asset> processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception { public BulkImportResult<Asset> processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception {

24
application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java

@ -78,8 +78,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profile (getAssetProfileById)", @ApiOperation(value = "Get Asset Profile (getAssetProfileById)",
notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " + notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " +
"The server checks that the asset profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH, "The server checks that the asset profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.GET) @RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -99,8 +98,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)", @ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)",
notes = "Fetch the Asset Profile Info object based on the provided Asset Profile Id. " notes = "Fetch the Asset Profile Info object based on the provided Asset Profile Id. "
+ ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfo/{assetProfileId}", method = RequestMethod.GET) @RequestMapping(value = "/assetProfileInfo/{assetProfileId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -114,8 +112,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Default Asset Profile (getDefaultAssetProfileInfo)", @ApiOperation(value = "Get Default Asset Profile (getDefaultAssetProfileInfo)",
notes = "Fetch the Default Asset Profile Info object. " + notes = "Fetch the Default Asset Profile Info object. " +
ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfo/default", method = RequestMethod.GET) @RequestMapping(value = "/assetProfileInfo/default", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -130,8 +127,7 @@ public class AssetProfileController extends BaseController {
"Referencing non-existing asset profile Id will cause 'Not Found' error. " + NEW_LINE + "Referencing non-existing asset profile Id will cause 'Not Found' error. " + NEW_LINE +
"Asset profile name is unique in the scope of tenant. Only one 'default' asset profile may exist in scope of tenant. " + "Asset profile name is unique in the scope of tenant. Only one 'default' asset profile may exist in scope of tenant. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Asset Profile entity. " + "Remove 'id', 'tenantId' from the request body example (below) to create new Asset Profile entity. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile", method = RequestMethod.POST) @RequestMapping(value = "/assetProfile", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -145,8 +141,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Delete asset profile (deleteAssetProfile)", @ApiOperation(value = "Delete asset profile (deleteAssetProfile)",
notes = "Deletes the asset profile. Referencing non-existing asset profile Id will cause an error. " + notes = "Deletes the asset profile. Referencing non-existing asset profile Id will cause an error. " +
"Can't delete the asset profile if it is referenced by existing assets." + TENANT_AUTHORITY_PARAGRAPH, "Can't delete the asset profile if it is referenced by existing assets." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.DELETE) @RequestMapping(value = "/assetProfile/{assetProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -160,8 +155,7 @@ public class AssetProfileController extends BaseController {
} }
@ApiOperation(value = "Make Asset Profile Default (setDefaultAssetProfile)", @ApiOperation(value = "Make Asset Profile Default (setDefaultAssetProfile)",
notes = "Marks asset profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH, notes = "Marks asset profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfile/{assetProfileId}/default", method = RequestMethod.POST) @RequestMapping(value = "/assetProfile/{assetProfileId}/default", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -177,8 +171,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profiles (getAssetProfiles)", @ApiOperation(value = "Get Asset Profiles (getAssetProfiles)",
notes = "Returns a page of asset profile objects owned by tenant. " + notes = "Returns a page of asset profile objects owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/assetProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/assetProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -199,8 +192,7 @@ public class AssetProfileController extends BaseController {
@ApiOperation(value = "Get Asset Profile infos (getAssetProfileInfos)", @ApiOperation(value = "Get Asset Profile infos (getAssetProfileInfos)",
notes = "Returns a page of asset profile info objects owned by tenant. " + notes = "Returns a page of asset profile info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + ASSET_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assetProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/assetProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -74,8 +74,7 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get audit logs by customer id (getAuditLogsByCustomerId)", @ApiOperation(value = "Get audit logs by customer id (getAuditLogsByCustomerId)",
notes = "Returns a page of audit logs related to the targeted customer entities (devices, assets, etc.), " + notes = "Returns a page of audit logs related to the targeted customer entities (devices, assets, etc.), " +
"and users actions (login, logout, etc.) that belong to this customer. " + "and users actions (login, logout, etc.) that belong to this customer. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/audit/logs/customer/{customerId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -108,8 +107,7 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get audit logs by user id (getAuditLogsByUserId)", @ApiOperation(value = "Get audit logs by user id (getAuditLogsByUserId)",
notes = "Returns a page of audit logs related to the actions of targeted user. " + notes = "Returns a page of audit logs related to the actions of targeted user. " +
"For example, RPC call to a particular device, or alarm acknowledgment for a specific device, etc. " + "For example, RPC call to a particular device, or alarm acknowledgment for a specific device, etc. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/audit/logs/user/{userId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -143,8 +141,7 @@ public class AuditLogController extends BaseController {
notes = "Returns a page of audit logs related to the actions on the targeted entity. " + notes = "Returns a page of audit logs related to the actions on the targeted entity. " +
"Basically, this API call is used to get the full lifecycle of some specific entity. " + "Basically, this API call is used to get the full lifecycle of some specific entity. " +
"For example to see when a device was created, updated, assigned to some customer, or even deleted from the system. " + "For example to see when a device was created, updated, assigned to some customer, or even deleted from the system. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -179,8 +176,7 @@ public class AuditLogController extends BaseController {
@ApiOperation(value = "Get all audit logs (getAuditLogs)", @ApiOperation(value = "Get all audit logs (getAuditLogs)",
notes = "Returns a page of audit logs related to all entities in the scope of the current user's Tenant. " + notes = "Returns a page of audit logs related to all entities in the scope of the current user's Tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/audit/logs", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -506,7 +506,7 @@ public abstract class BaseController {
TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException { TenantProfile checkTenantProfileId(TenantProfileId tenantProfileId, Operation operation) throws ThingsboardException {
try { try {
validateId(tenantProfileId, "Incorrect tenantProfileId " + tenantProfileId); validateId(tenantProfileId, id -> "Incorrect tenantProfileId " + id);
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId); TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(getTenantId(), tenantProfileId);
checkNotNull(tenantProfile, "Tenant profile with id [" + tenantProfileId + "] is not found"); checkNotNull(tenantProfile, "Tenant profile with id [" + tenantProfileId + "] is not found");
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation); accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, operation);
@ -541,7 +541,7 @@ public abstract class BaseController {
if (entityId == null) { if (entityId == null) {
throw new ThingsboardException("Parameter entityId can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Parameter entityId can't be empty!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} }
validateId(entityId.getId(), "Incorrect entityId " + entityId); validateId(entityId.getId(), id -> "Incorrect entityId " + id);
switch (entityId.getEntityType()) { switch (entityId.getEntityType()) {
case ALARM: case ALARM:
checkAlarmId(new AlarmId(entityId.getId()), operation); checkAlarmId(new AlarmId(entityId.getId()), operation);
@ -668,7 +668,7 @@ public abstract class BaseController {
AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) throws ThingsboardException { AlarmComment checkAlarmCommentId(AlarmCommentId alarmCommentId, AlarmId alarmId) throws ThingsboardException {
try { try {
validateId(alarmCommentId, "Incorrect alarmCommentId " + alarmCommentId); validateId(alarmCommentId, id -> "Incorrect alarmCommentId " + id);
AlarmComment alarmComment = alarmCommentService.findAlarmCommentByIdAsync(getCurrentUser().getTenantId(), alarmCommentId).get(); AlarmComment alarmComment = alarmCommentService.findAlarmCommentByIdAsync(getCurrentUser().getTenantId(), alarmCommentId).get();
checkNotNull(alarmComment, "Alarm comment with id [" + alarmCommentId + "] is not found"); checkNotNull(alarmComment, "Alarm comment with id [" + alarmCommentId + "] is not found");
if (!alarmId.equals(alarmComment.getAlarmId())) { if (!alarmId.equals(alarmComment.getAlarmId())) {
@ -736,7 +736,7 @@ public abstract class BaseController {
} }
protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException { protected RuleNode checkRuleNode(RuleNodeId ruleNodeId, Operation operation) throws ThingsboardException {
validateId(ruleNodeId, "Incorrect ruleNodeId " + ruleNodeId); validateId(ruleNodeId, id -> "Incorrect ruleNodeId " + id);
RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId); RuleNode ruleNode = ruleChainService.findRuleNodeById(getTenantId(), ruleNodeId);
checkNotNull(ruleNode, "Rule node with id [" + ruleNodeId + "] is not found"); checkNotNull(ruleNode, "Rule node with id [" + ruleNodeId + "] is not found");
checkRuleChain(ruleNode.getRuleChainId(), operation); checkRuleChain(ruleNode.getRuleChainId(), operation);

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

@ -130,7 +130,7 @@ public class CustomerController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer", method = RequestMethod.POST) @RequestMapping(value = "/customer", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Customer saveCustomer(@Parameter(description = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception { public Customer saveCustomer(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception {
customer.setTenantId(getTenantId()); customer.setTenantId(getTenantId());
checkEntity(customer.getId(), customer, Resource.CUSTOMER); checkEntity(customer.getId(), customer, Resource.CUSTOMER);
return tbCustomerService.save(customer, getCurrentUser()); return tbCustomerService.save(customer, getCurrentUser());

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

@ -133,9 +133,7 @@ public class DashboardController extends BaseController {
} }
@ApiOperation(value = "Get Dashboard Info (getDashboardInfoById)", @ApiOperation(value = "Get Dashboard Info (getDashboardInfoById)",
notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION, notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -148,8 +146,7 @@ public class DashboardController extends BaseController {
} }
@ApiOperation(value = "Get Dashboard (getDashboardById)", @ApiOperation(value = "Get Dashboard (getDashboardById)",
notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))
) )
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@ -174,13 +171,12 @@ public class DashboardController extends BaseController {
"Specify existing Dashboard id to update the dashboard. " + "Specify existing Dashboard id to update the dashboard. " +
"Referencing non-existing dashboard Id will cause 'Not Found' error. " + "Referencing non-existing dashboard Id will cause 'Not Found' error. " +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Dashboard entity. " + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Dashboard entity. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard", method = RequestMethod.POST) @RequestMapping(value = "/dashboard", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard saveDashboard( public Dashboard saveDashboard(
@Parameter(description = "A JSON value representing the dashboard.") @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard) throws Exception { @RequestBody Dashboard dashboard) throws Exception {
dashboard.setTenantId(getTenantId()); dashboard.setTenantId(getTenantId());
checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD); checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
@ -203,8 +199,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)", @ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)",
notes = "Assign the Dashboard to specified Customer or do nothing if the Dashboard is already assigned to that Customer. " + notes = "Assign the Dashboard to specified Customer or do nothing if the Dashboard is already assigned to that Customer. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -226,8 +221,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)", @ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)",
notes = "Unassign the Dashboard from specified Customer or do nothing if the Dashboard is already assigned to that Customer. " + notes = "Unassign the Dashboard from specified Customer or do nothing if the Dashboard is already assigned to that Customer. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -247,8 +241,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)", @ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)",
notes = "Updates the list of Customers that this Dashboard is assigned to. Removes previous assignments to customers that are not in the provided list. " + notes = "Updates the list of Customers that this Dashboard is assigned to. Removes previous assignments to customers that are not in the provided list. " +
"Returns the Dashboard object. " + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object. " + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST) @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
@ -267,8 +260,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)", @ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)",
notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " + notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST) @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -286,8 +278,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)", @ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)",
notes = "Removes the list of Customers from the existing list of assignments for the Dashboard. Keeps other assignments to customers that are not in the provided list. " + notes = "Removes the list of Customers from the existing list of assignments for the Dashboard. Keeps other assignments to customers that are not in the provided list. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST) @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -309,8 +300,7 @@ public class DashboardController extends BaseController {
"Be aware that making the dashboard public does not mean that it automatically makes all devices and assets you use in the dashboard to be public." + "Be aware that making the dashboard public does not mean that it automatically makes all devices and assets you use in the dashboard to be public." +
"Use [assign Asset to Public Customer](#!/asset-controller/assignAssetToPublicCustomerUsingPOST) and " + "Use [assign Asset to Public Customer](#!/asset-controller/assignAssetToPublicCustomerUsingPOST) and " +
"[assign Device to Public Customer](#!/device-controller/assignDeviceToPublicCustomerUsingPOST) for this purpose. " + "[assign Device to Public Customer](#!/device-controller/assignDeviceToPublicCustomerUsingPOST) for this purpose. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -325,8 +315,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)", @ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)",
notes = "Unassigns the dashboard from a special, auto-generated 'Public' Customer. Once unassigned, unauthenticated users may no longer browse the dashboard. " + notes = "Unassigns the dashboard from a special, auto-generated 'Public' Customer. Once unassigned, unauthenticated users may no longer browse the dashboard. " +
"Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH, "Returns the Dashboard object." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -341,8 +330,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)", @ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)",
notes = "Returns a page of dashboard info objects owned by tenant. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + notes = "Returns a page of dashboard info objects owned by tenant. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS +
SYSTEM_AUTHORITY_PARAGRAPH, SYSTEM_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -367,8 +355,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)", @ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)",
notes = "Returns a page of dashboard info objects owned by the tenant of a current user. " notes = "Returns a page of dashboard info objects owned by the tenant of a current user. "
+ DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -396,8 +383,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Customer Dashboards (getCustomerDashboards)", @ApiOperation(value = "Get Customer Dashboards (getCustomerDashboards)",
notes = "Returns a page of dashboard info objects owned by the specified customer. " notes = "Returns a page of dashboard info objects owned by the specified customer. "
+ DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -432,8 +418,7 @@ public class DashboardController extends BaseController {
notes = "Returns the home dashboard object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " + notes = "Returns the home dashboard object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " +
"If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " + "If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " +
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " "If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. "
+ DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + DASHBOARD_DEFINITION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -465,8 +450,7 @@ public class DashboardController extends BaseController {
notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " + notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " +
"If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " + "If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " +
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " + "If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -496,8 +480,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", @ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)",
notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the corresponding tenant. " + notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the corresponding tenant. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -518,8 +501,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)", @ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)",
notes = "Update the home dashboard assignment for the current tenant. " + notes = "Update the home dashboard assignment for the current tenant. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -584,8 +566,7 @@ public class DashboardController extends BaseController {
"Second, remote edge service will receive a copy of assignment dashboard " + "Second, remote edge service will receive a copy of assignment dashboard " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." + "Third, once dashboard will be delivered to edge service, it's going to be available for usage on remote edge instance." +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -608,8 +589,7 @@ public class DashboardController extends BaseController {
"Second, remote edge service will receive an 'unassign' command to remove dashboard " + "Second, remote edge service will receive an 'unassign' command to remove dashboard " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." + "Third, once 'unassign' command will be delivered to edge service, it's going to remove dashboard locally." +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -629,8 +609,7 @@ public class DashboardController extends BaseController {
@ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)", @ApiOperation(value = "Get Edge Dashboards (getEdgeDashboards)",
notes = "Returns a page of dashboard info objects assigned to the specified edge. " notes = "Returns a page of dashboard info objects assigned to the specified edge. "
+ DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -177,7 +177,7 @@ public class DeviceController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device", method = RequestMethod.POST) @RequestMapping(value = "/device", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device saveDevice(@Parameter(description = "A JSON value representing the device.") @RequestBody Device device, public Device saveDevice(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "A JSON value representing the device.") @RequestBody Device device,
@Parameter(description = "Optional value of the device credentials to be used during device creation. " + @Parameter(description = "Optional value of the device credentials to be used during device creation. " +
"If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception { "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
@ -534,8 +534,7 @@ public class DeviceController extends BaseController {
} }
@ApiOperation(value = "Get Device Types (getDeviceTypes)", @ApiOperation(value = "Get Device Types (getDeviceTypes)",
notes = "Deprecated. See 'getDeviceProfileNames' API from Device Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, notes = "Deprecated. See 'getDeviceProfileNames' API from Device Profile Controller instead." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/types", method = RequestMethod.GET) @RequestMapping(value = "/device/types", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -668,8 +667,7 @@ public class DeviceController extends BaseController {
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment device " + "Second, remote edge service will receive a copy of assignment device " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH, "Third, once device will be delivered to edge service, it's going to be available for usage on remote edge instance." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -693,8 +691,7 @@ public class DeviceController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove device " + "Second, remote edge service will receive an 'unassign' command to remove device " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH, "Third, once 'unassign' command will be delivered to edge service, it's going to remove device locally." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody

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

@ -86,8 +86,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profile (getDeviceProfileById)", @ApiOperation(value = "Get Device Profile (getDeviceProfileById)",
notes = "Fetch the Device Profile object based on the provided Device Profile Id. " + notes = "Fetch the Device Profile object based on the provided Device Profile Id. " +
"The server checks that the device profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH, "The server checks that the device profile is owned by the same tenant. " + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET) @RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -107,8 +106,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)", @ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)",
notes = "Fetch the Device Profile Info object based on the provided Device Profile Id. " notes = "Fetch the Device Profile Info object based on the provided Device Profile Id. "
+ DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET) @RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -122,8 +120,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Default Device Profile (getDefaultDeviceProfileInfo)", @ApiOperation(value = "Get Default Device Profile (getDefaultDeviceProfileInfo)",
notes = "Fetch the Default Device Profile Info object. " + notes = "Fetch the Default Device Profile Info object. " +
DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET) @RequestMapping(value = "/deviceProfileInfo/default", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -136,8 +133,7 @@ public class DeviceProfileController extends BaseController {
"If profile is not set returns a list of unique keys among all profiles. " + "If profile is not set returns a list of unique keys among all profiles. " +
"The call is used for auto-complete in the UI forms. " + "The call is used for auto-complete in the UI forms. " +
"The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " + "The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET) @RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -160,8 +156,7 @@ public class DeviceProfileController extends BaseController {
"If profile is not set returns a list of unique keys among all profiles. " + "If profile is not set returns a list of unique keys among all profiles. " +
"The call is used for auto-complete in the UI forms. " + "The call is used for auto-complete in the UI forms. " +
"The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " + "The implementation limits the number of devices that participate in search to 100 as a trade of between accurate results and time-consuming queries. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET) @RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -186,8 +181,7 @@ public class DeviceProfileController extends BaseController {
"Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE + "Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE +
"Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA + "Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA +
"Remove 'id', 'tenantId' from the request body example (below) to create new Device Profile entity. " + "Remove 'id', 'tenantId' from the request body example (below) to create new Device Profile entity. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile", method = RequestMethod.POST) @RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -215,8 +209,7 @@ public class DeviceProfileController extends BaseController {
} }
@ApiOperation(value = "Make Device Profile Default (setDefaultDeviceProfile)", @ApiOperation(value = "Make Device Profile Default (setDefaultDeviceProfile)",
notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH, notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST) @RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -232,8 +225,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profiles (getDeviceProfiles)", @ApiOperation(value = "Get Device Profiles (getDeviceProfiles)",
notes = "Returns a page of devices profile objects owned by tenant. " + notes = "Returns a page of devices profile objects owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/deviceProfiles", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -254,8 +246,7 @@ public class DeviceProfileController extends BaseController {
@ApiOperation(value = "Get Device Profiles for transport type (getDeviceProfileInfos)", @ApiOperation(value = "Get Device Profiles for transport type (getDeviceProfileInfos)",
notes = "Returns a page of devices profile info objects owned by tenant. " + notes = "Returns a page of devices profile info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + DEVICE_PROFILE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/deviceProfileInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -120,8 +120,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Get Edge (getEdgeById)", @ApiOperation(value = "Get Edge (getEdgeById)",
notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, notes = "Get the Edge object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -133,8 +132,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Get Edge Info (getEdgeInfoById)", @ApiOperation(value = "Get Edge Info (getEdgeInfoById)",
notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, notes = "Get the Edge Info object based on the provided Edge Id. " + EDGE_SECURITY_CHECK + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET) @RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -152,8 +150,7 @@ public class EdgeController extends BaseController {
"Referencing non-existing Edge Id will cause 'Not Found' error." + "Referencing non-existing Edge Id will cause 'Not Found' error." +
"\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." + "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " +
TENANT_AUTHORITY_PARAGRAPH, TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge", method = RequestMethod.POST) @RequestMapping(value = "/edge", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -193,7 +190,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edges (getEdges)", @ApiOperation(value = "Get Tenant Edges (getEdges)",
notes = "Returns a page of edges owned by tenant. " + notes = "Returns a page of edges owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -213,8 +210,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)", @ApiOperation(value = "Assign edge to customer (assignEdgeToCustomer)",
notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH, notes = "Creates assignment of the edge to customer. Customer will be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -232,8 +228,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)", @ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)",
notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH, notes = "Clears assignment of the edge to customer. Customer will not be able to query edge afterwards." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
@ -253,8 +248,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Make edge publicly available (assignEdgeToPublicCustomer)", @ApiOperation(value = "Make edge publicly available (assignEdgeToPublicCustomer)",
notes = "Edge will be available for non-authorized (not logged-in) users. " + notes = "Edge will be available for non-authorized (not logged-in) users. " +
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " + "This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
"However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH, "However, users that are logged-in and belong to different tenant will not be able to access the edge." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -268,7 +262,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edges (getTenantEdges)", @ApiOperation(value = "Get Tenant Edges (getTenantEdges)",
notes = "Returns a page of edges owned by tenant. " + notes = "Returns a page of edges owned by tenant. " +
PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -296,8 +290,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edge Infos (getTenantEdgeInfos)", @ApiOperation(value = "Get Tenant Edge Infos (getTenantEdgeInfos)",
notes = "Returns a page of edges info objects owned by tenant. " + notes = "Returns a page of edges info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -325,8 +318,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Tenant Edge (getTenantEdge)", @ApiOperation(value = "Get Tenant Edge (getTenantEdge)",
notes = "Requested edge must be owned by tenant or customer that the user belongs to. " + notes = "Requested edge must be owned by tenant or customer that the user belongs to. " +
"Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH, "Edge name is an unique property of edge. So it can be used to identify the edge." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -338,8 +330,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Set root rule chain for provided edge (setEdgeRootRuleChain)", @ApiOperation(value = "Set root rule chain for provided edge (setEdgeRootRuleChain)",
notes = "Change root rule chain of the edge to the new provided rule chain. \n" + notes = "Change root rule chain of the edge to the new provided rule chain. \n" +
"This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH, "This operation will send a notification to update root rule chain on remote edge service." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -359,7 +350,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Customer Edges (getCustomerEdges)", @ApiOperation(value = "Get Customer Edges (getCustomerEdges)",
notes = "Returns a page of edges objects assigned to customer. " + notes = "Returns a page of edges objects assigned to customer. " +
PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -395,7 +386,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Customer Edge Infos (getCustomerEdgeInfos)", @ApiOperation(value = "Get Customer Edge Infos (getCustomerEdgeInfos)",
notes = "Returns a page of edges info objects assigned to customer. " + notes = "Returns a page of edges info objects assigned to customer. " +
PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS + EDGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -430,8 +421,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Get Edges By Ids (getEdgesByIds)", @ApiOperation(value = "Get Edges By Ids (getEdgesByIds)",
notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, notes = "Requested edges must be owned by tenant or assigned to customer which user is performing the request." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET) @RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -459,8 +449,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Find related edges (findByQuery)", @ApiOperation(value = "Find related edges (findByQuery)",
notes = "Returns all edges that are related to the specific entity. " + notes = "Returns all edges that are related to the specific entity. " +
"The entity id, relation type, edge types, depth of the search, and other query parameters defined using complex 'EdgeSearchQuery' object. " + "The entity id, relation type, edge types, depth of the search, and other query parameters defined using complex 'EdgeSearchQuery' object. " +
"See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, "See 'Model' tab of the Parameters for more info." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edges", method = RequestMethod.POST) @RequestMapping(value = "/edges", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -485,8 +474,7 @@ public class EdgeController extends BaseController {
@ApiOperation(value = "Get Edge Types (getEdgeTypes)", @ApiOperation(value = "Get Edge Types (getEdgeTypes)",
notes = "Returns a set of unique edge types based on edges that are either owned by the tenant or assigned to the customer which user is performing the request." notes = "Returns a set of unique edge types based on edges that are either owned by the tenant or assigned to the customer which user is performing the request."
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/types", method = RequestMethod.GET) @RequestMapping(value = "/edge/types", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -542,8 +530,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Import the bulk of edges (processEdgesBulkImport)", @ApiOperation(value = "Import the bulk of edges (processEdgesBulkImport)",
notes = "There's an ability to import the bulk of edges using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH, notes = "There's an ability to import the bulk of edges using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PostMapping("/edge/bulk_import") @PostMapping("/edge/bulk_import")
public BulkImportResult<Edge> processEdgesBulkImport(@RequestBody BulkImportRequest request) throws Exception { public BulkImportResult<Edge> processEdgesBulkImport(@RequestBody BulkImportRequest request) throws Exception {
@ -557,8 +544,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Get Edge Install Instructions (getEdgeInstallInstructions)", @ApiOperation(value = "Get Edge Install Instructions (getEdgeInstallInstructions)",
notes = "Get an install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH, notes = "Get an install instructions for provided edge id." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/instructions/install/{edgeId}/{method}", method = RequestMethod.GET) @RequestMapping(value = "/edge/instructions/install/{edgeId}/{method}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -579,8 +565,7 @@ public class EdgeController extends BaseController {
} }
@ApiOperation(value = "Get Edge Upgrade Instructions (getEdgeUpgradeInstructions)", @ApiOperation(value = "Get Edge Upgrade Instructions (getEdgeUpgradeInstructions)",
notes = "Get an upgrade instructions for provided edge version." + TENANT_AUTHORITY_PARAGRAPH, notes = "Get an upgrade instructions for provided edge version." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/instructions/upgrade/{edgeVersion}/{method}", method = RequestMethod.GET) @RequestMapping(value = "/edge/instructions/upgrade/{edgeVersion}/{method}", method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -60,7 +60,7 @@ public class EdgeEventController extends BaseController {
@ApiOperation(value = "Get Edge Events (getEdgeEvents)", @ApiOperation(value = "Get Edge Events (getEdgeEvents)",
notes = "Returns a page of edge events for the requested edge. " + notes = "Returns a page of edge events for the requested edge. " +
PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET)
@ResponseBody @ResponseBody

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

@ -20,7 +20,6 @@ import io.swagger.v3.oas.annotations.media.Schema;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;

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

@ -135,8 +135,7 @@ public class EntityRelationController extends BaseController {
} }
@ApiOperation(value = "Get Relation (getRelation)", @ApiOperation(value = "Get Relation (getRelation)",
notes = "Returns relation object between two specified entities if present. Otherwise throws exception. " + SECURITY_CHECKS_ENTITIES_DESCRIPTION, notes = "Returns relation object between two specified entities if present. Otherwise throws exception. " + SECURITY_CHECKS_ENTITIES_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relation", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE, TO_ID, TO_TYPE}) @RequestMapping(value = "/relation", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE, TO_ID, TO_TYPE})
@ResponseBody @ResponseBody
@ -161,8 +160,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByFrom)", @ApiOperation(value = "Get List of Relations (findByFrom)",
notes = "Returns list of relation objects for the specified entity by the 'from' direction. " + notes = "Returns list of relation objects for the specified entity by the 'from' direction. " +
SECURITY_CHECKS_ENTITY_DESCRIPTION, SECURITY_CHECKS_ENTITY_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE}) @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE})
@ResponseBody @ResponseBody
@ -180,8 +178,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relation Infos (findInfoByFrom)", @ApiOperation(value = "Get List of Relation Infos (findInfoByFrom)",
notes = "Returns list of relation info objects for the specified entity by the 'from' direction. " + notes = "Returns list of relation info objects for the specified entity by the 'from' direction. " +
SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION, SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE}) @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE})
@ResponseBody @ResponseBody
@ -199,8 +196,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByFrom)", @ApiOperation(value = "Get List of Relations (findByFrom)",
notes = "Returns list of relation objects for the specified entity by the 'from' direction and relation type. " + notes = "Returns list of relation objects for the specified entity by the 'from' direction and relation type. " +
SECURITY_CHECKS_ENTITY_DESCRIPTION, SECURITY_CHECKS_ENTITY_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE}) @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {FROM_ID, FROM_TYPE, RELATION_TYPE})
@ResponseBody @ResponseBody
@ -220,8 +216,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByTo)", @ApiOperation(value = "Get List of Relations (findByTo)",
notes = "Returns list of relation objects for the specified entity by the 'to' direction. " + notes = "Returns list of relation objects for the specified entity by the 'to' direction. " +
SECURITY_CHECKS_ENTITY_DESCRIPTION, SECURITY_CHECKS_ENTITY_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE}) @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE})
@ResponseBody @ResponseBody
@ -239,8 +234,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relation Infos (findInfoByTo)", @ApiOperation(value = "Get List of Relation Infos (findInfoByTo)",
notes = "Returns list of relation info objects for the specified entity by the 'to' direction. " + notes = "Returns list of relation info objects for the specified entity by the 'to' direction. " +
SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION, SECURITY_CHECKS_ENTITY_DESCRIPTION + " " + RELATION_INFO_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {TO_ID, TO_TYPE}) @RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {TO_ID, TO_TYPE})
@ResponseBody @ResponseBody
@ -258,8 +252,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Get List of Relations (findByTo)", @ApiOperation(value = "Get List of Relations (findByTo)",
notes = "Returns list of relation objects for the specified entity by the 'to' direction and relation type. " + notes = "Returns list of relation objects for the specified entity by the 'to' direction and relation type. " +
SECURITY_CHECKS_ENTITY_DESCRIPTION, SECURITY_CHECKS_ENTITY_DESCRIPTION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE, RELATION_TYPE}) @RequestMapping(value = "/relations", method = RequestMethod.GET, params = {TO_ID, TO_TYPE, RELATION_TYPE})
@ResponseBody @ResponseBody
@ -280,7 +273,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Find related entities (findByQuery)", @ApiOperation(value = "Find related entities (findByQuery)",
notes = "Returns all entities that are related to the specific entity. " + notes = "Returns all entities that are related to the specific entity. " +
"The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " + "The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " +
"See 'Model' tab of the Parameters for more info.", responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "See 'Model' tab of the Parameters for more info.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations", method = RequestMethod.POST) @RequestMapping(value = "/relations", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -296,7 +289,7 @@ public class EntityRelationController extends BaseController {
@ApiOperation(value = "Find related entity infos (findInfoByQuery)", @ApiOperation(value = "Find related entity infos (findInfoByQuery)",
notes = "Returns all entity infos that are related to the specific entity. " + notes = "Returns all entity infos that are related to the specific entity. " +
"The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " + "The entity id, relation type, entity types, depth of the search, and other query parameters defined using complex 'EntityRelationsQuery' object. " +
"See 'Model' tab of the Parameters for more info. " + RELATION_INFO_DESCRIPTION, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) "See 'Model' tab of the Parameters for more info. " + RELATION_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/relations/info", method = RequestMethod.POST) @RequestMapping(value = "/relations/info", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -97,8 +97,7 @@ public class EntityViewController extends BaseController {
@ApiOperation(value = "Get entity view (getEntityViewById)", @ApiOperation(value = "Get entity view (getEntityViewById)",
notes = "Fetch the EntityView object based on the provided entity view id. " notes = "Fetch the EntityView object based on the provided entity view id. "
+ ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET) @RequestMapping(value = "/entityView/{entityViewId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -111,8 +110,7 @@ public class EntityViewController extends BaseController {
@ApiOperation(value = "Get Entity View info (getEntityViewInfoById)", @ApiOperation(value = "Get Entity View info (getEntityViewInfoById)",
notes = "Fetch the Entity View info object based on the provided Entity View Id. " notes = "Fetch the Entity View info object based on the provided Entity View Id. "
+ ENTITY_VIEW_INFO_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + ENTITY_VIEW_INFO_DESCRIPTION + MODEL_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView/info/{entityViewId}", method = RequestMethod.GET) @RequestMapping(value = "/entityView/info/{entityViewId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -127,8 +125,7 @@ public class EntityViewController extends BaseController {
@ApiOperation(value = "Save or update entity view (saveEntityView)", @ApiOperation(value = "Save or update entity view (saveEntityView)",
notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION +
"Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Entity View entity." + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Entity View entity." +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/entityView", method = RequestMethod.POST) @RequestMapping(value = "/entityView", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -162,8 +159,7 @@ public class EntityViewController extends BaseController {
} }
@ApiOperation(value = "Get Entity View by name (getTenantEntityView)", @ApiOperation(value = "Get Entity View by name (getTenantEntityView)",
notes = "Fetch the Entity View object based on the tenant id and entity view name. " + TENANT_AUTHORITY_PARAGRAPH, notes = "Fetch the Entity View object based on the tenant id and entity view name. " + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -399,8 +395,7 @@ public class EntityViewController extends BaseController {
EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive a copy of assignment entity view " + "Second, remote edge service will receive a copy of assignment entity view " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.", "Third, once entity view will be delivered to edge service, it's going to be available for usage on remote edge instance.")
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -424,8 +419,7 @@ public class EntityViewController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove entity view " + "Second, remote edge service will receive an 'unassign' command to remove entity view " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.", "Third, once 'unassign' command will be delivered to edge service, it's going to remove entity view locally.")
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody

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

@ -108,7 +108,7 @@ public class EventController extends BaseController {
@ApiOperation(value = "Get Events by type (getEvents)", @ApiOperation(value = "Get Events by type (getEvents)",
notes = "Returns a page of events for specified entity by specifying event type. " + notes = "Returns a page of events for specified entity by specifying event type. " +
PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET) @RequestMapping(value = "/events/{entityType}/{entityId}/{eventType}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -150,7 +150,7 @@ public class EventController extends BaseController {
"The call was deprecated to improve the performance of the system. " + "The call was deprecated to improve the performance of the system. " +
"Current implementation will return 'Lifecycle' events only. " + "Current implementation will return 'Lifecycle' events only. " +
"Use 'Get events by type' or 'Get events by filter' instead. " + "Use 'Get events by type' or 'Get events by filter' instead. " +
PAGE_DATA_PARAMETERS, responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE))) PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET) @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -190,8 +190,7 @@ public class EventController extends BaseController {
@ApiOperation(value = "Get Events by event filter (getEvents)", @ApiOperation(value = "Get Events by event filter (getEvents)",
notes = "Returns a page of events for the chosen entity by specifying the event filter. " + notes = "Returns a page of events for the chosen entity by specifying the event filter. " +
PAGE_DATA_PARAMETERS + NEW_LINE + PAGE_DATA_PARAMETERS + NEW_LINE +
EVENT_FILTER_DEFINITION, EVENT_FILTER_DEFINITION)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST) @RequestMapping(value = "/events/{entityType}/{entityId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody

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

@ -59,8 +59,7 @@ public class Lwm2mController extends BaseController {
@ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)", @ApiOperation(value = "Get Lwm2m Bootstrap SecurityInfo (getLwm2mBootstrapSecurityInfo)",
notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " + notes = "Get the Lwm2m Bootstrap SecurityInfo object (of the current server) based on the provided isBootstrapServer parameter. If isBootstrapServer == true, get the parameters of the current Bootstrap Server. If isBootstrapServer == false, get the parameters of the current Lwm2m Server. Used for client settings when starting the client in Bootstrap mode. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET) @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{isBootstrapServer}", method = RequestMethod.GET)
@ResponseBody @ResponseBody

19
application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java

@ -105,8 +105,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)", @ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)",
notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " + notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " +
OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET) @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -119,8 +118,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package (getOtaPackageById)", @ApiOperation(value = "Get OTA Package (getOtaPackageById)",
notes = "Fetch the OTA Package object based on the provided OTA Package Id. " + notes = "Fetch the OTA Package object based on the provided OTA Package Id. " +
"The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, "The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET) @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -136,8 +134,7 @@ public class OtaPackageController extends BaseController {
"The newly created OTA Package id will be present in the response. " + "The newly created OTA Package id will be present in the response. " +
"Specify existing OTA Package id to update the OTA Package Info. " + "Specify existing OTA Package id to update the OTA Package Info. " +
"Referencing non-existing OTA Package Id will cause 'Not Found' error. " + "Referencing non-existing OTA Package Id will cause 'Not Found' error. " +
"\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH, "\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage", method = RequestMethod.POST) @RequestMapping(value = "/otaPackage", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -151,7 +148,6 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Save OTA Package data (saveOtaPackageData)", @ApiOperation(value = "Save OTA Package data (saveOtaPackageData)",
notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH, notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH,
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)),
requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MULTIPART_FORM_DATA_VALUE))) requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody(content = @Content(mediaType = MULTIPART_FORM_DATA_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE) @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE)
@ -176,8 +172,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)",
notes = "Returns a page of OTA Package Info objects owned by tenant. " + notes = "Returns a page of OTA Package Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages", method = RequestMethod.GET) @RequestMapping(value = "/otaPackages", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -197,8 +192,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)",
notes = "Returns a page of OTA Package Info objects owned by tenant. " + notes = "Returns a page of OTA Package Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET) @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -225,8 +219,7 @@ public class OtaPackageController extends BaseController {
@ApiOperation(value = "Delete OTA Package (deleteOtaPackage)", @ApiOperation(value = "Delete OTA Package (deleteOtaPackage)",
notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " + notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " +
"Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH, "Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE) @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody

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

@ -371,7 +371,7 @@ public class RuleChainController extends BaseController {
public JsonNode testScript( public JsonNode testScript(
@Parameter(description = "Script language: JS or TBEL") @Parameter(description = "Script language: JS or TBEL")
@RequestParam(required = false) ScriptLanguage scriptLang, @RequestParam(required = false) ScriptLanguage scriptLang,
@Parameter(description = "Test JS request. See API call description above.") @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Test JS request. See API call description above.")
@RequestBody JsonNode inputParams) throws ThingsboardException, JsonProcessingException { @RequestBody JsonNode inputParams) throws ThingsboardException, JsonProcessingException {
String script = inputParams.get("script").asText(); String script = inputParams.get("script").asText();
String scriptType = inputParams.get("scriptType").asText(); String scriptType = inputParams.get("scriptType").asText();
@ -499,8 +499,7 @@ public class RuleChainController extends BaseController {
"Second, remote edge service will receive a copy of assignment rule chain " + "Second, remote edge service will receive a copy of assignment rule chain " +
EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " + "Third, once rule chain will be delivered to edge service, it's going to start processing messages locally. " +
"\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH, "\n\nOnly rule chain with type 'EDGE' can be assigned to edge." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -522,8 +521,7 @@ public class RuleChainController extends BaseController {
EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION + EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION +
"Second, remote edge service will receive an 'unassign' command to remove rule chain " + "Second, remote edge service will receive an 'unassign' command to remove rule chain " +
EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION + EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION +
"Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH, "Third, once 'unassign' command will be delivered to edge service, it's going to remove rule chain locally." + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody

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

@ -146,8 +146,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource Info (getResourceInfoById)", @ApiOperation(value = "Get Resource Info (getResourceInfoById)",
notes = "Fetch the Resource Info object based on the provided Resource Id. " + notes = "Fetch the Resource Info object based on the provided Resource Id. " +
RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource/info/{resourceId}") @GetMapping(value = "/resource/info/{resourceId}")
public TbResourceInfo getResourceInfoById(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION) public TbResourceInfo getResourceInfoById(@Parameter(description = RESOURCE_ID_PARAM_DESCRIPTION)
@ -159,8 +158,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource (getResourceById)", @ApiOperation(value = "Get Resource (getResourceById)",
notes = "Fetch the Resource object based on the provided Resource Id. " + notes = "Fetch the Resource object based on the provided Resource Id. " +
RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, hidden = true)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)), hidden = true)
@Deprecated // resource's data should be fetched with a download request @Deprecated // resource's data should be fetched with a download request
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource/{resourceId}") @GetMapping(value = "/resource/{resourceId}")
@ -178,8 +176,7 @@ public class TbResourceController extends BaseController {
"Referencing non-existing Resource Id will cause 'Not Found' error. " + "Referencing non-existing Resource Id will cause 'Not Found' error. " +
"\n\nResource combination of the title with the key is unique in the scope of tenant. " + "\n\nResource combination of the title with the key is unique in the scope of tenant. " +
"Remove 'id', 'tenantId' from the request body example (below) to create new Resource entity." + "Remove 'id', 'tenantId' from the request body example (below) to create new Resource entity." +
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PostMapping(value = "/resource") @PostMapping(value = "/resource")
public TbResourceInfo saveResource(@Parameter(description = "A JSON value representing the Resource.") public TbResourceInfo saveResource(@Parameter(description = "A JSON value representing the Resource.")
@ -191,8 +188,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource Infos (getResources)", @ApiOperation(value = "Get Resource Infos (getResources)",
notes = "Returns a page of Resource Info objects owned by tenant or sysadmin. " + notes = "Returns a page of Resource Info objects owned by tenant or sysadmin. " +
PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = "/resource") @GetMapping(value = "/resource")
public PageData<TbResourceInfo> getResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) public PageData<TbResourceInfo> getResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@ -227,8 +223,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get All Resource Infos (getAllResources)", @ApiOperation(value = "Get All Resource Infos (getAllResources)",
notes = "Returns a page of Resource Info objects owned by tenant. " + notes = "Returns a page of Resource Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/tenant") @GetMapping(value = "/resource/tenant")
public PageData<TbResourceInfo> getTenantResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) public PageData<TbResourceInfo> getTenantResources(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@ -251,8 +246,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjectsPage)", @ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjectsPage)",
notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " + notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " +
PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/lwm2m/page") @GetMapping(value = "/resource/lwm2m/page")
public List<LwM2mObject> getLwm2mListObjectsPage(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true) public List<LwM2mObject> getLwm2mListObjectsPage(@Parameter(description = PAGE_SIZE_DESCRIPTION, required = true)
@ -271,8 +265,7 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)", @ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)",
notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " + notes = "Returns a page of LwM2M objects parsed from Resources with type 'LWM2M_MODEL' owned by tenant or sysadmin. " +
"You can specify parameters to filter the results. " + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, "You can specify parameters to filter the results. " + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/lwm2m") @GetMapping(value = "/resource/lwm2m")
public List<LwM2mObject> getLwm2mListObjects(@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}, required = true)) public List<LwM2mObject> getLwm2mListObjects(@Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}, required = true))

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

@ -177,8 +177,7 @@ public class TelemetryController extends BaseController {
"\n\n * SERVER_SCOPE - supported for all entity types;" + "\n\n * SERVER_SCOPE - supported for all entity types;" +
"\n * CLIENT_SCOPE - supported for devices;" + "\n * CLIENT_SCOPE - supported for devices;" +
"\n * SHARED_SCOPE - supported for devices. " "\n * SHARED_SCOPE - supported for devices. "
+ "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET) @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -193,8 +192,7 @@ public class TelemetryController extends BaseController {
"\n\n * SERVER_SCOPE - supported for all entity types;" + "\n\n * SERVER_SCOPE - supported for all entity types;" +
"\n * CLIENT_SCOPE - supported for devices;" + "\n * CLIENT_SCOPE - supported for devices;" +
"\n * SHARED_SCOPE - supported for devices. " "\n * SHARED_SCOPE - supported for devices. "
+ "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET) @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -212,8 +210,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START + MARKDOWN_CODE_BLOCK_START
+ ATTRIBUTE_DATA_EXAMPLE + ATTRIBUTE_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END
+ "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET) @RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -235,8 +232,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START + MARKDOWN_CODE_BLOCK_START
+ ATTRIBUTE_DATA_EXAMPLE + ATTRIBUTE_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END
+ "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET) @RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -252,8 +248,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Get time-series keys (getTimeseriesKeys)", @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)",
notes = "Returns a set of unique time-series key names for the selected entity. " + notes = "Returns a set of unique time-series key names for the selected entity. " +
"\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET) @RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -275,8 +270,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START + MARKDOWN_CODE_BLOCK_START
+ LATEST_TS_STRICT_DATA_EXAMPLE + LATEST_TS_STRICT_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END
+ "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET) @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -299,8 +293,7 @@ public class TelemetryController extends BaseController {
+ MARKDOWN_CODE_BLOCK_START + MARKDOWN_CODE_BLOCK_START
+ TS_STRICT_DATA_EXAMPLE + TS_STRICT_DATA_EXAMPLE
+ MARKDOWN_CODE_BLOCK_END + MARKDOWN_CODE_BLOCK_END
+ "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"}) @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
@ResponseBody @ResponseBody
@ -348,8 +341,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Save device attributes (saveDeviceAttributes)", @ApiOperation(value = "Save device attributes (saveDeviceAttributes)",
notes = "Creates or updates the device attributes based on device id and specified attribute scope. " + notes = "Creates or updates the device attributes based on device id and specified attribute scope. " +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK +
"Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED', " + "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED', " +
@ -365,7 +357,7 @@ public class TelemetryController extends BaseController {
public DeferredResult<ResponseEntity> saveDeviceAttributes( public DeferredResult<ResponseEntity> saveDeviceAttributes(
@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr, @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope") AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope") AttributeScope scope,
@Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
return saveAttributes(getTenantId(), entityId, scope, request); return saveAttributes(getTenantId(), entityId, scope, request);
} }
@ -374,8 +366,7 @@ public class TelemetryController extends BaseController {
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " + notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_SAVE_ATTRIBUTE_SCOPES + ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
@ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), @ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST),
@ -389,7 +380,7 @@ public class TelemetryController extends BaseController {
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope")AttributeScope scope,
@Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request); return saveAttributes(getTenantId(), entityId, scope, request);
} }
@ -398,8 +389,7 @@ public class TelemetryController extends BaseController {
notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " + notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
ENTITY_SAVE_ATTRIBUTE_SCOPES + ENTITY_SAVE_ATTRIBUTE_SCOPES +
SAVE_ATTRIBUTES_REQUEST_PAYLOAD SAVE_ATTRIBUTES_REQUEST_PAYLOAD
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
@ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST), @ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST),
@ -413,7 +403,7 @@ public class TelemetryController extends BaseController {
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope")AttributeScope scope, @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, required = true)) @PathVariable("scope")AttributeScope scope,
@Parameter(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody JsonNode request) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveAttributes(getTenantId(), entityId, scope, request); return saveAttributes(getTenantId(), entityId, scope, request);
} }
@ -423,8 +413,7 @@ public class TelemetryController extends BaseController {
notes = "Creates or updates the entity time-series data based on the Entity Id and request payload." + notes = "Creates or updates the entity time-series data based on the Entity Id and request payload." +
SAVE_TIMESERIES_REQUEST_PAYLOAD + SAVE_TIMESERIES_REQUEST_PAYLOAD +
"\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. " "\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. "
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK), @ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK),
@ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST), @ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST),
@ -438,7 +427,7 @@ public class TelemetryController extends BaseController {
@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType, @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope, @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")String scope,
@Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, 0L); return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
} }
@ -448,8 +437,7 @@ public class TelemetryController extends BaseController {
SAVE_TIMESERIES_REQUEST_PAYLOAD + SAVE_TIMESERIES_REQUEST_PAYLOAD +
"\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. " "\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. "
+ "\n\nThe ttl parameter takes affect only in case of Cassandra DB." + "\n\nThe ttl parameter takes affect only in case of Cassandra DB."
+ INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK), @ApiResponse(responseCode = "200", description = SAVE_ENTITY_TIMESERIES_STATUS_OK),
@ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST), @ApiResponse(responseCode = "400", description = INVALID_STRUCTURE_OF_THE_REQUEST),
@ -464,7 +452,7 @@ public class TelemetryController extends BaseController {
@Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr, @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
@Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope, @Parameter(description = TELEMETRY_SCOPE_DESCRIPTION, required = true, schema = @Schema(allowableValues = "ANY")) @PathVariable("scope")AttributeScope scope,
@Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl, @Parameter(description = "A long value representing TTL (Time to Live) parameter.", required = true)@PathVariable("ttl")Long ttl,
@Parameter(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException { @io.swagger.v3.oas.annotations.parameters.RequestBody(description = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true)@RequestBody String requestBody) throws ThingsboardException {
EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
return saveTelemetry(getTenantId(), entityId, requestBody, ttl); return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
} }
@ -476,8 +464,7 @@ public class TelemetryController extends BaseController {
" Use 'deleteLatest' to delete latest value (stored in separate table for performance) if the value's timestamp matches the time-range. " + " Use 'deleteLatest' to delete latest value (stored in separate table for performance) if the value's timestamp matches the time-range. " +
" Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) if the value's timestamp matches the time-range and 'deleteLatest' param is true." + " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) if the value's timestamp matches the time-range and 'deleteLatest' param is true." +
" The replacement value will be fetched from the 'time-series' table, and its timestamp will be the most recent one before the defined time-range. " + " The replacement value will be fetched from the 'time-series' table, and its timestamp will be the most recent one before the defined time-range. " +
TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Timeseries for the selected keys in the request was removed. " + @ApiResponse(responseCode = "200", description = "Timeseries for the selected keys in the request was removed. " +
"Platform creates an audit log event about entity timeseries removal with action type 'TIMESERIES_DELETED'."), "Platform creates an audit log event about entity timeseries removal with action type 'TIMESERIES_DELETED'."),
@ -552,8 +539,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)", @ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)",
notes = "Delete device attributes using provided Device Id, scope and a list of keys. " + notes = "Delete device attributes using provided Device Id, scope and a list of keys. " +
"Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Device attributes was removed for the selected keys in the request. " + @ApiResponse(responseCode = "200", description = "Device attributes was removed for the selected keys in the request. " +
"Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED'."), "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED'."),
@ -575,8 +561,7 @@ public class TelemetryController extends BaseController {
@ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)", @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)",
notes = "Delete entity attributes using provided Entity Id, scope and a list of keys. " + notes = "Delete entity attributes using provided Entity Id, scope and a list of keys. " +
INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
responses = @ApiResponse(content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE)))
@ApiResponses(value = { @ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Entity attributes was removed for the selected keys in the request. " + @ApiResponse(responseCode = "200", description = "Entity attributes was removed for the selected keys in the request. " +
"Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."), "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."),

9
application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
@ -127,20 +128,20 @@ public class EntityActionService {
} else { } else {
entityNode = JacksonUtil.newObjectNode(); entityNode = JacksonUtil.newObjectNode();
if (actionType == ActionType.ATTRIBUTES_UPDATED) { if (actionType == ActionType.ATTRIBUTES_UPDATED) {
String scope = extractParameter(String.class, 0, additionalInfo); AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo); List<AttributeKvEntry> attributes = extractParameter(List.class, 1, additionalInfo);
metaData.putValue(DataConstants.SCOPE, scope); metaData.putValue(DataConstants.SCOPE, scope.name());
if (attributes != null) { if (attributes != null) {
for (AttributeKvEntry attr : attributes) { for (AttributeKvEntry attr : attributes) {
JacksonUtil.addKvEntry(entityNode, attr); JacksonUtil.addKvEntry(entityNode, attr);
} }
} }
} else if (actionType == ActionType.ATTRIBUTES_DELETED) { } else if (actionType == ActionType.ATTRIBUTES_DELETED) {
String scope = extractParameter(String.class, 0, additionalInfo); AttributeScope scope = extractParameter(AttributeScope.class, 0, additionalInfo);
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<String> keys = extractParameter(List.class, 1, additionalInfo); List<String> keys = extractParameter(List.class, 1, additionalInfo);
metaData.putValue(DataConstants.SCOPE, scope); metaData.putValue(DataConstants.SCOPE, scope.name());
ArrayNode attrsArrayNode = entityNode.putArray("attributes"); ArrayNode attrsArrayNode = entityNode.putArray("attributes");
if (keys != null) { if (keys != null) {
keys.forEach(attrsArrayNode::add); keys.forEach(attrsArrayNode::add);

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

@ -42,6 +42,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProces
import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor;
@ -126,6 +127,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Autowired @Autowired
private ResourceEdgeProcessor resourceEdgeProcessor; private ResourceEdgeProcessor resourceEdgeProcessor;
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
@Autowired @Autowired
protected ApplicationEventPublisher eventPublisher; protected ApplicationEventPublisher eventPublisher;
@ -231,6 +235,9 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case TB_RESOURCE: case TB_RESOURCE:
resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg); resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
break; break;
case OAUTH2:
oAuth2EdgeProcessor.processOAuth2Notification(tenantId, edgeNotificationMsg);
break;
default: default:
log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type); log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type);
} }

8
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java

@ -32,6 +32,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
@ -61,6 +62,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.profile.DevicePr
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor;
@ -151,6 +153,9 @@ public class EdgeContextComponent {
@Autowired @Autowired
private ResourceService resourceService; private ResourceService resourceService;
@Autowired
private OAuth2Service oAuth2Service;
@Autowired @Autowired
private RateLimitService rateLimitService; private RateLimitService rateLimitService;
@ -220,6 +225,9 @@ public class EdgeContextComponent {
@Autowired @Autowired
private ResourceEdgeProcessor resourceEdgeProcessor; private ResourceEdgeProcessor resourceEdgeProcessor;
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
@Autowired @Autowired
private EdgeMsgConstructor edgeMsgConstructor; private EdgeMsgConstructor edgeMsgConstructor;

75
application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java

@ -33,6 +33,7 @@ import org.thingsboard.server.common.data.alarm.AlarmComment;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
@ -159,43 +160,41 @@ public class EdgeEventSourcingListener {
private boolean isValidSaveEntityEventForEdgeProcessing(SaveEntityEvent<?> event) { private boolean isValidSaveEntityEventForEdgeProcessing(SaveEntityEvent<?> event) {
Object entity = event.getEntity(); Object entity = event.getEntity();
Object oldEntity = event.getOldEntity(); Object oldEntity = event.getOldEntity();
switch (event.getEntityId().getEntityType()) { if (event.getEntityId() != null) {
case RULE_CHAIN: switch (event.getEntityId().getEntityType()) {
if (entity instanceof RuleChain) { case RULE_CHAIN:
RuleChain ruleChain = (RuleChain) entity; if (entity instanceof RuleChain ruleChain) {
return RuleChainType.EDGE.equals(ruleChain.getType()); return RuleChainType.EDGE.equals(ruleChain.getType());
} }
break; break;
case USER: case USER:
if (entity instanceof User) { if (entity instanceof User user) {
User user = (User) entity; if (Authority.SYS_ADMIN.equals(user.getAuthority())) {
if (Authority.SYS_ADMIN.equals(user.getAuthority())) { return false;
return false; }
if (oldEntity != null) {
User oldUser = (User) oldEntity;
cleanUpUserAdditionalInfo(oldUser);
cleanUpUserAdditionalInfo(user);
return !user.equals(oldUser);
}
} }
if (oldEntity != null) { break;
User oldUser = (User) oldEntity; case OTA_PACKAGE:
cleanUpUserAdditionalInfo(oldUser); if (entity instanceof OtaPackageInfo otaPackageInfo) {
cleanUpUserAdditionalInfo(user); return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData();
return !user.equals(oldUser); }
break;
case ALARM:
if (entity instanceof AlarmApiCallResult || entity instanceof Alarm) {
return false;
} }
} break;
break; case TENANT:
case OTA_PACKAGE: return !event.getCreated();
if (entity instanceof OtaPackageInfo) { case API_USAGE_STATE, EDGE:
OtaPackageInfo otaPackageInfo = (OtaPackageInfo) entity;
return otaPackageInfo.hasUrl() || otaPackageInfo.isHasData();
}
break;
case ALARM:
if (entity instanceof AlarmApiCallResult || entity instanceof Alarm) {
return false; return false;
} }
break;
case TENANT:
return !event.getCreated();
case API_USAGE_STATE:
case EDGE:
return false;
} }
// Default: If the entity doesn't match any of the conditions, consider it as valid. // Default: If the entity doesn't match any of the conditions, consider it as valid.
return true; return true;
@ -206,8 +205,7 @@ public class EdgeEventSourcingListener {
if (user.getAdditionalInfo() instanceof NullNode) { if (user.getAdditionalInfo() instanceof NullNode) {
user.setAdditionalInfo(null); user.setAdditionalInfo(null);
} }
if (user.getAdditionalInfo() instanceof ObjectNode) { if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) {
ObjectNode additionalInfo = ((ObjectNode) user.getAdditionalInfo());
additionalInfo.remove(UserServiceImpl.FAILED_LOGIN_ATTEMPTS); additionalInfo.remove(UserServiceImpl.FAILED_LOGIN_ATTEMPTS);
additionalInfo.remove(UserServiceImpl.LAST_LOGIN_TS); additionalInfo.remove(UserServiceImpl.LAST_LOGIN_TS);
if (additionalInfo.isEmpty()) { if (additionalInfo.isEmpty()) {
@ -221,6 +219,8 @@ public class EdgeEventSourcingListener {
private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) { private EdgeEventType getEdgeEventTypeForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) { if (entity instanceof AlarmComment) {
return EdgeEventType.ALARM_COMMENT; return EdgeEventType.ALARM_COMMENT;
} else if (entity instanceof OAuth2Info) {
return EdgeEventType.OAUTH2;
} }
return null; return null;
} }
@ -228,6 +228,8 @@ public class EdgeEventSourcingListener {
private String getBodyMsgForEntityEvent(Object entity) { private String getBodyMsgForEntityEvent(Object entity) {
if (entity instanceof AlarmComment) { if (entity instanceof AlarmComment) {
return JacksonUtil.toString(entity); return JacksonUtil.toString(entity);
} else if (entity instanceof OAuth2Info) {
return JacksonUtil.toString(entity);
} }
return null; return null;
} }
@ -238,4 +240,5 @@ public class EdgeEventSourcingListener {
} }
return isCreated ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED; return isCreated ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED;
} }
} }

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

@ -498,9 +498,9 @@ public final class EdgeGrpcSession implements Closeable {
log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", this.tenantId, this.sessionId, copy.size()); log.trace("[{}][{}][{}] downlink msg(s) are going to be send.", this.tenantId, this.sessionId, copy.size());
for (DownlinkMsg downlinkMsg : copy) { for (DownlinkMsg downlinkMsg : copy) {
if (this.clientMaxInboundMessageSize != 0 && downlinkMsg.getSerializedSize() > this.clientMaxInboundMessageSize) { if (this.clientMaxInboundMessageSize != 0 && downlinkMsg.getSerializedSize() > this.clientMaxInboundMessageSize) {
String error = String.format("Client max inbound message size [{%s}] is exceeded. Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE " + String error = String.format("Client max inbound message size %s is exceeded. Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE " +
"env variable on the edge and restart it.", this.clientMaxInboundMessageSize); "env variable on the edge and restart it.", this.clientMaxInboundMessageSize);
String message = String.format("Downlink msg size [{%s}] exceeds client max inbound message size [{%s}]. " + String message = String.format("Downlink msg size %s exceeds client max inbound message size %s. " +
"Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), this.clientMaxInboundMessageSize); "Please increase value of CLOUD_RPC_MAX_INBOUND_MESSAGE_SIZE env variable on the edge and restart it.", downlinkMsg.getSerializedSize(), this.clientMaxInboundMessageSize);
log.error("[{}][{}][{}] {} Message {}", this.tenantId, edge.getId(), this.sessionId, message, downlinkMsg); log.error("[{}][{}][{}] {} Message {}", this.tenantId, edge.getId(), this.sessionId, message, downlinkMsg);
ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId) ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId)
@ -551,35 +551,14 @@ public final class EdgeGrpcSession implements Closeable {
DownlinkMsg downlinkMsg = null; DownlinkMsg downlinkMsg = null;
try { try {
switch (edgeEvent.getAction()) { switch (edgeEvent.getAction()) {
case UPDATED: case UPDATED, ADDED, DELETED, ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, ALARM_ACK, ALARM_CLEAR, ALARM_DELETE, CREDENTIALS_UPDATED, RELATION_ADD_OR_UPDATE, RELATION_DELETED, CREDENTIALS_REQUEST, RPC_CALL, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, ADDED_COMMENT, UPDATED_COMMENT, DELETED_COMMENT -> {
case ADDED:
case DELETED:
case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE:
case ALARM_ACK:
case ALARM_CLEAR:
case ALARM_DELETE:
case CREDENTIALS_UPDATED:
case RELATION_ADD_OR_UPDATE:
case RELATION_DELETED:
case CREDENTIALS_REQUEST:
case RPC_CALL:
case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER:
case ADDED_COMMENT:
case UPDATED_COMMENT:
case DELETED_COMMENT:
downlinkMsg = convertEntityEventToDownlink(edgeEvent); downlinkMsg = convertEntityEventToDownlink(edgeEvent);
log.trace("[{}][{}] entity message processed [{}]", this.tenantId, this.sessionId, downlinkMsg); log.trace("[{}][{}] entity message processed [{}]", this.tenantId, this.sessionId, downlinkMsg);
break; }
case ATTRIBUTES_UPDATED: case ATTRIBUTES_UPDATED, POST_ATTRIBUTES, ATTRIBUTES_DELETED, TIMESERIES_UPDATED ->
case POST_ATTRIBUTES: downlinkMsg = ctx.getTelemetryProcessor().convertTelemetryEventToDownlink(edge, edgeEvent);
case ATTRIBUTES_DELETED: default ->
case TIMESERIES_UPDATED: log.warn("[{}][{}] Unsupported action type [{}]", this.tenantId, this.sessionId, edgeEvent.getAction());
downlinkMsg = ctx.getTelemetryProcessor().convertTelemetryEventToDownlink(edgeEvent);
break;
default:
log.warn("[{}][{}] Unsupported action type [{}]", this.tenantId, this.sessionId, edgeEvent.getAction());
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[{}][{}] Exception during converting edge event to downlink msg", this.tenantId, this.sessionId, e); log.error("[{}][{}] Exception during converting edge event to downlink msg", this.tenantId, this.sessionId, e);
@ -705,6 +684,8 @@ public final class EdgeGrpcSession implements Closeable {
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion); return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion);
case TENANT_PROFILE: case TENANT_PROFILE:
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion); return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion);
case OAUTH2:
return ctx.getOAuth2EdgeProcessor().convertOAuth2EventToDownlink(edgeEvent);
default: default:
log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent); log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent);
return null; return null;
@ -855,7 +836,7 @@ public final class EdgeGrpcSession implements Closeable {
.build(); .build();
} }
String error = "Failed to validate the edge!"; String error = "Failed to validate the edge!";
String failureMsg = String.format("{%s} Provided request secret: %s", error, request.getEdgeSecret()); String failureMsg = String.format("%s Provided request secret: %s", error, request.getEdgeSecret());
ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId()) ctx.getNotificationRuleProcessor().process(EdgeCommunicationFailureTrigger.builder().tenantId(tenantId).edgeId(edge.getId())
.customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build()); .customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(failureMsg).error(error).build());
return ConnectResponseMsg.newBuilder() return ConnectResponseMsg.newBuilder()

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

@ -30,6 +30,7 @@ import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetc
import org.thingsboard.server.service.edge.rpc.fetch.DevicesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.DevicesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EntityViewsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.EntityViewsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OAuth2EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher;
@ -79,6 +80,7 @@ public class EdgeSyncCursor {
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService()));
fetchers.add(new TenantResourcesEdgeEventFetcher(ctx.getResourceService())); fetchers.add(new TenantResourcesEdgeEventFetcher(ctx.getResourceService()));
fetchers.add(new OAuth2EdgeEventFetcher(ctx.getOAuth2Service()));
} }
} }

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

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.constructor.oauth2;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
@TbCoreComponent
public class OAuth2MsgConstructor {
public OAuth2UpdateMsg constructOAuth2UpdateMsg(OAuth2Info oAuth2Info) {
return OAuth2UpdateMsg.newBuilder().setEntity(JacksonUtil.toString(oAuth2Info)).build();
}
}

19
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV1.java

@ -73,6 +73,24 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor {
@Override @Override
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
tenantProfile = JacksonUtil.clone(tenantProfile);
// clear all config
var tenantProfileData = tenantProfile.getProfileData();
var configuration = tenantProfile.getDefaultProfileConfiguration();
configuration.setRpcTtlDays(0);
configuration.setMaxJSExecutions(0);
configuration.setMaxREExecutions(0);
configuration.setMaxDPStorageDays(0);
configuration.setMaxTbelExecutions(0);
configuration.setQueueStatsTtlDays(0);
configuration.setMaxTransportMessages(0);
configuration.setDefaultStorageTtlDays(0);
configuration.setMaxTransportDataPoints(0);
configuration.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0);
tenantProfileData.setConfiguration(configuration);
tenantProfile.setProfileData(tenantProfileData);
ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_2) ? ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_2) ?
ByteString.empty() : ByteString.copyFrom(tenantProfile.getProfileDataBytes()); ByteString.empty() : ByteString.copyFrom(tenantProfile.getProfileDataBytes());
TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder() TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder()
@ -88,4 +106,5 @@ public class TenantMsgConstructorV1 implements TenantMsgConstructor {
} }
return builder.build(); return builder.build();
} }
} }

18
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/tenant/TenantMsgConstructorV2.java

@ -19,6 +19,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg;
@ -36,6 +37,23 @@ public class TenantMsgConstructorV2 implements TenantMsgConstructor {
@Override @Override
public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) {
tenantProfile = JacksonUtil.clone(tenantProfile);
// clear all config
var configuration = tenantProfile.getDefaultProfileConfiguration();
configuration.setRpcTtlDays(0);
configuration.setMaxJSExecutions(0);
configuration.setMaxREExecutions(0);
configuration.setMaxDPStorageDays(0);
configuration.setMaxTbelExecutions(0);
configuration.setQueueStatsTtlDays(0);
configuration.setMaxTransportMessages(0);
configuration.setDefaultStorageTtlDays(0);
configuration.setMaxTransportDataPoints(0);
configuration.setRuleEngineExceptionsTtlDays(0);
configuration.setMaxRuleNodeExecutionsPerMessage(0);
tenantProfile.getProfileData().setConfiguration(configuration);
return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build(); return TenantProfileUpdateMsg.newBuilder().setMsgType(msgType).setEntity(JacksonUtil.toString(tenantProfile)).build();
} }
} }

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java

@ -46,7 +46,7 @@ public class CustomerEdgeEventFetcher implements EdgeEventFetcher {
List<EdgeEvent> result = new ArrayList<>(); List<EdgeEvent> result = new ArrayList<>();
result.add(EdgeUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(), result.add(EdgeUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(),
EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null)); EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null));
// @voba - returns PageData object to be in sync with other fetchers // returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false); return new PageData<>(result, 1, result.size(), false);
} }
} }

56
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import java.util.ArrayList;
import java.util.List;
@AllArgsConstructor
@Slf4j
public class OAuth2EdgeEventFetcher implements EdgeEventFetcher {
private final OAuth2Service oAuth2Service;
@Override
public PageLink getPageLink(int pageSize) {
return null;
}
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> result = new ArrayList<>();
OAuth2Info oAuth2Info = oAuth2Service.findOAuth2Info();
result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OAUTH2,
EdgeEventActionType.ADDED, null, JacksonUtil.valueToTree(oAuth2Info)));
// returns PageData object to be in sync with other fetchers
return new PageData<>(result, 1, result.size(), false);
}
}

248
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -25,7 +25,6 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
@ -51,9 +50,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
@ -75,6 +73,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
@ -93,7 +92,6 @@ import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.service.entitiy.TbLogEntityActionService;
import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.alarm.AlarmMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.asset.AssetMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.customer.CustomerMsgConstructorFactory;
@ -101,6 +99,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.dashboard.DashboardMs
import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.device.DeviceMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.queue.QueueMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.relation.RelationMsgConstructorFactory;
@ -114,6 +113,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.widget.WidgetMsgConst
import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory;
import org.thingsboard.server.service.entitiy.TbLogEntityActionService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbAssetProfileCache; import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
@ -226,6 +226,9 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected ResourceService resourceService; protected ResourceService resourceService;
@Autowired
protected OAuth2Service oAuth2Service;
@Autowired @Autowired
@Lazy @Lazy
protected TbQueueProducerProvider producerProvider; protected TbQueueProducerProvider producerProvider;
@ -257,6 +260,9 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor; protected EntityDataMsgConstructor entityDataMsgConstructor;
@Autowired
protected OAuth2MsgConstructor oAuth2MsgConstructor;
@Autowired @Autowired
protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory; protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory;
@ -349,35 +355,15 @@ public abstract class BaseEdgeProcessor {
private boolean doSaveIfEdgeIsOffline(EdgeEventType type, private boolean doSaveIfEdgeIsOffline(EdgeEventType type,
EdgeEventActionType action) { EdgeEventActionType action) {
switch (action) { return switch (action) {
case TIMESERIES_UPDATED: case TIMESERIES_UPDATED, ALARM_ACK, ALARM_CLEAR, ALARM_ASSIGNED, ALARM_UNASSIGNED, CREDENTIALS_REQUEST, ADDED_COMMENT, UPDATED_COMMENT ->
case ALARM_ACK: true;
case ALARM_CLEAR: default -> switch (type) {
case ALARM_ASSIGNED: case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, WIDGETS_BUNDLE, WIDGET_TYPE, ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION ->
case ALARM_UNASSIGNED: true;
case CREDENTIALS_REQUEST: default -> false;
case ADDED_COMMENT: };
case UPDATED_COMMENT: };
return true;
}
switch (type) {
case ALARM:
case ALARM_COMMENT:
case RULE_CHAIN:
case RULE_CHAIN_METADATA:
case USER:
case CUSTOMER:
case TENANT:
case TENANT_PROFILE:
case WIDGETS_BUNDLE:
case WIDGET_TYPE:
case ADMIN_SETTINGS:
case OTA_PACKAGE:
case QUEUE:
case RELATION:
return true;
}
return false;
} }
private ListenableFuture<Void> doSaveEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventType type, EdgeEventActionType action, EntityId entityId, JsonNode body) { private ListenableFuture<Void> doSaveEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventType type, EdgeEventActionType action, EntityId entityId, JsonNode body) {
@ -392,18 +378,13 @@ public abstract class BaseEdgeProcessor {
protected ListenableFuture<Void> processActionForAllEdges(TenantId tenantId, EdgeEventType type, protected ListenableFuture<Void> processActionForAllEdges(TenantId tenantId, EdgeEventType type,
EdgeEventActionType actionType, EntityId entityId, EdgeEventActionType actionType, EntityId entityId,
EdgeId sourceEdgeId) { JsonNode body, EdgeId sourceEdgeId) {
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
if (TenantId.SYS_TENANT_ID.equals(tenantId)) { if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageDataIterable<TenantId> tenantIds = new PageDataIterable<>(link -> tenantService.findTenantsIds(link), 1024);
PageData<TenantId> tenantsIds; for (TenantId tenantId1 : tenantIds) {
do { futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId, body, sourceEdgeId));
tenantsIds = tenantService.findTenantsIds(pageLink); }
for (TenantId tenantId1 : tenantsIds.getData()) {
futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId, null, sourceEdgeId));
}
pageLink = pageLink.nextPageLink();
} while (tenantsIds.hasNext());
} else { } else {
futures = processActionForAllEdgesByTenantId(tenantId, type, actionType, entityId, null, sourceEdgeId); futures = processActionForAllEdgesByTenantId(tenantId, type, actionType, entityId, null, sourceEdgeId);
} }
@ -416,22 +397,13 @@ public abstract class BaseEdgeProcessor {
EntityId entityId, EntityId entityId,
JsonNode body, JsonNode body,
EdgeId sourceEdgeId) { EdgeId sourceEdgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Edge> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
do { PageDataIterable<Edge> edges = new PageDataIterable<>(link -> edgeService.findEdgesByTenantId(tenantId, link), 1024);
pageData = edgeService.findEdgesByTenantId(tenantId, pageLink); for (Edge edge : edges) {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (!edge.getId().equals(sourceEdgeId)) {
for (Edge edge : pageData.getData()) { futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, body));
if (!edge.getId().equals(sourceEdgeId)) {
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, body));
}
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} }
} while (pageData != null && pageData.hasNext()); }
return futures; return futures;
} }
@ -442,31 +414,17 @@ public abstract class BaseEdgeProcessor {
} }
protected UpdateMsgType getUpdateMsgType(EdgeEventActionType actionType) { protected UpdateMsgType getUpdateMsgType(EdgeEventActionType actionType) {
switch (actionType) { return switch (actionType) {
case UPDATED: case UPDATED, CREDENTIALS_UPDATED, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, UPDATED_COMMENT ->
case CREDENTIALS_UPDATED: UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
case ASSIGNED_TO_CUSTOMER: case ADDED, ASSIGNED_TO_EDGE, RELATION_ADD_OR_UPDATE, ADDED_COMMENT ->
case UNASSIGNED_FROM_CUSTOMER: UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
case UPDATED_COMMENT: case DELETED, UNASSIGNED_FROM_EDGE, RELATION_DELETED, DELETED_COMMENT, ALARM_DELETE ->
return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE; UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
case ADDED: case ALARM_ACK -> UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
case ASSIGNED_TO_EDGE: case ALARM_CLEAR -> UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE;
case RELATION_ADD_OR_UPDATE: default -> throw new RuntimeException("Unsupported actionType [" + actionType + "]");
case ADDED_COMMENT: };
return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
case DELETED:
case UNASSIGNED_FROM_EDGE:
case RELATION_DELETED:
case DELETED_COMMENT:
case ALARM_DELETE:
return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
case ALARM_ACK:
return UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
case ALARM_CLEAR:
return UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE;
default:
throw new RuntimeException("Unsupported actionType [" + actionType + "]");
}
} }
public ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { public ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
@ -539,48 +497,33 @@ public abstract class BaseEdgeProcessor {
} }
private ListenableFuture<Void> updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { private ListenableFuture<Void> updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<RuleChain> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
do { PageDataIterable<RuleChain> ruleChains = new PageDataIterable<>(link -> ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, link), 1024);
pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); for (RuleChain ruleChain : ruleChains) {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { List<RuleChainConnectionInfo> connectionInfos =
for (RuleChain ruleChain : pageData.getData()) { ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections();
if (!ruleChain.getId().equals(processingRuleChainId)) { if (connectionInfos != null && !connectionInfos.isEmpty()) {
List<RuleChainConnectionInfo> connectionInfos = for (RuleChainConnectionInfo connectionInfo : connectionInfos) {
ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections(); if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) {
if (connectionInfos != null && !connectionInfos.isEmpty()) { futures.add(saveEdgeEvent(tenantId,
for (RuleChainConnectionInfo connectionInfo : connectionInfos) { edgeId,
if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) { EdgeEventType.RULE_CHAIN_METADATA,
futures.add(saveEdgeEvent(tenantId, EdgeEventActionType.UPDATED,
edgeId, ruleChain.getId(),
EdgeEventType.RULE_CHAIN_METADATA, null));
EdgeEventActionType.UPDATED,
ruleChain.getId(),
null));
}
}
}
} }
} }
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} }
} while (pageData != null && pageData.hasNext()); }
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
} }
private ListenableFuture<Void> processEntityNotificationForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId, EdgeId sourceEdgeId) { private ListenableFuture<Void> processEntityNotificationForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId, EdgeId sourceEdgeId) {
switch (actionType) { return switch (actionType) {
case ADDED: case ADDED, UPDATED, DELETED, CREDENTIALS_UPDATED -> // used by USER entity
case UPDATED: processActionForAllEdges(tenantId, type, actionType, entityId, null, sourceEdgeId);
case DELETED: default -> Futures.immediateFuture(null);
case CREDENTIALS_UPDATED: // used by USER entity };
return processActionForAllEdges(tenantId, type, actionType, entityId, sourceEdgeId);
default:
return Futures.immediateFuture(null);
}
} }
protected EntityId constructEntityId(String entityTypeStr, long entityIdMSB, long entityIdLSB) { protected EntityId constructEntityId(String entityTypeStr, long entityIdMSB, long entityIdLSB) {
@ -623,26 +566,18 @@ public abstract class BaseEdgeProcessor {
} }
protected boolean isEntityExists(TenantId tenantId, EntityId entityId) { protected boolean isEntityExists(TenantId tenantId, EntityId entityId) {
switch (entityId.getEntityType()) { return switch (entityId.getEntityType()) {
case TENANT: case TENANT -> tenantService.findTenantById(tenantId) != null;
return tenantService.findTenantById(tenantId) != null; case DEVICE -> deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null;
case DEVICE: case ASSET -> assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null;
return deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())) != null; case ENTITY_VIEW ->
case ASSET: entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null;
return assetService.findAssetById(tenantId, new AssetId(entityId.getId())) != null; case CUSTOMER -> customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null;
case ENTITY_VIEW: case USER -> userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
return entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())) != null; case DASHBOARD -> dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
case CUSTOMER: case EDGE -> edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null;
return customerService.findCustomerById(tenantId, new CustomerId(entityId.getId())) != null; default -> false;
case USER: };
return userService.findUserById(tenantId, new UserId(entityId.getId())) != null;
case DASHBOARD:
return dashboardService.findDashboardById(tenantId, new DashboardId(entityId.getId())) != null;
case EDGE:
return edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())) != null;
default:
return false;
}
} }
protected void createRelationFromEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId) { protected void createRelationFromEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId) {
@ -681,37 +616,25 @@ public abstract class BaseEdgeProcessor {
} }
protected AssetProfile checkIfAssetProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, AssetProfile assetProfile, EdgeVersion edgeVersion) { protected AssetProfile checkIfAssetProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, AssetProfile assetProfile, EdgeVersion edgeVersion) {
switch (edgeVersion) { if (EdgeVersion.V_3_3_0.equals(edgeVersion) || EdgeVersion.V_3_3_3.equals(edgeVersion) || EdgeVersion.V_3_4_0.equals(edgeVersion)) {
case V_3_3_3: if (assetProfile.getDefaultDashboardId() != null && isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultDashboardId(), edgeId)) {
case V_3_3_0: assetProfile.setDefaultDashboardId(null);
case V_3_4_0: }
if (assetProfile.getDefaultDashboardId() != null if (assetProfile.getDefaultEdgeRuleChainId() != null && isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultEdgeRuleChainId(), edgeId)) {
&& isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultDashboardId(), edgeId)) { assetProfile.setDefaultEdgeRuleChainId(null);
assetProfile.setDefaultDashboardId(null); }
}
if (assetProfile.getDefaultEdgeRuleChainId() != null
&& isEntityNotAssignedToEdge(tenantId, assetProfile.getDefaultEdgeRuleChainId(), edgeId)) {
assetProfile.setDefaultEdgeRuleChainId(null);
}
break;
} }
return assetProfile; return assetProfile;
} }
protected DeviceProfile checkIfDeviceProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, DeviceProfile deviceProfile, EdgeVersion edgeVersion) { protected DeviceProfile checkIfDeviceProfileDefaultFieldsAssignedToEdge(TenantId tenantId, EdgeId edgeId, DeviceProfile deviceProfile, EdgeVersion edgeVersion) {
switch (edgeVersion) { if (EdgeVersion.V_3_3_0.equals(edgeVersion) || EdgeVersion.V_3_3_3.equals(edgeVersion) || EdgeVersion.V_3_4_0.equals(edgeVersion)) {
case V_3_3_3: if (deviceProfile.getDefaultDashboardId() != null && isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultDashboardId(), edgeId)) {
case V_3_3_0: deviceProfile.setDefaultDashboardId(null);
case V_3_4_0: }
if (deviceProfile.getDefaultDashboardId() != null if (deviceProfile.getDefaultEdgeRuleChainId() != null && isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultEdgeRuleChainId(), edgeId)) {
&& isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultDashboardId(), edgeId)) { deviceProfile.setDefaultEdgeRuleChainId(null);
deviceProfile.setDefaultDashboardId(null); }
}
if (deviceProfile.getDefaultEdgeRuleChainId() != null
&& isEntityNotAssignedToEdge(tenantId, deviceProfile.getDefaultEdgeRuleChainId(), edgeId)) {
deviceProfile.setDefaultEdgeRuleChainId(null);
}
break;
} }
return deviceProfile; return deviceProfile;
} }
@ -726,4 +649,5 @@ public abstract class BaseEdgeProcessor {
} }
return true; return true;
} }
} }

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/asset/AssetEdgeProcessor.java

@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType; import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.asset.BaseAssetService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
@ -115,7 +114,7 @@ public abstract class AssetEdgeProcessor extends BaseAssetProcessor implements A
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId); Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId);
if (asset != null && !BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) { if (asset != null) {
UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction());
AssetUpdateMsg assetUpdateMsg = ((AssetMsgConstructor) AssetUpdateMsg assetUpdateMsg = ((AssetMsgConstructor)
assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetUpdatedMsg(msgType, asset); assetMsgConstructorFactory.getMsgConstructorByEdgeVersion(edgeVersion)).constructAssetUpdatedMsg(msgType, asset);

20
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/customer/CustomerEdgeProcessor.java

@ -29,8 +29,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@ -85,20 +84,11 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor {
CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId()); CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId());
switch (actionType) { switch (actionType) {
case UPDATED: case UPDATED:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<Edge> pageData;
List<ListenableFuture<Void>> futures = new ArrayList<>(); List<ListenableFuture<Void>> futures = new ArrayList<>();
do { PageDataIterable<Edge> edges = new PageDataIterable<>(link -> edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, link), 1024);
pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink); for (Edge edge : edges) {
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
for (Edge edge : pageData.getData()) { }
futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null));
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService);
case DELETED: case DELETED:
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));

63
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/oauth2/OAuth2EdgeProcessor.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor.oauth2;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
@Slf4j
@Component
@TbCoreComponent
public class OAuth2EdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg convertOAuth2EventToDownlink(EdgeEvent edgeEvent) {
DownlinkMsg downlinkMsg = null;
OAuth2Info oAuth2Info = JacksonUtil.convertValue(edgeEvent.getBody(), OAuth2Info.class);
if (oAuth2Info != null) {
OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2MsgConstructor.constructOAuth2UpdateMsg(oAuth2Info);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOAuth2UpdateMsg(oAuth2UpdateMsg)
.build();
}
return downlinkMsg;
}
public ListenableFuture<Void> processOAuth2Notification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
OAuth2Info oAuth2Info = JacksonUtil.fromString(edgeNotificationMsg.getBody(), OAuth2Info.class);
if (oAuth2Info == null) {
return Futures.immediateFuture(null);
}
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
return processActionForAllEdges(tenantId, type, actionType, null, JacksonUtil.toJsonNode(edgeNotificationMsg.getBody()), null);
}
}

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

@ -16,12 +16,18 @@
package org.thingsboard.server.service.edge.rpc.processor.telemetry; package org.thingsboard.server.service.edge.rpc.processor.telemetry;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.notification.rule.trigger.EdgeCommunicationFailureTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
@ -31,18 +37,28 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
@TbCoreComponent @TbCoreComponent
public class TelemetryEdgeProcessor extends BaseTelemetryProcessor { public class TelemetryEdgeProcessor extends BaseTelemetryProcessor {
@Value("${edges.rpc.max_telemetry_message_size:0}")
private int maxTelemetryMessageSize;
@Lazy
@Autowired
private NotificationRuleProcessor notificationRuleProcessor;
@Override @Override
protected String getMsgSourceKey() { protected String getMsgSourceKey() {
return DataConstants.EDGE_MSG_SOURCE; return DataConstants.EDGE_MSG_SOURCE;
} }
public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) { public DownlinkMsg convertTelemetryEventToDownlink(Edge edge, EdgeEvent edgeEvent) {
if (edgeEvent.getBody() != null) { if (edgeEvent.getBody() != null) {
String bodyStr = edgeEvent.getBody().toString(); String bodyStr = edgeEvent.getBody().toString();
if (bodyStr.length() > 1000) { if (maxTelemetryMessageSize > 0 && bodyStr.length() > maxTelemetryMessageSize) {
log.debug("[{}][{}][{}] Conversion to a DownlinkMsg telemetry event failed due to a size limit violation. " + String error = "Conversion to a DownlinkMsg telemetry event failed due to a size limit violation.";
"Current size is {}, but the limit is 1000. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(), String message = String.format("%s Current size is %s, but the limit is %s", error, bodyStr.length(), maxTelemetryMessageSize);
edgeEvent.getEntityId(), bodyStr.length(), StringUtils.truncate(bodyStr, 100)); log.debug("[{}][{}][{}] {}. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(),
edgeEvent.getEntityId(), message, StringUtils.truncate(bodyStr, 100));
notificationRuleProcessor.process(EdgeCommunicationFailureTrigger.builder().tenantId(edgeEvent.getTenantId())
.edgeId(edgeEvent.getEdgeId()).customerId(edge.getCustomerId()).edgeName(edge.getName()).failureMsg(message).error(error).build());
return null; return null;
} }
} }
@ -55,4 +71,5 @@ public class TelemetryEdgeProcessor extends BaseTelemetryProcessor {
.addEntityData(entityDataProto) .addEntityData(entityDataProto)
.build(); .build();
} }
} }

1
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java

@ -60,4 +60,5 @@ public class TenantEdgeProcessor extends BaseEdgeProcessor {
} }
return downlinkMsg; return downlinkMsg;
} }
} }

14
application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java

@ -22,10 +22,8 @@ import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -33,30 +31,18 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@Service @Service
@AllArgsConstructor @AllArgsConstructor
public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService { public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService {
private final AssetService assetService; private final AssetService assetService;
private final TbAssetProfileCache assetProfileCache;
@Override @Override
public Asset save(Asset asset, User user) throws Exception { public Asset save(Asset asset, User user) throws Exception {
ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = asset.getTenantId(); TenantId tenantId = asset.getTenantId();
try { try {
if (TB_SERVICE_QUEUE.equals(asset.getType())) {
throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else if (asset.getAssetProfileId() != null) {
AssetProfile assetProfile = assetProfileCache.get(tenantId, asset.getAssetProfileId());
if (assetProfile != null && TB_SERVICE_QUEUE.equals(assetProfile.getName())) {
throw new ThingsboardException("Unable to save asset with profile " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
autoCommit(user, savedAsset.getId()); autoCommit(user, savedAsset.getId());
logEntityActionService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(), logEntityActionService.logEntityAction(tenantId, savedAsset.getId(), savedAsset, asset.getCustomerId(),

11
application/src/main/java/org/thingsboard/server/service/entitiy/asset/profile/DefaultTbAssetProfileService.java

@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -30,8 +29,6 @@ import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@Service @Service
@TbCoreComponent @TbCoreComponent
@AllArgsConstructor @AllArgsConstructor
@ -45,14 +42,6 @@ public class DefaultTbAssetProfileService extends AbstractTbEntityService implem
ActionType actionType = assetProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED; ActionType actionType = assetProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = assetProfile.getTenantId(); TenantId tenantId = assetProfile.getTenantId();
try { try {
if (TB_SERVICE_QUEUE.equals(assetProfile.getName())) {
throw new ThingsboardException("Unable to save asset profile with name " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} else if (assetProfile.getId() != null) {
AssetProfile foundAssetProfile = assetProfileService.findAssetProfileById(tenantId, assetProfile.getId());
if (foundAssetProfile != null && TB_SERVICE_QUEUE.equals(foundAssetProfile.getName())) {
throw new ThingsboardException("Updating asset profile with name " + TB_SERVICE_QUEUE + " is prohibited!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
}
AssetProfile savedAssetProfile = checkNotNull(assetProfileService.saveAssetProfile(assetProfile)); AssetProfile savedAssetProfile = checkNotNull(assetProfileService.saveAssetProfile(assetProfile));
autoCommit(user, savedAssetProfile.getId()); autoCommit(user, savedAssetProfile.getId());
logEntityActionService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile, logEntityActionService.logEntityAction(tenantId, savedAssetProfile.getId(), savedAssetProfile,

2
application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsDeletionTaskProcessor.java

@ -34,7 +34,7 @@ public class AlarmsDeletionTaskProcessor extends HousekeeperTaskProcessor<Housek
public void process(HousekeeperTask task) throws Exception { public void process(HousekeeperTask task) throws Exception {
EntityType entityType = task.getEntityId().getEntityType(); EntityType entityType = task.getEntityId().getEntityType();
if (entityType == EntityType.DEVICE || entityType == EntityType.ASSET) { if (entityType == EntityType.DEVICE || entityType == EntityType.ASSET) {
int count = alarmService.deleteAlarmsByEntityId(task.getTenantId(), task.getEntityId()); int count = alarmService.deleteAlarmsByOriginatorId(task.getTenantId(), task.getEntityId());
log.debug("[{}][{}][{}] Deleted {} alarms", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), count); log.debug("[{}][{}][{}] Deleted {} alarms", task.getTenantId(), task.getEntityId().getEntityType(), task.getEntityId(), count);
} else { } else {
int count = alarmService.deleteEntityAlarmRecords(task.getTenantId(), task.getEntityId()); int count = alarmService.deleteEntityAlarmRecords(task.getTenantId(), task.getEntityId());

42
application/src/main/java/org/thingsboard/server/service/mail/RefreshTokenExpCheckService.java

@ -54,23 +54,35 @@ public class RefreshTokenExpCheckService {
AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); AdminSettings settings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail");
if (settings != null && settings.getJsonValue().has("enableOauth2") && settings.getJsonValue().get("enableOauth2").asBoolean()) { if (settings != null && settings.getJsonValue().has("enableOauth2") && settings.getJsonValue().get("enableOauth2").asBoolean()) {
JsonNode jsonValue = settings.getJsonValue(); JsonNode jsonValue = settings.getJsonValue();
if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshTokenExpires")) { if (OFFICE_365.name().equals(jsonValue.get("providerId").asText()) && jsonValue.has("refreshToken")
long expiresIn = jsonValue.get("refreshTokenExpires").longValue(); && jsonValue.has("refreshTokenExpires")) {
if ((expiresIn - System.currentTimeMillis()) < 604800000L) { //less than 7 days try {
log.info("Trying to refresh refresh token."); long expiresIn = jsonValue.get("refreshTokenExpires").longValue();
long tokenLifeDuration = expiresIn - System.currentTimeMillis();
if (tokenLifeDuration < 0) {
((ObjectNode) jsonValue).put("tokenGenerated", false);
((ObjectNode) jsonValue).remove("refreshToken");
((ObjectNode) jsonValue).remove("refreshTokenExpires");
String clientId = jsonValue.get("clientId").asText(); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
String clientSecret = jsonValue.get("clientSecret").asText(); } else if (tokenLifeDuration < 604800000L) { //less than 7 days
String refreshToken = jsonValue.get("refreshToken").asText(); log.info("Trying to refresh refresh token.");
String tokenUri = jsonValue.get("tokenUri").asText();
TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(), String clientId = jsonValue.get("clientId").asText();
new GenericUrl(tokenUri), refreshToken) String clientSecret = jsonValue.get("clientSecret").asText();
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) String refreshToken = jsonValue.get("refreshToken").asText();
.execute(); String tokenUri = jsonValue.get("tokenUri").asText();
((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli()); TokenResponse tokenResponse = new RefreshTokenRequest(new NetHttpTransport(), new GsonFactory(),
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings); new GenericUrl(tokenUri), refreshToken)
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret))
.execute();
((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli());
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
}
} catch (Exception e) {
log.error("Error occurred while checking token", e);
} }
} }
} }

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

@ -18,6 +18,8 @@ package org.thingsboard.server.service.queue;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -50,9 +52,9 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.util.KvProtoUtil;
import org.thingsboard.server.common.util.ProtoUtils; import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.dao.resource.ImageCacheKey; import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.common.util.KvProtoUtil;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -101,8 +103,6 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -583,7 +583,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
subscriptionManagerService.onTimeSeriesUpdate( subscriptionManagerService.onTimeSeriesUpdate(
toTenantId(tenantIdMSB, tenantIdLSB), toTenantId(tenantIdMSB, tenantIdLSB),
TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()), TbSubscriptionUtils.toEntityId(proto.getEntityType(), proto.getEntityIdMSB(), proto.getEntityIdLSB()),
KvProtoUtil.toTsKvEntityList(proto.getDataList()), callback); KvProtoUtil.fromTsKvProtoList(proto.getDataList()), callback);
} else if (msg.hasAttrUpdate()) { } else if (msg.hasAttrUpdate()) {
TbAttributeUpdateProto proto = msg.getAttrUpdate(); TbAttributeUpdateProto proto = msg.getAttrUpdate();
subscriptionManagerService.onAttributesUpdate( subscriptionManagerService.onAttributesUpdate(

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

@ -112,9 +112,17 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
if (partitionService.isManagedByCurrentService(queueKey.getTenantId())) { if (partitionService.isManagedByCurrentService(queueKey.getTenantId())) {
var consumer = getConsumer(queueKey).orElseGet(() -> { var consumer = getConsumer(queueKey).orElseGet(() -> {
Queue config = queueService.findQueueByTenantIdAndName(queueKey.getTenantId(), queueKey.getQueueName()); Queue config = queueService.findQueueByTenantIdAndName(queueKey.getTenantId(), queueKey.getQueueName());
if (config == null) {
if (!partitions.isEmpty()) {
log.error("[{}] Queue configuration is missing", queueKey, new RuntimeException("stacktrace"));
}
return null;
}
return createConsumer(queueKey, config); return createConsumer(queueKey, config);
}); });
consumer.update(partitions); if (consumer != null) {
consumer.update(partitions);
}
} }
}); });
consumers.keySet().stream() consumers.keySet().stream()

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

@ -18,7 +18,6 @@ package org.thingsboard.server.service.queue;
import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
@ -45,6 +44,7 @@ public class TbRuleEngineConsumerStats {
public static final String FAILED_MSGS = "failedMsgs"; public static final String FAILED_MSGS = "failedMsgs";
public static final String SUCCESSFUL_ITERATIONS = "successfulIterations"; public static final String SUCCESSFUL_ITERATIONS = "successfulIterations";
public static final String FAILED_ITERATIONS = "failedIterations"; public static final String FAILED_ITERATIONS = "failedIterations";
public static final String TENANT_ID_TAG = "tenantId";
private final StatsFactory statsFactory; private final StatsFactory statsFactory;
@ -73,14 +73,15 @@ public class TbRuleEngineConsumerStats {
this.statsFactory = statsFactory; this.statsFactory = statsFactory;
String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName; String statsKey = StatsType.RULE_ENGINE.getName() + "." + queueName;
this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS); String tenant = tenantId == null || tenantId.isSysTenantId() ? "system" : tenantId.toString();
this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS); this.totalMsgCounter = statsFactory.createStatsCounter(statsKey, TOTAL_MSGS, TENANT_ID_TAG, tenant);
this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS); this.successMsgCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_MSGS, TENANT_ID_TAG, tenant);
this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS); this.timeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TIMEOUT_MSGS, TENANT_ID_TAG, tenant);
this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT); this.failedMsgCounter = statsFactory.createStatsCounter(statsKey, FAILED_MSGS, TENANT_ID_TAG, tenant);
this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED); this.tmpTimeoutMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_TIMEOUT, TENANT_ID_TAG, tenant);
this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS); this.tmpFailedMsgCounter = statsFactory.createStatsCounter(statsKey, TMP_FAILED, TENANT_ID_TAG, tenant);
this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS); this.successIterationsCounter = statsFactory.createStatsCounter(statsKey, SUCCESSFUL_ITERATIONS, TENANT_ID_TAG, tenant);
this.failedIterationsCounter = statsFactory.createStatsCounter(statsKey, FAILED_ITERATIONS, TENANT_ID_TAG, tenant);
counters.add(totalMsgCounter); counters.add(totalMsgCounter);
counters.add(successMsgCounter); counters.add(successMsgCounter);
@ -93,7 +94,7 @@ public class TbRuleEngineConsumerStats {
counters.add(failedIterationsCounter); counters.add(failedIterationsCounter);
} }
public Timer getTimer(TenantId tenantId, String status){ public Timer getTimer(TenantId tenantId, String status) {
return tenantMsgProcessTimers.computeIfAbsent(tenantId, return tenantMsgProcessTimers.computeIfAbsent(tenantId,
id -> statsFactory.createTimer(StatsType.RULE_ENGINE.getName() + "." + queueName, id -> statsFactory.createTimer(StatsType.RULE_ENGINE.getName() + "." + queueName,
"tenantId", tenantId.getId().toString(), "tenantId", tenantId.getId().toString(),

4
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -95,7 +95,7 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
@Override @Override
public List<LwM2mObject> findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, String[] objectIds) { public List<LwM2mObject> findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, String[] objectIds) {
log.trace("Executing findByTenantId [{}]", tenantId); log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL, List<TbResource> resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL,
objectIds); objectIds);
return resources.stream() return resources.stream()
@ -107,7 +107,7 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
@Override @Override
public List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) { public List<LwM2mObject> findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) {
log.trace("Executing findByTenantId [{}]", tenantId); log.trace("Executing findByTenantId [{}]", tenantId);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, id -> INCORRECT_TENANT_ID + id);
PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink); PageData<TbResource> resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink);
return resourcePageData.getData().stream() return resourcePageData.getData().stream()
.flatMap(s -> Stream.ofNullable(toLwM2mObject(s, false))) .flatMap(s -> Stream.ofNullable(toLwM2mObject(s, false)))

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

@ -21,15 +21,15 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService; import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.queue.util.TbRuleEngineComponent;
@ -52,7 +52,6 @@ import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService { public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService {
public static final String TB_SERVICE_QUEUE = "TbServiceQueue";
public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException"; public static final String RULE_ENGINE_EXCEPTION = "ruleEngineException";
public static final FutureCallback<Integer> CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Integer> CALLBACK = new FutureCallback<Integer>() {
@Override @Override
@ -68,10 +67,10 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
private final TbServiceInfoProvider serviceInfoProvider; private final TbServiceInfoProvider serviceInfoProvider;
private final TelemetrySubscriptionService tsService; private final TelemetrySubscriptionService tsService;
private final AssetService assetService; private final QueueStatsService queueStatsService;
private final ApiLimitService apiLimitService; private final ApiLimitService apiLimitService;
private final Lock lock = new ReentrantLock(); private final Lock lock = new ReentrantLock();
private final ConcurrentMap<TenantQueueKey, AssetId> tenantQueueAssets = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantQueueKey, QueueStatsId> tenantQueueStats = new ConcurrentHashMap<>();
@Value("${queue.rule-engine.stats.max-error-message-length:4096}") @Value("${queue.rule-engine.stats.max-error-message-length:4096}")
private int maxErrorMessageLength; private int maxErrorMessageLength;
@ -82,7 +81,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
ruleEngineStats.getTenantStats().forEach((id, stats) -> { ruleEngineStats.getTenantStats().forEach((id, stats) -> {
try { try {
TenantId tenantId = TenantId.fromUUID(id); TenantId tenantId = TenantId.fromUUID(id);
AssetId serviceAssetId = getServiceAssetId(tenantId, queueName); QueueStatsId queueStatsId = getQueueStatsId(tenantId, queueName);
if (stats.getTotalMsgCounter().get() > 0) { if (stats.getTotalMsgCounter().get() > 0) {
List<TsKvEntry> tsList = stats.getCounters().entrySet().stream() List<TsKvEntry> tsList = stats.getCounters().entrySet().stream()
.map(kv -> new BasicTsKvEntry(ts, new LongDataEntry(kv.getKey(), (long) kv.getValue().get()))) .map(kv -> new BasicTsKvEntry(ts, new LongDataEntry(kv.getKey(), (long) kv.getValue().get())))
@ -90,7 +89,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
if (!tsList.isEmpty()) { if (!tsList.isEmpty()) {
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getQueueStatsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, serviceAssetId, tsList, ttl, CALLBACK); tsService.saveAndNotifyInternal(tenantId, queueStatsId, tsList, ttl, CALLBACK);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -104,7 +103,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength))); TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry(RULE_ENGINE_EXCEPTION, e.toJsonString(maxErrorMessageLength)));
long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays); long ttl = apiLimitService.getLimit(tenantId, DefaultTenantProfileConfiguration::getRuleEngineExceptionsTtlDays);
ttl = TimeUnit.DAYS.toSeconds(ttl); ttl = TimeUnit.DAYS.toSeconds(ttl);
tsService.saveAndNotifyInternal(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK); tsService.saveAndNotifyInternal(tenantId, getQueueStatsId(tenantId, queueName), Collections.singletonList(tsKv), ttl, CALLBACK);
} catch (Exception e2) { } catch (Exception e2) {
if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) { if (!"Asset is referencing to non-existent tenant!".equalsIgnoreCase(e2.getMessage())) {
log.debug("[{}] Failed to store the statistics", tenantId, e2); log.debug("[{}] Failed to store the statistics", tenantId, e2);
@ -113,30 +112,30 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
}); });
} }
private AssetId getServiceAssetId(TenantId tenantId, String queueName) { private QueueStatsId getQueueStatsId(TenantId tenantId, String queueName) {
TenantQueueKey key = new TenantQueueKey(tenantId, queueName); TenantQueueKey key = new TenantQueueKey(tenantId, queueName);
AssetId assetId = tenantQueueAssets.get(key); QueueStatsId queueStatsId = tenantQueueStats.get(key);
if (assetId == null) { if (queueStatsId == null) {
lock.lock(); lock.lock();
try { try {
assetId = tenantQueueAssets.get(key); queueStatsId = tenantQueueStats.get(key);
if (assetId == null) { if (queueStatsId == null) {
Asset asset = assetService.findAssetByTenantIdAndName(tenantId, queueName + "_" + serviceInfoProvider.getServiceId()); QueueStats queueStats = queueStatsService.findByTenantIdAndNameAndServiceId(tenantId, queueName , serviceInfoProvider.getServiceId());
if (asset == null) { if (queueStats == null) {
asset = new Asset(); queueStats = new QueueStats();
asset.setTenantId(tenantId); queueStats.setTenantId(tenantId);
asset.setName(queueName + "_" + serviceInfoProvider.getServiceId()); queueStats.setQueueName(queueName);
asset.setType(TB_SERVICE_QUEUE); queueStats.setServiceId(serviceInfoProvider.getServiceId());
asset = assetService.saveAsset(asset); queueStats = queueStatsService.save(tenantId, queueStats);
} }
assetId = asset.getId(); queueStatsId = queueStats.getId();
tenantQueueAssets.put(key, assetId); tenantQueueStats.put(key, queueStatsId);
} }
} finally { } finally {
lock.unlock(); lock.unlock();
} }
} }
return assetId; return queueStatsId;
} }
@Data @Data

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

@ -23,20 +23,9 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.JsonDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueProto;
import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType;
import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto;
import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto;
@ -47,7 +36,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesDeletePr
import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationRequestUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationUpdate;
import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate; import org.thingsboard.server.service.ws.notification.sub.NotificationsSubscriptionUpdate;
@ -60,6 +48,10 @@ import java.util.Map;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.common.util.KvProtoUtil.fromTsValueProtoList;
import static org.thingsboard.server.common.util.KvProtoUtil.toTsKvProtoBuilder;
import static org.thingsboard.server.common.util.KvProtoUtil.toTsValueProto;
public class TbSubscriptionUtils { public class TbSubscriptionUtils {
public static ToCoreMsg toSubEventProto(String serviceId, TbEntitySubEvent event) { public static ToCoreMsg toSubEventProto(String serviceId, TbEntitySubEvent event) {
@ -181,7 +173,7 @@ public class TbSubscriptionUtils {
builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
ts.forEach(v -> builder.addData(toKeyValueProto(v.getTs(), v).build())); ts.forEach(v -> builder.addData(toTsKvProtoBuilder(v.getTs(), v).build()));
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setTsUpdate(builder); msgBuilder.setTsUpdate(builder);
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
@ -208,7 +200,7 @@ public class TbSubscriptionUtils {
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setScope(scope); builder.setScope(scope);
attributes.forEach(v -> builder.addData(toKeyValueProto(v.getLastUpdateTs(), v).build())); attributes.forEach(v -> builder.addData(toTsKvProtoBuilder(v.getLastUpdateTs(), v).build()));
SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder();
msgBuilder.setAttrUpdate(builder); msgBuilder.setAttrUpdate(builder);
@ -231,123 +223,10 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build();
} }
private static TsKvProto.Builder toKeyValueProto(long ts, KvEntry attr) {
KeyValueProto.Builder dataBuilder = KeyValueProto.newBuilder();
dataBuilder.setKey(attr.getKey());
dataBuilder.setType(KeyValueType.forNumber(attr.getDataType().ordinal()));
switch (attr.getDataType()) {
case BOOLEAN:
attr.getBooleanValue().ifPresent(dataBuilder::setBoolV);
break;
case LONG:
attr.getLongValue().ifPresent(dataBuilder::setLongV);
break;
case DOUBLE:
attr.getDoubleValue().ifPresent(dataBuilder::setDoubleV);
break;
case JSON:
attr.getJsonValue().ifPresent(dataBuilder::setJsonV);
break;
case STRING:
attr.getStrValue().ifPresent(dataBuilder::setStringV);
break;
}
return TsKvProto.newBuilder().setTs(ts).setKv(dataBuilder);
}
private static TransportProtos.TsValueProto toTsValueProto(long ts, KvEntry attr) {
TransportProtos.TsValueProto.Builder dataBuilder = TransportProtos.TsValueProto.newBuilder();
dataBuilder.setTs(ts);
dataBuilder.setType(KeyValueType.forNumber(attr.getDataType().ordinal()));
switch (attr.getDataType()) {
case BOOLEAN:
attr.getBooleanValue().ifPresent(dataBuilder::setBoolV);
break;
case LONG:
attr.getLongValue().ifPresent(dataBuilder::setLongV);
break;
case DOUBLE:
attr.getDoubleValue().ifPresent(dataBuilder::setDoubleV);
break;
case JSON:
attr.getJsonValue().ifPresent(dataBuilder::setJsonV);
break;
case STRING:
attr.getStrValue().ifPresent(dataBuilder::setStringV);
break;
}
return dataBuilder.build();
}
public static EntityId toEntityId(String entityType, long entityIdMSB, long entityIdLSB) { public static EntityId toEntityId(String entityType, long entityIdMSB, long entityIdLSB) {
return EntityIdFactory.getByTypeAndUuid(entityType, new UUID(entityIdMSB, entityIdLSB)); return EntityIdFactory.getByTypeAndUuid(entityType, new UUID(entityIdMSB, entityIdLSB));
} }
public static List<TsKvEntry> toTsKvEntityList(List<TsKvProto> dataList) {
List<TsKvEntry> result = new ArrayList<>(dataList.size());
dataList.forEach(proto -> result.add(new BasicTsKvEntry(proto.getTs(), getKvEntry(proto.getKv()))));
return result;
}
public static List<AttributeKvEntry> toAttributeKvList(List<TsKvProto> dataList) {
List<AttributeKvEntry> result = new ArrayList<>(dataList.size());
dataList.forEach(proto -> result.add(new BaseAttributeKvEntry(getKvEntry(proto.getKv()), proto.getTs())));
return result;
}
private static KvEntry getKvEntry(KeyValueProto proto) {
KvEntry entry = null;
DataType type = DataType.values()[proto.getType().getNumber()];
switch (type) {
case BOOLEAN:
entry = new BooleanDataEntry(proto.getKey(), proto.getBoolV());
break;
case LONG:
entry = new LongDataEntry(proto.getKey(), proto.getLongV());
break;
case DOUBLE:
entry = new DoubleDataEntry(proto.getKey(), proto.getDoubleV());
break;
case STRING:
entry = new StringDataEntry(proto.getKey(), proto.getStringV());
break;
case JSON:
entry = new JsonDataEntry(proto.getKey(), proto.getJsonV());
break;
}
return entry;
}
public static List<TsKvEntry> toTsKvEntityList(String key, List<TransportProtos.TsValueProto> dataList) {
List<TsKvEntry> result = new ArrayList<>(dataList.size());
dataList.forEach(proto -> result.add(new BasicTsKvEntry(proto.getTs(), getKvEntry(key, proto))));
return result;
}
private static KvEntry getKvEntry(String key, TransportProtos.TsValueProto proto) {
KvEntry entry = null;
DataType type = DataType.values()[proto.getType().getNumber()];
switch (type) {
case BOOLEAN:
entry = new BooleanDataEntry(key, proto.getBoolV());
break;
case LONG:
entry = new LongDataEntry(key, proto.getLongV());
break;
case DOUBLE:
entry = new DoubleDataEntry(key, proto.getDoubleV());
break;
case STRING:
entry = new StringDataEntry(key, proto.getStringV());
break;
case JSON:
entry = new JsonDataEntry(key, proto.getJsonV());
break;
}
return entry;
}
public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) { public static ToCoreMsg toAlarmUpdateProto(TenantId tenantId, EntityId entityId, AlarmInfo alarm) {
TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder(); TbAlarmUpdateProto.Builder builder = TbAlarmUpdateProto.newBuilder();
builder.setEntityType(entityId.getEntityType().name()); builder.setEntityType(entityId.getEntityType().name());
@ -405,7 +284,7 @@ public class TbSubscriptionUtils {
public static List<TsKvEntry> fromProto(TransportProtos.TbSubUpdateProto proto) { public static List<TsKvEntry> fromProto(TransportProtos.TbSubUpdateProto proto) {
List<TsKvEntry> result = new ArrayList<>(); List<TsKvEntry> result = new ArrayList<>();
for (var p : proto.getDataList()) { for (var p : proto.getDataList()) {
result.addAll(toTsKvEntityList(p.getKey(), p.getTsValueList())); result.addAll(fromTsValueProtoList(p.getKey(), p.getTsValueList()));
} }
return result; return result;
} }

6
application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java

@ -101,7 +101,11 @@ public class DefaultEntitiesExportImportService implements EntitiesExportImportS
ctx.putInternalId(exportData.getExternalId(), importResult.getSavedEntity().getId()); ctx.putInternalId(exportData.getExternalId(), importResult.getSavedEntity().getId());
ctx.addReferenceCallback(exportData.getExternalId(), importResult.getSaveReferencesCallback()); ctx.addReferenceCallback(exportData.getExternalId(), importResult.getSaveReferencesCallback());
ctx.addEventCallback(importResult.getSendEventsCallback()); if (ctx.isRollbackOnError()) {
ctx.addEventCallback(importResult.getSendEventsCallback());
} else {
importResult.getSendEventsCallback().run();
}
return importResult; return importResult;
} }

4
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java

@ -236,14 +236,14 @@ public abstract class AbstractBulkImportService<E extends HasId<? extends Entity
@Override @Override
public void onSuccess(Void unused) { public void onSuccess(Void unused) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
null, ActionType.ATTRIBUTES_UPDATED, null, scope, attributes); null, ActionType.ATTRIBUTES_UPDATED, null, AttributeScope.valueOf(scope), attributes);
} }
@Override @Override
public void onFailure(Throwable throwable) { public void onFailure(Throwable throwable) {
entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null, entityActionService.logEntityAction(user, (UUIDBased & EntityId) entityId, null,
null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable), null, ActionType.ATTRIBUTES_UPDATED, BaseController.toException(throwable),
scope, attributes); AttributeScope.valueOf(scope), attributes);
throw new RuntimeException(throwable); throw new RuntimeException(throwable);
} }

76
application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.sync.ie.EntityExportData; import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; import org.thingsboard.server.common.data.sync.ie.EntityExportSettings;
@ -89,6 +90,7 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.function.Function; import java.util.function.Function;
@ -246,16 +248,18 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
switch (request.getType()) { switch (request.getType()) {
case SINGLE_ENTITY: { case SINGLE_ENTITY: {
SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request; SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request;
ctx.setRollbackOnError(true);
VersionLoadConfig config = versionLoadRequest.getConfig(); VersionLoadConfig config = versionLoadRequest.getConfig();
ListenableFuture<EntityExportData> future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId()); ListenableFuture<EntityExportData> future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId());
DonAsynchron.withCallback(future, DonAsynchron.withCallback(future,
entityData -> doInTemplate(ctx, request, c -> loadSingleEntity(c, config, entityData)), entityData -> load(ctx, request, c -> loadSingleEntity(c, config, entityData)),
e -> processLoadError(ctx, e), executor); e -> processLoadError(ctx, e), executor);
break; break;
} }
case ENTITY_TYPE: { case ENTITY_TYPE: {
EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request; EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request;
executor.submit(() -> doInTemplate(ctx, request, c -> loadMultipleEntities(c, versionLoadRequest))); ctx.setRollbackOnError(versionLoadRequest.isRollbackOnError());
executor.submit(() -> load(ctx, request, c -> loadMultipleEntities(c, versionLoadRequest)));
break; break;
} }
default: default:
@ -265,19 +269,24 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
return ctx.getRequestId(); return ctx.getRequestId();
} }
private <R> VersionLoadResult doInTemplate(EntitiesImportCtx ctx, VersionLoadRequest request, Function<EntitiesImportCtx, VersionLoadResult> function) { private <R> VersionLoadResult load(EntitiesImportCtx ctx, VersionLoadRequest request, Function<EntitiesImportCtx, VersionLoadResult> loadFunction) {
try { try {
VersionLoadResult result = transactionTemplate.execute(status -> { VersionLoadResult result;
try { if (ctx.isRollbackOnError()) {
return function.apply(ctx); result = transactionTemplate.execute(status -> {
} catch (RuntimeException e) { try {
throw e; return loadFunction.apply(ctx);
} catch (Exception e) { } catch (RuntimeException e) {
throw new RuntimeException(e); // to prevent UndeclaredThrowableException throw e;
} catch (Exception e) {
throw new RuntimeException(e); // to prevent UndeclaredThrowableException
}
});
for (ThrowingRunnable eventCallback : ctx.getEventCallbacks()) {
eventCallback.run();
} }
}); } else {
for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) { result = loadFunction.apply(ctx);
throwingRunnable.run();
} }
result.setDone(true); result.setDone(true);
return cachePut(ctx.getRequestId(), result); return cachePut(ctx.getRequestId(), result);
@ -324,7 +333,6 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
sw.startNew("Entities " + entityType.name()); sw.startNew("Entities " + entityType.name());
ctx.setSettings(getEntityImportSettings(request, entityType)); ctx.setSettings(getEntityImportSettings(request, entityType));
importEntities(ctx, entityType); importEntities(ctx, entityType);
persistToCache(ctx);
} }
sw.startNew("Reimport"); sw.startNew("Reimport");
@ -336,7 +344,6 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
.filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities()) .filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities())
.sorted(exportImportService.getEntityTypeComparatorForImport().reversed()) .sorted(exportImportService.getEntityTypeComparatorForImport().reversed())
.forEach(entityType -> removeOtherEntities(ctx, entityType)); .forEach(entityType -> removeOtherEntities(ctx, entityType));
persistToCache(ctx);
sw.startNew("References and Relations"); sw.startNew("References and Relations");
exportImportService.saveReferencesAndRelations(ctx); exportImportService.saveReferencesAndRelations(ctx);
@ -389,6 +396,8 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>()) ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>())
.add(importResult.getSavedEntity().getId()); .add(importResult.getSavedEntity().getId());
} }
persistToCache(ctx);
log.debug("Imported {} pack ({}) for tenant {}", entityType, entityDataList.size(), ctx.getTenantId()); log.debug("Imported {} pack ({}) for tenant {}", entityType, entityDataList.size(), ctx.getTenantId());
offset += limit; offset += limit;
} while (entityDataList.size() == limit); } while (entityDataList.size() == limit);
@ -413,17 +422,34 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
} }
private void removeOtherEntities(EntitiesImportCtx ctx, EntityType entityType) { private void removeOtherEntities(EntitiesImportCtx ctx, EntityType entityType) {
DaoUtil.processInBatches(pageLink -> { var entities = new PageDataIterable<>(link -> exportableEntitiesService.findEntitiesIdsByTenantId(ctx.getTenantId(), entityType, link), 100);
return exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink); Set<EntityId> toRemove = new HashSet<>();
}, 100, entity -> { for (EntityId entityId : entities) {
if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entity.getId())) { if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entityId)) {
exportableEntitiesService.removeById(ctx.getTenantId(), entity.getId()); toRemove.add(entityId);
ctx.addEventCallback(() -> logEntityActionService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null,
ActionType.DELETED, ctx.getUser()));
ctx.registerDeleted(entityType);
} }
}); }
for (EntityId entityId : toRemove) {
ExportableEntity<EntityId> entity = exportableEntitiesService.findEntityById(entityId);
exportableEntitiesService.removeById(ctx.getTenantId(), entityId);
ThrowingRunnable callback = () -> {
logEntityActionService.logEntityAction(ctx.getTenantId(), entity.getId(), entity, null,
ActionType.DELETED, ctx.getUser());
};
if (ctx.isRollbackOnError()) {
ctx.addEventCallback(callback);
} else {
try {
callback.run();
} catch (ThingsboardException e) {
throw new RuntimeException(e);
}
}
ctx.registerDeleted(entityType);
}
persistToCache(ctx);
} }
private VersionLoadResult onError(EntityId externalId, Throwable e) { private VersionLoadResult onError(EntityId externalId, Throwable e) {

1
application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java

@ -58,6 +58,7 @@ public class EntitiesImportCtx {
private boolean finalImportAttempt = false; private boolean finalImportAttempt = false;
private EntityImportSettings settings; private EntityImportSettings settings;
private EntityImportResult<?> currentImportResult; private EntityImportResult<?> currentImportResult;
private boolean rollbackOnError;
public EntitiesImportCtx(UUID requestId, User user, String versionId) { public EntitiesImportCtx(UUID requestId, User user, String versionId) {
this(requestId, user, versionId, null); this(requestId, user, versionId, null);

2
application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java

@ -68,7 +68,7 @@ public class RpcCleanUpService {
long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getRpcTtlDays()); long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getRpcTtlDays());
long expirationTime = System.currentTimeMillis() - ttl; long expirationTime = System.currentTimeMillis() - ttl;
long totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime); int totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime);
if (totalRemoved > 0) { if (totalRemoved > 0) {
log.info("Removed {} outdated rpc(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime)); log.info("Removed {} outdated rpc(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime));

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

@ -866,6 +866,8 @@ js:
monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}" monitor_thread_pool_size: "${LOCAL_JS_SANDBOX_MONITOR_THREAD_POOL_SIZE:4}"
# Maximum CPU time in milliseconds allowed for script execution # Maximum CPU time in milliseconds allowed for script execution
max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:8000}" max_cpu_time: "${LOCAL_JS_SANDBOX_MAX_CPU_TIME:8000}"
# Maximum memory in Bytes which JS executor thread can allocate (approximate calculation). A zero memory limit in combination with a non-zero CPU limit is not recommended due to the implementation of Nashorn 0.4.2. 100MiB is effectively unlimited for most cases
max_memory: "${LOCAL_JS_SANDBOX_MAX_MEMORY:104857600}"
# Maximum allowed JavaScript execution errors before JavaScript will be blacklisted # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted
max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}" max_errors: "${LOCAL_JS_SANDBOX_MAX_ERRORS:3}"
# JS Eval max request timeout. 0 - no timeout # JS Eval max request timeout. 0 - no timeout
@ -1304,6 +1306,8 @@ edges:
private_key: "${EDGES_RPC_SSL_PRIVATE_KEY:privateKeyFile.pem}" private_key: "${EDGES_RPC_SSL_PRIVATE_KEY:privateKeyFile.pem}"
# Maximum size (in bytes) of inbound messages the cloud can handle from the edge. By default, it can handle messages up to 4 Megabytes # Maximum size (in bytes) of inbound messages the cloud can handle from the edge. By default, it can handle messages up to 4 Megabytes
max_inbound_message_size: "${EDGES_RPC_MAX_INBOUND_MESSAGE_SIZE:4194304}" max_inbound_message_size: "${EDGES_RPC_MAX_INBOUND_MESSAGE_SIZE:4194304}"
# Maximum length of telemetry (time-series and attributes) message the cloud sends to the edge. By default, there is no limitation.
max_telemetry_message_size: "${EDGES_RPC_MAX_TELEMETRY_MESSAGE_SIZE:0}"
storage: storage:
# Max records of edge event to read from DB and sent to the edge # Max records of edge event to read from DB and sent to the edge
max_read_records_count: "${EDGES_STORAGE_MAX_READ_RECORDS_COUNT:50}" max_read_records_count: "${EDGES_STORAGE_MAX_READ_RECORDS_COUNT:50}"
@ -1332,6 +1336,8 @@ springdoc:
swagger: swagger:
# General swagger match pattern of swagger UI links # General swagger match pattern of swagger UI links
api_path: "${SWAGGER_API_PATH:/api/**}" api_path: "${SWAGGER_API_PATH:/api/**}"
# General swagger match pattern path of swagger UI links
security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api/.*}"
# Nonsecurity API path match pattern of swagger UI links # Nonsecurity API path match pattern of swagger UI links
non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/(?:noauth|v1)/.*}" non_security_path_regex: "${SWAGGER_NON_SECURITY_PATH_REGEX:/api/(?:noauth|v1)/.*}"
# The title on the API doc UI page # The title on the API doc UI page

53
application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java

@ -29,6 +29,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
@ -45,16 +46,17 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -327,6 +329,53 @@ public class AssetControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarm.getId().getId().toString())))); .andExpect(statusReason(containsString(msgErrorNoFound("Alarm", alarm.getId().getId().toString()))));
} }
@Test
public void testDeleteAssetWithPropagatedAlarm() throws Exception {
Device device = new Device();
device.setTenantId(savedTenant.getTenantId());
device.setName("Test device");
device.setLabel("Label");
device.setType("default");
device = doPost("/api/device", device, Device.class);
Asset asset = new Asset();
asset.setName("My asset");
asset.setType("default");
asset = doPost("/api/asset", asset, Asset.class);
EntityRelation entityRelation = new EntityRelation(asset.getId(), device.getId(), "CONTAINS");
doPost("/api/relation", entityRelation);
//create alarm
Alarm alarm = Alarm.builder()
.tenantId(savedTenant.getTenantId())
.originator(device.getId())
.severity(AlarmSeverity.CRITICAL)
.type("test_type")
.propagate(true)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
PageData<AlarmInfo> deviceAlarms = doGetTyped("/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=10", new TypeReference<>() {
});
assertThat(deviceAlarms.getData()).hasSize(1);
PageData<AlarmInfo> assetAlarms = doGetTyped("/api/alarm/ASSET/" + asset.getUuidId() + "?page=0&pageSize=10", new TypeReference<>() {
});
assertThat(assetAlarms.getData()).hasSize(1);
//delete asset
doDelete("/api/asset/" + asset.getId().getId().toString())
.andExpect(status().isOk());
//check device alarms
PageData<AlarmInfo> deviceAlarmsAfterAssetDeletion = doGetTyped("/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=10", new TypeReference<PageData<AlarmInfo>>() {
});
assertThat(deviceAlarmsAfterAssetDeletion.getData()).hasSize(1);
}
@Test @Test
public void testDeleteAssetAssignedToEntityView() throws Exception { public void testDeleteAssetAssignedToEntityView() throws Exception {
Asset asset1 = new Asset(); Asset asset1 = new Asset();
@ -567,8 +616,6 @@ public class AssetControllerTest extends AbstractControllerTest {
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED, cntEntity, cntEntity, cntEntity); ActionType.ADDED, cntEntity, cntEntity, cntEntity);
loadedAssets.removeIf(asset -> asset.getType().equals(DefaultRuleEngineStatisticsService.TB_SERVICE_QUEUE));
assets.sort(idComparator); assets.sort(idComparator);
loadedAssets.sort(idComparator); loadedAssets.sort(idComparator);

21
application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java

@ -25,7 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -34,12 +33,13 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.queue.ProcessingStrategy; import org.thingsboard.server.common.data.queue.ProcessingStrategy;
import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.common.data.queue.SubmitStrategy;
import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.queue.SubmitStrategyType;
import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.timeseries.TimeseriesDao; import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
@ -50,6 +50,7 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingRes
import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService;
import org.thingsboard.server.service.stats.RuleEngineStatisticsService; import org.thingsboard.server.service.stats.RuleEngineStatisticsService;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -66,7 +67,6 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@DaoSqlTest @DaoSqlTest
@TestPropertySource(properties = { @TestPropertySource(properties = {
@ -81,7 +81,7 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
@SpyBean @SpyBean
private TimeseriesDao timeseriesDao; private TimeseriesDao timeseriesDao;
@Autowired @Autowired
private AssetService assetService; private QueueStatsService queueStatsService;
@Test @Test
public void testQueueWithServiceTypeRE() throws Exception { public void testQueueWithServiceTypeRE() throws Exception {
@ -176,16 +176,17 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
}); });
ruleEngineStatisticsService.reportQueueStats(System.currentTimeMillis(), testStats); ruleEngineStatisticsService.reportQueueStats(System.currentTimeMillis(), testStats);
Asset serviceAsset = assetService.findAssetsByTenantIdAndType(tenantId, TB_SERVICE_QUEUE, new PageLink(100)).getData() List<QueueStats> queueStatsList = queueStatsService.findByTenantId(tenantId);
.stream().filter(asset -> asset.getName().startsWith(queue.getName())) assertThat(queueStatsList).hasSize(1);
.findFirst().get(); QueueStats queueStats = queueStatsList.get(0);
assertThat(queueStats.getQueueName()).isEqualTo(queue.getName());
ArgumentCaptor<Long> ttlCaptor = ArgumentCaptor.forClass(Long.class); ArgumentCaptor<Long> ttlCaptor = ArgumentCaptor.forClass(Long.class);
verify(timeseriesDao).save(eq(tenantId), eq(serviceAsset.getId()), argThat(tsKvEntry -> { verify(timeseriesDao).save(eq(tenantId), eq(queueStats.getId()), argThat(tsKvEntry -> {
return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.SUCCESSFUL_MSGS) && return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.SUCCESSFUL_MSGS) &&
tsKvEntry.getLongValue().get().equals(5L); tsKvEntry.getLongValue().get().equals(5L);
}), ttlCaptor.capture()); }), ttlCaptor.capture());
verify(timeseriesDao).save(eq(tenantId), eq(serviceAsset.getId()), argThat(tsKvEntry -> { verify(timeseriesDao).save(eq(tenantId), eq(queueStats.getId()), argThat(tsKvEntry -> {
return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.FAILED_MSGS) && return tsKvEntry.getKey().equals(TbRuleEngineConsumerStats.FAILED_MSGS) &&
tsKvEntry.getLongValue().get().equals(5L); tsKvEntry.getLongValue().get().equals(5L);
}), ttlCaptor.capture()); }), ttlCaptor.capture());
@ -193,7 +194,7 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
assertThat(usedTtl).isEqualTo(TimeUnit.DAYS.toSeconds(queueStatsTtlDays)); assertThat(usedTtl).isEqualTo(TimeUnit.DAYS.toSeconds(queueStatsTtlDays));
}); });
verify(timeseriesDao).save(eq(tenantId), eq(serviceAsset.getId()), argThat(tsKvEntry -> { verify(timeseriesDao).save(eq(tenantId), eq(queueStats.getId()), argThat(tsKvEntry -> {
return tsKvEntry.getKey().equals(DefaultRuleEngineStatisticsService.RULE_ENGINE_EXCEPTION) && return tsKvEntry.getKey().equals(DefaultRuleEngineStatisticsService.RULE_ENGINE_EXCEPTION) &&
tsKvEntry.getJsonValue().get().equals(ruleEngineException.toJsonString(0)); tsKvEntry.getJsonValue().get().equals(ruleEngineException.toJsonString(0));
}), ttlCaptor.capture()); }), ttlCaptor.capture());

38
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -76,6 +76,7 @@ import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg; import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg;
@ -631,7 +632,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
List<Edge> loadedEdges = new ArrayList<>(); List<Edge> loadedEdges = new ArrayList<>();
PageLink pageLink = new PageLink(23); PageLink pageLink = new PageLink(23);
PageData<Edge> pageData = null; PageData<Edge> pageData;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
new TypeReference<>() { new TypeReference<>() {
@ -885,6 +886,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret()); EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); edgeImitator.ignoreType(UserCredentialsUpdateMsg.class);
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
edgeImitator.expectMessageAmount(24); edgeImitator.expectMessageAmount(24);
edgeImitator.connect(); edgeImitator.connect();
@ -973,8 +975,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popQueueMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popQueueMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof QueueUpdateMsg) { if (message instanceof QueueUpdateMsg queueUpdateMsg) {
QueueUpdateMsg queueUpdateMsg = (QueueUpdateMsg) message;
Queue queue = JacksonUtil.fromString(queueUpdateMsg.getEntity(), Queue.class, true); Queue queue = JacksonUtil.fromString(queueUpdateMsg.getEntity(), Queue.class, true);
Assert.assertNotNull(queue); Assert.assertNotNull(queue);
if (msgType.equals(queueUpdateMsg.getMsgType()) && name.equals(queue.getName())) { if (msgType.equals(queueUpdateMsg.getMsgType()) && name.equals(queue.getName())) {
@ -988,8 +989,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popRuleChainMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popRuleChainMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof RuleChainUpdateMsg) { if (message instanceof RuleChainUpdateMsg ruleChainUpdateMsg) {
RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) message;
RuleChain ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true); RuleChain ruleChain = JacksonUtil.fromString(ruleChainUpdateMsg.getEntity(), RuleChain.class, true);
Assert.assertNotNull(ruleChain); Assert.assertNotNull(ruleChain);
if (msgType.equals(ruleChainUpdateMsg.getMsgType()) if (msgType.equals(ruleChainUpdateMsg.getMsgType())
@ -1005,8 +1005,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key) { private boolean popAdminSettingsMsg(List<AbstractMessage> messages, String key) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AdminSettingsUpdateMsg) { if (message instanceof AdminSettingsUpdateMsg adminSettingsUpdateMsg) {
AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message;
AdminSettings adminSettings = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true); AdminSettings adminSettings = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true);
Assert.assertNotNull(adminSettings); Assert.assertNotNull(adminSettings);
if (key.equals(adminSettings.getKey())) { if (key.equals(adminSettings.getKey())) {
@ -1020,8 +1019,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popDeviceProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popDeviceProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof DeviceProfileUpdateMsg) { if (message instanceof DeviceProfileUpdateMsg deviceProfileUpdateMsg) {
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) message;
DeviceProfile deviceProfile = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true); DeviceProfile deviceProfile = JacksonUtil.fromString(deviceProfileUpdateMsg.getEntity(), DeviceProfile.class, true);
Assert.assertNotNull(deviceProfile); Assert.assertNotNull(deviceProfile);
if (msgType.equals(deviceProfileUpdateMsg.getMsgType()) if (msgType.equals(deviceProfileUpdateMsg.getMsgType())
@ -1036,8 +1034,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popDeviceMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popDeviceMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof DeviceUpdateMsg) { if (message instanceof DeviceUpdateMsg deviceUpdateMsg) {
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) message;
Device device = JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true); Device device = JacksonUtil.fromString(deviceUpdateMsg.getEntity(), Device.class, true);
Assert.assertNotNull(device); Assert.assertNotNull(device);
if (msgType.equals(deviceUpdateMsg.getMsgType()) if (msgType.equals(deviceUpdateMsg.getMsgType())
@ -1052,8 +1049,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popAssetProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popAssetProfileMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AssetProfileUpdateMsg) { if (message instanceof AssetProfileUpdateMsg assetProfileUpdateMsg) {
AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) message;
AssetProfile assetProfile = JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true); AssetProfile assetProfile = JacksonUtil.fromString(assetProfileUpdateMsg.getEntity(), AssetProfile.class, true);
Assert.assertNotNull(assetProfile); Assert.assertNotNull(assetProfile);
if (msgType.equals(assetProfileUpdateMsg.getMsgType()) if (msgType.equals(assetProfileUpdateMsg.getMsgType())
@ -1068,8 +1064,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popAssetMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) { private boolean popAssetMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String name) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof AssetUpdateMsg) { if (message instanceof AssetUpdateMsg assetUpdateMsg) {
AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) message;
Asset asset = JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true); Asset asset = JacksonUtil.fromString(assetUpdateMsg.getEntity(), Asset.class, true);
Assert.assertNotNull(asset); Assert.assertNotNull(asset);
if (msgType.equals(assetUpdateMsg.getMsgType()) if (msgType.equals(assetUpdateMsg.getMsgType())
@ -1084,8 +1079,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popUserMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String email, Authority authority) { private boolean popUserMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String email, Authority authority) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof UserUpdateMsg) { if (message instanceof UserUpdateMsg userUpdateMsg) {
UserUpdateMsg userUpdateMsg = (UserUpdateMsg) message;
User user = JacksonUtil.fromString(userUpdateMsg.getEntity(), User.class, true); User user = JacksonUtil.fromString(userUpdateMsg.getEntity(), User.class, true);
Assert.assertNotNull(user); Assert.assertNotNull(user);
if (msgType.equals(userUpdateMsg.getMsgType()) if (msgType.equals(userUpdateMsg.getMsgType())
@ -1101,8 +1095,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popCustomerMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String title) { private boolean popCustomerMsg(List<AbstractMessage> messages, UpdateMsgType msgType, String title) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof CustomerUpdateMsg) { if (message instanceof CustomerUpdateMsg customerUpdateMsg) {
CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) message;
Customer customer = JacksonUtil.fromString(customerUpdateMsg.getEntity(), Customer.class, true); Customer customer = JacksonUtil.fromString(customerUpdateMsg.getEntity(), Customer.class, true);
Assert.assertNotNull(customer); Assert.assertNotNull(customer);
if (msgType.equals(customerUpdateMsg.getMsgType()) if (msgType.equals(customerUpdateMsg.getMsgType())
@ -1117,8 +1110,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popTenantMsg(List<AbstractMessage> messages, TenantId tenantId1) { private boolean popTenantMsg(List<AbstractMessage> messages, TenantId tenantId1) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof TenantUpdateMsg) { if (message instanceof TenantUpdateMsg tenantUpdateMsg) {
TenantUpdateMsg tenantUpdateMsg = (TenantUpdateMsg) message;
Tenant tenant = JacksonUtil.fromString(tenantUpdateMsg.getEntity(), Tenant.class, true); Tenant tenant = JacksonUtil.fromString(tenantUpdateMsg.getEntity(), Tenant.class, true);
Assert.assertNotNull(tenant); Assert.assertNotNull(tenant);
if (UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(tenantUpdateMsg.getMsgType()) if (UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(tenantUpdateMsg.getMsgType())
@ -1133,8 +1125,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
private boolean popTenantProfileMsg(List<AbstractMessage> messages, TenantProfileId tenantProfileId) { private boolean popTenantProfileMsg(List<AbstractMessage> messages, TenantProfileId tenantProfileId) {
for (AbstractMessage message : messages) { for (AbstractMessage message : messages) {
if (message instanceof TenantProfileUpdateMsg) { if (message instanceof TenantProfileUpdateMsg tenantProfileUpdateMsg) {
TenantProfileUpdateMsg tenantProfileUpdateMsg = (TenantProfileUpdateMsg) message;
TenantProfile tenantProfile = JacksonUtil.fromString(tenantProfileUpdateMsg.getEntity(), TenantProfile.class, true); TenantProfile tenantProfile = JacksonUtil.fromString(tenantProfileUpdateMsg.getEntity(), TenantProfile.class, true);
Assert.assertNotNull(tenantProfile); Assert.assertNotNull(tenantProfile);
if (UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(tenantProfileUpdateMsg.getMsgType()) if (UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(tenantProfileUpdateMsg.getMsgType())
@ -1236,4 +1227,5 @@ public class EdgeControllerTest extends AbstractControllerTest {
edgeUpgradeInstructionsService.setAppVersion("3.6.2.6"); edgeUpgradeInstructionsService.setAppVersion("3.6.2.6");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId())); Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
} }
} }

51
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -22,11 +22,13 @@ import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
@ -51,10 +53,13 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.queue.QueueStatsService;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -69,6 +74,9 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@Autowired
private QueueStatsService queueStatsService;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
@ -593,4 +601,47 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
assertThat(getErrorMessage(result)).contains("Invalid").contains("sort property"); assertThat(getErrorMessage(result)).contains("Invalid").contains("sort property");
} }
@Test
public void testFindQueueStatsEntitiesByQuery() throws Exception {
List<QueueStats> queueStatsList = new ArrayList<>();
for (int i = 0; i < 97; i++) {
QueueStats queueStats = new QueueStats();
queueStats.setQueueName(StringUtils.randomAlphabetic(5));
queueStats.setServiceId(StringUtils.randomAlphabetic(5));
queueStats.setTenantId(savedTenant.getTenantId());
queueStatsList.add(queueStatsService.save(savedTenant.getId(), queueStats));
Thread.sleep(1);
}
EntityTypeFilter entityTypeFilter = new EntityTypeFilter();
entityTypeFilter.setEntityType(EntityType.QUEUE_STATS);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Arrays.asList(new EntityKey(EntityKeyType.ENTITY_FIELD, "queueName"),
new EntityKey(EntityKeyType.ENTITY_FIELD, "serviceId"));
EntityDataQuery query = new EntityDataQuery(entityTypeFilter, pageLink, entityFields, null, null);
PageData<EntityData> data =
doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {
});
Assert.assertEquals(97, data.getTotalElements());
Assert.assertEquals(10, data.getTotalPages());
Assert.assertTrue(data.hasNext());
Assert.assertEquals(10, data.getData().size());
data.getData().forEach(entityData -> {
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("queueName")).asString().isNotBlank();
assertThat(entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("serviceId")).asString().isNotBlank();
});
EntityCountQuery countQuery = new EntityCountQuery(entityTypeFilter);
Long count = doPostWithResponse("/api/entitiesQuery/count", countQuery, Long.class);
Assert.assertEquals(97, count.longValue());
}
} }

2
application/src/test/java/org/thingsboard/server/controller/HomePageApiTest.java

@ -494,7 +494,7 @@ public class HomePageApiTest extends AbstractControllerTest {
} }
private OAuth2Info createDefaultOAuth2Info() { private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, Lists.newArrayList( return new OAuth2Info(true, false, Lists.newArrayList(
OAuth2ParamsInfo.builder() OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList( .domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build() OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build()

3
application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java

@ -1109,13 +1109,14 @@ public class UserControllerTest extends AbstractControllerTest {
private List<UserEmailInfo> getUsersInfo(PageLink pageLink) throws Exception { private List<UserEmailInfo> getUsersInfo(PageLink pageLink) throws Exception {
List<UserEmailInfo> loadedCustomerUsers = new ArrayList<>(); List<UserEmailInfo> loadedCustomerUsers = new ArrayList<>();
PageData<UserEmailInfo> pageData = null; PageData<UserEmailInfo> pageData;
do { do {
pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() { pageData = doGetTypedWithPageLink("/api/users/info?", new TypeReference<>() {
}, pageLink); }, pageLink);
loadedCustomerUsers.addAll(pageData.getData()); loadedCustomerUsers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
Assert.assertEquals(pageLink.getPageSize(), pageData.getData().size());
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
return loadedCustomerUsers; return loadedCustomerUsers;

15
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -63,6 +63,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -85,6 +86,7 @@ import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
@ -140,6 +142,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
installation(); installation();
edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret());
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
edgeImitator.expectMessageAmount(21); edgeImitator.expectMessageAmount(21);
edgeImitator.connect(); edgeImitator.connect();
@ -538,6 +541,18 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
Assert.assertTrue(customer.isPublic()); Assert.assertTrue(customer.isPublic());
} }
private void validateOAuth2() throws Exception {
Optional<OAuth2UpdateMsg> oAuth2UpdateMsgOpt = edgeImitator.findMessageByType(OAuth2UpdateMsg.class);
Assert.assertTrue(oAuth2UpdateMsgOpt.isPresent());
OAuth2UpdateMsg oAuth2UpdateMsg = oAuth2UpdateMsgOpt.get();
OAuth2Info oAuth2Info = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true);
Assert.assertNotNull(oAuth2Info);
OAuth2Info auth2Info = doGet("/api/oauth2/config", OAuth2Info.class);
Assert.assertNotNull(auth2Info);
Assert.assertEquals(oAuth2Info, auth2Info);
testAutoGeneratedCodeByProtobuf(oAuth2UpdateMsg);
}
private void validateSyncCompleted() { private void validateSyncCompleted() {
Optional<SyncCompletedMsg> syncCompletedMsgOpt = edgeImitator.findMessageByType(SyncCompletedMsg.class); Optional<SyncCompletedMsg> syncCompletedMsgOpt = edgeImitator.findMessageByType(SyncCompletedMsg.class);
Assert.assertTrue(syncCompletedMsgOpt.isPresent()); Assert.assertTrue(syncCompletedMsgOpt.isPresent());

113
application/src/test/java/org/thingsboard/server/edge/OAuth2EdgeTest.java

@ -0,0 +1,113 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.edge;
import com.google.common.collect.Lists;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2Info;
import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo;
import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo;
import org.thingsboard.server.common.data.oauth2.SchemeType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import java.util.Arrays;
import java.util.Collections;
import java.util.UUID;
@DaoSqlTest
public class OAuth2EdgeTest extends AbstractEdgeTest {
@Test
public void testOAuth2Support() throws Exception {
loginSysAdmin();
// enable oauth
edgeImitator.allowIgnoredTypes();
edgeImitator.expectMessageAmount(1);
OAuth2Info oAuth2Info = createDefaultOAuth2Info();
oAuth2Info = doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg);
OAuth2UpdateMsg oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage;
OAuth2Info result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true);
Assert.assertEquals(oAuth2Info, result);
// disable oauth support
edgeImitator.expectMessageAmount(1);
oAuth2Info.setEnabled(false);
oAuth2Info.setEdgeEnabled(false);
doPost("/api/oauth2/config", oAuth2Info, OAuth2Info.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OAuth2UpdateMsg);
oAuth2UpdateMsg = (OAuth2UpdateMsg) latestMessage;
result = JacksonUtil.fromString(oAuth2UpdateMsg.getEntity(), OAuth2Info.class, true);
Assert.assertEquals(oAuth2Info, result);
edgeImitator.ignoreType(OAuth2UpdateMsg.class);
loginTenantAdmin();
}
private OAuth2Info createDefaultOAuth2Info() {
return new OAuth2Info(true, true, Lists.newArrayList(
OAuth2ParamsInfo.builder()
.domainInfos(Lists.newArrayList(
OAuth2DomainInfo.builder().name("domain").scheme(SchemeType.MIXED).build()
))
.mobileInfos(Collections.emptyList())
.clientRegistrations(Lists.newArrayList(
validRegistrationInfo()
))
.build()
));
}
private OAuth2RegistrationInfo validRegistrationInfo() {
return OAuth2RegistrationInfo.builder()
.clientId(UUID.randomUUID().toString())
.clientSecret(UUID.randomUUID().toString())
.authorizationUri(UUID.randomUUID().toString())
.accessTokenUri(UUID.randomUUID().toString())
.scope(Arrays.asList(UUID.randomUUID().toString(), UUID.randomUUID().toString()))
.platforms(Collections.emptyList())
.userInfoUri(UUID.randomUUID().toString())
.userNameAttributeName(UUID.randomUUID().toString())
.jwkSetUri(UUID.randomUUID().toString())
.clientAuthenticationMethod(UUID.randomUUID().toString())
.loginButtonLabel(UUID.randomUUID().toString())
.mapperConfig(
OAuth2MapperConfig.builder()
.type(MapperType.CUSTOM)
.custom(
OAuth2CustomMapperConfig.builder()
.url(UUID.randomUUID().toString())
.build()
)
.build()
)
.build();
}
}

6
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -44,6 +44,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OAuth2UpdateMsg;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
@ -314,6 +315,11 @@ public class EdgeImitator {
result.add(saveDownlinkMsg(resourceUpdateMsg)); result.add(saveDownlinkMsg(resourceUpdateMsg));
} }
} }
if (downlinkMsg.getOAuth2UpdateMsgCount() > 0) {
for (OAuth2UpdateMsg oAuth2UpdateMsg : downlinkMsg.getOAuth2UpdateMsgList()) {
result.add(saveDownlinkMsg(oAuth2UpdateMsg));
}
}
if (downlinkMsg.hasEdgeConfiguration()) { if (downlinkMsg.hasEdgeConfiguration()) {
result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration())); result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration()));
} }

25
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java

@ -46,6 +46,7 @@ import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.edge.EdgeSynchronizationManager; import org.thingsboard.server.dao.edge.EdgeSynchronizationManager;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
@ -81,6 +82,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstruct
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorV1; import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorV1;
import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorV2; import org.thingsboard.server.service.edge.rpc.constructor.entityview.EntityViewMsgConstructorV2;
import org.thingsboard.server.service.edge.rpc.constructor.oauth2.OAuth2MsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory; import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorFactory;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorV1; import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorV1;
import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorV2; import org.thingsboard.server.service.edge.rpc.constructor.ota.OtaPackageMsgConstructorV2;
@ -128,6 +130,7 @@ import org.thingsboard.server.service.edge.rpc.processor.device.profile.DevicePr
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorV1; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorV1;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorV2; import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewProcessorV2;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorFactory; import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorFactory;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorV1; import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorV1;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorV2; import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessorV2;
@ -239,6 +242,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean @MockBean
protected ResourceService resourceService; protected ResourceService resourceService;
@MockBean
protected OAuth2Service oAuth2Service;
@MockBean @MockBean
@Lazy @Lazy
protected TbQueueProducerProvider producerProvider; protected TbQueueProducerProvider producerProvider;
@ -360,6 +366,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean @MockBean
protected WidgetMsgConstructorV2 widgetMsgConstructorV2; protected WidgetMsgConstructorV2 widgetMsgConstructorV2;
@MockBean
protected OAuth2MsgConstructor oAuth2MsgConstructor;
@MockBean @MockBean
protected AlarmEdgeProcessorV1 alarmProcessorV1; protected AlarmEdgeProcessorV1 alarmProcessorV1;
@ -417,6 +426,9 @@ public abstract class BaseEdgeProcessorTest {
@MockBean @MockBean
protected RelationEdgeProcessorV2 relationEdgeProcessorV2; protected RelationEdgeProcessorV2 relationEdgeProcessorV2;
@MockBean
protected OAuth2EdgeProcessor oAuth2EdgeProcessor;
@SpyBean @SpyBean
protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory; protected RuleChainMsgConstructorFactory ruleChainMsgConstructorFactory;
@ -514,17 +526,18 @@ public abstract class BaseEdgeProcessorTest {
} }
protected static Stream<Arguments> provideParameters() { protected static Stream<Arguments> provideParameters() {
UUID dashoboardUUID = UUID.randomUUID(); UUID dashboardUUID = UUID.randomUUID();
UUID ruleChaindUUID = UUID.randomUUID(); UUID ruleChainUUID = UUID.randomUUID();
return Stream.of( return Stream.of(
Arguments.of(EdgeVersion.V_3_3_0, 0, 0, 0, 0), Arguments.of(EdgeVersion.V_3_3_0, 0, 0, 0, 0),
Arguments.of(EdgeVersion.V_3_3_3, 0, 0, 0, 0), Arguments.of(EdgeVersion.V_3_3_3, 0, 0, 0, 0),
Arguments.of(EdgeVersion.V_3_4_0, 0, 0, 0, 0), Arguments.of(EdgeVersion.V_3_4_0, 0, 0, 0, 0),
Arguments.of(EdgeVersion.V_3_6_0, Arguments.of(EdgeVersion.V_3_6_0,
dashoboardUUID.getMostSignificantBits(), dashboardUUID.getMostSignificantBits(),
dashoboardUUID.getLeastSignificantBits(), dashboardUUID.getLeastSignificantBits(),
ruleChaindUUID.getMostSignificantBits(), ruleChainUUID.getMostSignificantBits(),
ruleChaindUUID.getLeastSignificantBits()) ruleChainUUID.getLeastSignificantBits())
); );
} }
} }

40
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java

@ -16,27 +16,51 @@
package org.thingsboard.server.service.edge.rpc.processor.telemetry; package org.thingsboard.server.service.edge.rpc.processor.telemetry;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.Mockito;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessorTest;
@Slf4j import static org.mockito.ArgumentMatchers.any;
@RunWith(MockitoJUnitRunner.class) import static org.mockito.Mockito.verify;
public class TelemetryEdgeProcessorTest {
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {TelemetryEdgeProcessor.class})
@TestPropertySource(properties = {
"edges.rpc.max_telemetry_message_size=1000"
})
public class TelemetryEdgeProcessorTest extends BaseEdgeProcessorTest {
@SpyBean
private TelemetryEdgeProcessor telemetryEdgeProcessor;
@MockBean
private NotificationRuleProcessor notificationRuleProcessor;
@Test @Test
public void testConvert_maxSizeLimit() throws Exception { public void testConvert_maxSizeLimit() {
Edge edge = new Edge();
EdgeEvent edgeEvent = new EdgeEvent(); EdgeEvent edgeEvent = new EdgeEvent();
ObjectNode body = JacksonUtil.newObjectNode(); ObjectNode body = JacksonUtil.newObjectNode();
body.put("value", StringUtils.randomAlphanumeric(10000)); body.put("value", StringUtils.randomAlphanumeric(1000));
edgeEvent.setBody(body); edgeEvent.setBody(body);
DownlinkMsg downlinkMsg = new TelemetryEdgeProcessor().convertTelemetryEventToDownlink(edgeEvent);
DownlinkMsg downlinkMsg = telemetryEdgeProcessor.convertTelemetryEventToDownlink(edge, edgeEvent);
Assert.assertNull(downlinkMsg); Assert.assertNull(downlinkMsg);
verify(notificationRuleProcessor, Mockito.times(1)).process(any());
} }
} }

80
application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java

@ -15,23 +15,29 @@
*/ */
package org.thingsboard.server.service.script; package org.thingsboard.server.service.script;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert; import org.junit.Assert;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.TbStopWatch;
import org.thingsboard.script.api.ScriptType; import org.thingsboard.script.api.ScriptType;
import org.thingsboard.script.api.js.NashornJsInvokeService; import org.thingsboard.script.api.js.NashornJsInvokeService;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST; import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_REQUEST;
@ -41,8 +47,9 @@ import static org.thingsboard.server.common.data.msg.TbMsgType.POST_TELEMETRY_RE
"js.max_script_body_size=50", "js.max_script_body_size=50",
"js.max_total_args_size=50", "js.max_total_args_size=50",
"js.max_result_size=50", "js.max_result_size=50",
"js.local.max_errors=2" "js.local.max_errors=2",
}) })
@Slf4j
class NashornJsInvokeServiceTest extends AbstractControllerTest { class NashornJsInvokeServiceTest extends AbstractControllerTest {
@Autowired @Autowired
@ -56,23 +63,64 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
int iterations = 1000; int iterations = 1000;
UUID scriptId = evalScript("return msg.temperature > 20"); UUID scriptId = evalScript("return msg.temperature > 20");
// warmup // warmup
ObjectNode msg = JacksonUtil.newObjectNode(); log.info("Warming up 1000 times...");
for (int i = 0; i < 100; i++) { var warmupWatch = TbStopWatch.create();
msg.put("temperature", i); for (int i = 0; i < 1000; i++) {
boolean expected = i > 20; boolean expected = i > 20;
boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); boolean result = Boolean.parseBoolean(invokeScript(scriptId, "{\"temperature\":" + i + "}"));
Assert.assertEquals(expected, result); Assert.assertEquals(expected, result);
} }
long startTs = System.currentTimeMillis(); log.info("Warming up finished in {} ms", warmupWatch.stopAndGetTotalTimeMillis());
log.info("Starting performance test...");
var watch = TbStopWatch.create();
for (int i = 0; i < iterations; i++) { for (int i = 0; i < iterations; i++) {
msg.put("temperature", i);
boolean expected = i > 20; boolean expected = i > 20;
boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); boolean result = Boolean.parseBoolean(invokeScript(scriptId, "{\"temperature\":" + i + "}"));
log.debug("asserting result");
Assert.assertEquals(expected, result); Assert.assertEquals(expected, result);
} }
long duration = System.currentTimeMillis() - startTs; long duration = watch.stopAndGetTotalTimeMillis();
System.out.println(iterations + " invocations took: " + duration + "ms"); log.info("Performance test with {} invocations took: {} ms", iterations, duration);
Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(4)); assertThat(duration).as("duration ms")
.isLessThan(TimeUnit.MINUTES.toMillis(1)); // effective exec time is about 500ms
}
@Test
void givenSimpleScriptMultiThreadTestPerformance() throws ExecutionException, InterruptedException, TimeoutException {
int iterations = 1000*4;
List<ListenableFuture<Object>> futures = new ArrayList<>(iterations);
UUID scriptId = evalScript("return msg.temperature > 20 ;");
// warmup
log.info("Warming up 1000 times...");
var warmupWatch = TbStopWatch.create();
for (int i = 0; i < 1000; i++) {
futures.add(invokeScriptAsync(scriptId, "{\"temperature\":" + i + "}"));
}
List<Object> results = Futures.allAsList(futures).get(1, TimeUnit.MINUTES);
for (int i = 0; i < 1000; i++) {
boolean expected = i > 20;
boolean result = Boolean.parseBoolean(results.get(i).toString());
Assert.assertEquals(expected, result);
}
log.info("Warming up finished in {} ms", warmupWatch.stopAndGetTotalTimeMillis());
futures.clear();
log.info("Starting performance test...");
var watch = TbStopWatch.create();
for (int i = 0; i < iterations; i++) {
futures.add(invokeScriptAsync(scriptId, "{\"temperature\":" + i + "}"));
}
results = Futures.allAsList(futures).get(1, TimeUnit.MINUTES);
for (int i = 0; i < iterations; i++) {
boolean expected = i > 20;
boolean result = Boolean.parseBoolean(results.get(i).toString());
Assert.assertEquals(expected, result);
}
long duration = watch.stopAndGetTotalTimeMillis();
log.info("Performance test with {} invocations took: {} ms", iterations, duration);
assertThat(duration).as("duration ms")
.isLessThan(TimeUnit.MINUTES.toMillis(1)); // effective exec time is about 500ms
} }
@Test @Test
@ -122,7 +170,11 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest {
} }
private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException {
return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.name()).get().toString(); return invokeScriptAsync(scriptId, msg).get().toString();
}
private ListenableFuture<Object> invokeScriptAsync(UUID scriptId, String msg) {
return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", POST_TELEMETRY_REQUEST.name());
} }
} }

5
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java

@ -19,7 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.Resource;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.coapserver.DefaultCoapServerService; import org.thingsboard.server.coapserver.DefaultCoapServerService;
@ -60,15 +59,11 @@ public class CoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributes
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processJsonTestSubscribeToAttributesUpdates(false); processJsonTestSubscribeToAttributesUpdates(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
processJsonTestSubscribeToAttributesUpdates(true); processJsonTestSubscribeToAttributesUpdates(true);

4
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.attributes.updates;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -45,12 +44,11 @@ public class CoapAttributesUpdatesJsonIntegrationTest extends AbstractCoapAttrib
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processJsonTestSubscribeToAttributesUpdates(false); processJsonTestSubscribeToAttributesUpdates(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
processJsonTestSubscribeToAttributesUpdates(true); processJsonTestSubscribeToAttributesUpdates(true);

3
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.attributes.updates;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -44,12 +43,10 @@ public class CoapAttributesUpdatesProtoIntegrationTest extends AbstractCoapAttri
public void afterTest() throws Exception { public void afterTest() throws Exception {
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception {
processProtoTestSubscribeToAttributesUpdates(false); processProtoTestSubscribeToAttributesUpdates(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception {
processProtoTestSubscribeToAttributesUpdates(true); processProtoTestSubscribeToAttributesUpdates(true);

4
application/src/test/java/org/thingsboard/server/transport/coap/client/CoapClientIntegrationTest.java

@ -26,7 +26,6 @@ import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.MediaTypeRegistry;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ -69,7 +68,6 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
private static final List<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5"); private static final List<String> EXPECTED_KEYS = Arrays.asList("key1", "key2", "key3", "key4", "key5");
private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}";
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder()
@ -83,7 +81,6 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testConfirmableRequests() throws Exception { public void testConfirmableRequests() throws Exception {
boolean confirmable = true; boolean confirmable = true;
@ -92,7 +89,6 @@ public class CoapClientIntegrationTest extends AbstractCoapIntegrationTest {
processTestRequestAttributesValuesFromTheServer(confirmable); processTestRequestAttributesValuesFromTheServer(confirmable);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testNonConfirmableRequests() throws Exception { public void testNonConfirmableRequests() throws Exception {
boolean confirmable = false; boolean confirmable = false;

3
application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java

@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
@ -83,13 +82,11 @@ public class CoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerS
Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapOneWayRpc() throws Exception { public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest(false); processOneWayRpcTest(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapTwoWayRpc() throws Exception { public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false);

3
application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -44,13 +43,11 @@ public class CoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSide
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapOneWayRpc() throws Exception { public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest(false); processOneWayRpcTest(false);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapTwoWayRpc() throws Exception { public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false);

3
application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java

@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.rpc;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.TransportPayloadType;
@ -45,13 +44,11 @@ public class CoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSid
processAfterTest(); processAfterTest();
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapOneWayRpc() throws Exception { public void testServerCoapOneWayRpc() throws Exception {
processOneWayRpcTest(true); processOneWayRpcTest(true);
} }
@Ignore // Uncomment when Californium 3.11 is released with https://github.com/eclipse-californium/californium/pull/2215
@Test @Test
public void testServerCoapTwoWayRpc() throws Exception { public void testServerCoapTwoWayRpc() throws Exception {
processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", true); processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", true);

2
application/src/test/resources/logback-test.xml

@ -16,7 +16,7 @@
<logger name="org.testcontainers" level="INFO" /> <logger name="org.testcontainers" level="INFO" />
<logger name="org.eclipse.leshan" level="INFO"/> <logger name="org.eclipse.leshan" level="INFO"/>
<logger name="org.thingsboard.server.controller.AbstractWebTest" level="INFO"/> <logger name="org.thingsboard.server.controller.AbstractWebTest" level="INFO"/>
<logger name="org.thingsboard.server.service.script" level="INFO"/>
<!-- mute TelemetryEdgeSqlTest that causes a lot of randomly generated errors --> <!-- mute TelemetryEdgeSqlTest that causes a lot of randomly generated errors -->
<logger name="org.thingsboard.server.service.edge.rpc.EdgeGrpcSession" level="OFF"/> <logger name="org.thingsboard.server.service.edge.rpc.EdgeGrpcSession" level="OFF"/>

4
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -81,7 +81,7 @@ public interface AlarmService extends EntityDaoService {
void delAlarmTypes(TenantId tenantId, Set<String> types); void delAlarmTypes(TenantId tenantId, Set<String> types);
int deleteAlarmsByEntityId(TenantId tenantId, EntityId entityId); int deleteAlarmsByOriginatorId(TenantId tenantId, EntityId entityId);
// Other API // Other API
Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); Alarm findAlarmById(TenantId tenantId, AlarmId alarmId);
@ -108,6 +108,8 @@ public interface AlarmService extends EntityDaoService {
PageData<AlarmId> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink); PageData<AlarmId> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink);
PageData<AlarmId> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, PageLink pageLink);
int deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId); int deleteEntityAlarmRecords(TenantId tenantId, EntityId entityId);
void deleteEntityAlarmRecordsByTenantId(TenantId tenantId); void deleteEntityAlarmRecordsByTenantId(TenantId tenantId);

1
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java

@ -24,6 +24,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
public interface OAuth2Service { public interface OAuth2Service {
List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType); List<OAuth2ClientInfo> getOAuth2Clients(String domainScheme, String domainName, String pkgName, PlatformType platformType);
void saveOAuth2Info(OAuth2Info oauth2Info); void saveOAuth2Info(OAuth2Info oauth2Info);

35
common/dao-api/src/main/java/org/thingsboard/server/dao/queue/QueueStatsService.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.queue;
import org.thingsboard.server.common.data.id.QueueStatsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.queue.QueueStats;
import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface QueueStatsService extends EntityDaoService {
QueueStats save(TenantId tenantId, QueueStats queueStats);
QueueStats findQueueStatsById(TenantId tenantId, QueueStatsId queueStatsId);
QueueStats findByTenantIdAndNameAndServiceId(TenantId tenantId, String queueName, String serviceId);
List<QueueStats> findByTenantId(TenantId tenantId);
}

3
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -58,7 +58,8 @@ public enum EntityType {
NOTIFICATION_TEMPLATE (30), NOTIFICATION_TEMPLATE (30),
NOTIFICATION_REQUEST (31), NOTIFICATION_REQUEST (31),
NOTIFICATION (32), NOTIFICATION (32),
NOTIFICATION_RULE (33); NOTIFICATION_RULE (33),
QUEUE_STATS(34);
@Getter @Getter
private final int protoNumber; // Corresponds to EntityTypeProto private final int protoNumber; // Corresponds to EntityTypeProto

11
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmComment.java

@ -25,6 +25,7 @@ import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.id.AlarmCommentId; import org.thingsboard.server.common.data.id.AlarmCommentId;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.Length;
@ -36,21 +37,21 @@ import org.thingsboard.server.common.data.validation.NoXss;
@AllArgsConstructor @AllArgsConstructor
public class AlarmComment extends BaseData<AlarmCommentId> implements HasName { public class AlarmComment extends BaseData<AlarmCommentId> implements HasName {
@Schema(description = "JSON object with Alarm id.", accessMode = Schema.AccessMode.READ_ONLY) @Schema(description = "JSON object with Alarm id.", accessMode = Schema.AccessMode.READ_ONLY)
private EntityId alarmId; private AlarmId alarmId;
@Schema(description = "JSON object with User id.", accessMode = Schema.AccessMode.READ_ONLY) @Schema(description = "JSON object with User id.", accessMode = Schema.AccessMode.READ_ONLY)
private UserId userId; private UserId userId;
@Schema(description = "Defines origination of comment. System type means comment was created by TB. OTHER type means comment was created by user.", example = "SYSTEM/OTHER", accessMode = Schema.AccessMode.READ_ONLY) @Schema(description = "Defines origination of comment. System type means comment was created by TB. OTHER type means comment was created by user.", example = "SYSTEM/OTHER", accessMode = Schema.AccessMode.READ_ONLY)
private AlarmCommentType type; private AlarmCommentType type;
@Schema(description = "JSON object with text of comment.",implementation = com.fasterxml.jackson.databind.JsonNode.class) @Schema(description = "JSON object with text of comment.")
@NoXss @NoXss
@Length(fieldName = "comment", max = 10000) @Length(fieldName = "comment", max = 10000)
@EqualsAndHashCode.Include @EqualsAndHashCode.Include
private transient JsonNode comment; private JsonNode comment;
@Schema(description = "JSON object with the alarm comment Id. " + @Schema(description = "JSON object with the alarm comment Id. " +
"Specify this field to update the alarm comment. " + "Specify this field to update the alarm comment. " +
"Referencing non-existing alarm Id will cause error. " + "Referencing non-existing alarm Id will cause error. " +
"Omit this field to create new alarm." ) "Omit this field to create new alarm.", accessMode = Schema.AccessMode.READ_ONLY)
@Override @Override
public AlarmCommentId getId() { public AlarmCommentId getId() {
return super.getId(); return super.getId();
@ -72,7 +73,7 @@ public class AlarmComment extends BaseData<AlarmCommentId> implements HasName {
@Override @Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
@Schema(required = true, description = "representing comment text", example = "Please take a look") @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "representing comment text", example = "Please take a look")
public String getName() { public String getName() {
return comment.toString(); return comment.toString();
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java

@ -41,7 +41,8 @@ public enum EdgeEventType {
ADMIN_SETTINGS(true, null), ADMIN_SETTINGS(true, null),
OTA_PACKAGE(true, EntityType.OTA_PACKAGE), OTA_PACKAGE(true, EntityType.OTA_PACKAGE),
QUEUE(true, EntityType.QUEUE), QUEUE(true, EntityType.QUEUE),
TB_RESOURCE(true, EntityType.TB_RESOURCE); TB_RESOURCE(true, EntityType.TB_RESOURCE),
OAUTH2(true, null);
private final boolean allEdgesRelated; private final boolean allEdgesRelated;

4
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java

@ -35,10 +35,10 @@ public interface EntityId extends HasUUID, Serializable { //NOSONAR, the constan
UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080"); UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080");
@Schema(required = true, description = "ID of the entity, time-based UUID v1", example = "784f394c-42b6-435a-983c-b7beff2784f9") @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "ID of the entity, time-based UUID v1", example = "784f394c-42b6-435a-983c-b7beff2784f9")
UUID getId(); UUID getId();
@Schema(required = true, example = "DEVICE") @Schema(requiredMode = Schema.RequiredMode.REQUIRED, example = "DEVICE")
EntityType getEntityType(); EntityType getEntityType();
@JsonIgnore @JsonIgnore

2
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -103,6 +103,8 @@ public class EntityIdFactory {
return new NotificationTemplateId(uuid); return new NotificationTemplateId(uuid);
case NOTIFICATION: case NOTIFICATION:
return new NotificationId(uuid); return new NotificationId(uuid);
case QUEUE_STATS:
return new QueueStatsId(uuid);
} }
throw new IllegalArgumentException("EntityType " + type + " is not supported!"); throw new IllegalArgumentException("EntityType " + type + " is not supported!");
} }

43
common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2024 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
public class QueueStatsId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@JsonCreator
public QueueStatsId(@JsonProperty("id") UUID id) {
super(id);
}
public static QueueStatsId fromString(String queueId) {
return new QueueStatsId(UUID.fromString(queueId));
}
@Schema(required = true, description = "string", example = "QUEUE_STATS", allowableValues = "QUEUE_STATS")
@Override
public EntityType getEntityType() {
return EntityType.QUEUE_STATS;
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java

@ -68,7 +68,7 @@ public abstract class UUIDBased implements HasUUID, Serializable {
@Override @Override
public String toString() { public String toString() {
return id.toString(); return String.valueOf(id);
} }
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@Schema
public class UserId extends UUIDBased implements EntityId { public class UserId extends UUIDBased implements EntityId {
@JsonCreator @JsonCreator
@ -33,7 +34,7 @@ public class UserId extends UUIDBased implements EntityId {
return new UserId(UUID.fromString(userId)); return new UserId(UUID.fromString(userId));
} }
@Schema(required = true, description = "string", example = "USER", allowableValues = "USER") @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "USER", allowableValues = "USER")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.USER; return EntityType.USER;

15
common/data/src/main/java/org/thingsboard/server/common/data/kv/DataType.java

@ -15,8 +15,21 @@
*/ */
package org.thingsboard.server.common.data.kv; package org.thingsboard.server.common.data.kv;
import lombok.Getter;
public enum DataType { public enum DataType {
STRING, LONG, BOOLEAN, DOUBLE, JSON; BOOLEAN(0),
LONG(1),
DOUBLE(2),
STRING(3),
JSON(4);
@Getter
private final int protoNumber; // Corresponds to KeyValueType
DataType(int protoNumber) {
this.protoNumber = protoNumber;
}
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java

@ -35,6 +35,8 @@ import java.util.List;
public class OAuth2Info { public class OAuth2Info {
@Schema(description = "Whether OAuth2 settings are enabled or not") @Schema(description = "Whether OAuth2 settings are enabled or not")
private boolean enabled; private boolean enabled;
@Schema(description = "Whether OAuth2 settings are enabled on Edge or not")
private boolean edgeEnabled;
@Schema(description = "List of configured OAuth2 clients. Cannot contain null values", required = true) @Schema(description = "List of configured OAuth2 clients. Cannot contain null values", required = true)
private List<OAuth2ParamsInfo> oauth2ParamsInfos; private List<OAuth2ParamsInfo> oauth2ParamsInfos;
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Params.java

@ -30,11 +30,13 @@ import org.thingsboard.server.common.data.id.TenantId;
public class OAuth2Params extends BaseData<OAuth2ParamsId> { public class OAuth2Params extends BaseData<OAuth2ParamsId> {
private boolean enabled; private boolean enabled;
private boolean edgeEnabled;
private TenantId tenantId; private TenantId tenantId;
public OAuth2Params(OAuth2Params oauth2Params) { public OAuth2Params(OAuth2Params oauth2Params) {
super(oauth2Params); super(oauth2Params);
this.enabled = oauth2Params.enabled; this.enabled = oauth2Params.enabled;
this.edgeEnabled = oauth2Params.edgeEnabled;
this.tenantId = oauth2Params.tenantId; this.tenantId = oauth2Params.tenantId;
} }
} }

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

Loading…
Cancel
Save