Browse Source

Merge remote-tracking branch 'upstream/master' into feature/edge-json-refactoring

pull/9726/head
Andrii Landiak 3 years ago
parent
commit
67aa74f1dc
  1. 2
      application/src/main/data/json/edge/install_instructions/docker/instructions.md
  2. 8
      application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json
  3. 2
      application/src/main/data/json/system/widget_types/image_map.json
  4. 2
      application/src/main/data/json/system/widget_types/label_widget.json
  5. 2
      application/src/main/data/json/system/widget_types/markers_placement___image_map.json
  6. 8
      application/src/main/data/json/tenant/device_profile/rule_chain_template.json
  7. 8
      application/src/main/data/json/tenant/rule_chains/root_rule_chain.json
  8. 26
      application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql
  9. 37
      application/src/main/data/upgrade/3.6.1/schema_update.sql
  10. 6
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  11. 6
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  12. 14
      application/src/main/java/org/thingsboard/server/controller/AssetProfileController.java
  13. 6
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  14. 5
      application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java
  15. 14
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  16. 14
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  17. 283
      application/src/main/java/org/thingsboard/server/controller/ImageController.java
  18. 128
      application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
  19. 2
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  20. 1
      application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java
  21. 26
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  22. 18
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  23. 10
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  24. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java
  25. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java
  26. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java
  27. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java
  28. 1
      application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java
  29. 3
      application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java
  30. 63
      application/src/main/java/org/thingsboard/server/service/executors/PubSubRuleNodeExecutorProvider.java
  31. 1
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  32. 60
      application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java
  33. 125
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  34. 133
      application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java
  35. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  36. 17
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  37. 1
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  38. 155
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java
  39. 130
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  40. 38
      application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java
  41. 18
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  42. 14
      application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java
  43. 10
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java
  44. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java
  45. 36
      application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/ResourceExportService.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java
  47. 83
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ResourceImportService.java
  48. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetTypeImportService.java
  49. 6
      application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java
  50. 7
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  51. 10
      application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java
  52. 49
      application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java
  53. 11
      application/src/main/resources/thingsboard.yml
  54. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  55. 53
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  56. 277
      application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java
  57. 118
      application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java
  58. 2
      application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java
  59. 7
      application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java
  60. 3
      application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java
  61. 90
      application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
  62. 4
      application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java
  63. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  64. 59
      application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java
  65. 4
      common/cluster-api/pom.xml
  66. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  67. 58
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java
  68. 7
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  69. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  70. 1
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  71. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java
  72. 24
      common/data/src/main/java/org/thingsboard/server/common/data/HasImage.java
  73. 30
      common/data/src/main/java/org/thingsboard/server/common/data/ImageDescriptor.java
  74. 33
      common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java
  75. 17
      common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java
  76. 4
      common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java
  77. 32
      common/data/src/main/java/org/thingsboard/server/common/data/TbImageDeleteResult.java
  78. 69
      common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java
  79. 77
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java
  80. 4
      common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfoFilter.java
  81. 6
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java
  82. 4
      common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java
  83. 1
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  84. 5
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  85. 7
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java
  86. 1
      common/pom.xml
  87. 94
      common/proto/pom.xml
  88. 2
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java
  89. 2
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java
  90. 2
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java
  91. 2
      common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java
  92. 2
      common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java
  93. 0
      common/proto/src/main/proto/jsinvoke.proto
  94. 7
      common/proto/src/main/proto/queue.proto
  95. 0
      common/proto/src/main/proto/transport.proto
  96. 3
      common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterTest.java
  97. 2
      common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java
  98. 2
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java
  99. 6
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubProducerTemplate.java
  100. 25
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java

2
application/src/main/data/json/edge/install_instructions/docker/instructions.md

@ -51,7 +51,7 @@ services:
- ~/.mytb-edge-logs:/var/log/tb-edge
postgres:
restart: always
image: "postgres:12"
image: "postgres:15"
ports:
- "5432"
environment:

8
application/src/main/data/json/edge/rule_chains/edge_root_rule_chain.json

@ -48,12 +48,12 @@
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configurationVersion": 1,
"configurationVersion": 2,
"configuration": {
"scope": "CLIENT_SCOPE",
"notifyDevice": "false",
"sendAttributesUpdatedNotification": "false",
"updateAttributesOnlyOnValueChange": "true"
"notifyDevice": false,
"sendAttributesUpdatedNotification": false,
"updateAttributesOnlyOnValueChange": true
},
"externalId": null
},

2
application/src/main/data/json/system/widget_types/image_map.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/label_widget.json

File diff suppressed because one or more lines are too long

2
application/src/main/data/json/system/widget_types/markers_placement___image_map.json

File diff suppressed because one or more lines are too long

8
application/src/main/data/json/tenant/device_profile/rule_chain_template.json

@ -32,12 +32,12 @@
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configurationVersion": 1,
"configurationVersion": 2,
"configuration": {
"scope": "CLIENT_SCOPE",
"notifyDevice": "false",
"sendAttributesUpdatedNotification": "false",
"updateAttributesOnlyOnValueChange": "true"
"notifyDevice": false,
"sendAttributesUpdatedNotification": false,
"updateAttributesOnlyOnValueChange": true
}
},
{

8
application/src/main/data/json/tenant/rule_chains/root_rule_chain.json

@ -31,12 +31,12 @@
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
"name": "Save Client Attributes",
"debugMode": false,
"configurationVersion": 1,
"configurationVersion": 2,
"configuration": {
"scope": "CLIENT_SCOPE",
"notifyDevice": "false",
"sendAttributesUpdatedNotification": "false",
"updateAttributesOnlyOnValueChange": "true"
"notifyDevice": false,
"sendAttributesUpdatedNotification": false,
"updateAttributesOnlyOnValueChange": true
}
},
{

26
application/src/main/data/upgrade/3.6.1/save_attributes_node_update.sql

@ -0,0 +1,26 @@
--
-- Copyright © 2016-2023 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.
--
UPDATE rule_node SET
configuration = (configuration::jsonb || jsonb_build_object(
'notifyDevice',
CASE WHEN configuration::jsonb ->> 'notifyDevice' = 'false' THEN false ELSE true END,
'sendAttributesUpdatedNotification',
CASE WHEN configuration::jsonb ->> 'sendAttributesUpdatedNotification' = 'true' THEN true ELSE false END,
'updateAttributesOnlyOnValueChange',
CASE WHEN configuration::jsonb ->> 'updateAttributesOnlyOnValueChange' = 'false' THEN false ELSE true END)::jsonb)::varchar,
configuration_version = 2
WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version = 1;

37
application/src/main/data/upgrade/3.6.1/schema_update.sql

@ -0,0 +1,37 @@
--
-- Copyright © 2016-2023 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.
--
-- RESOURCES UPDATE START
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name = 'resource' AND column_name = 'data' AND data_type = 'bytea') THEN
ALTER TABLE resource RENAME COLUMN data TO base64_data;
ALTER TABLE resource ADD COLUMN data bytea;
UPDATE resource SET data = decode(base64_data, 'base64') WHERE base64_data IS NOT NULL;
ALTER TABLE resource DROP COLUMN base64_data;
END IF;
END;
$$;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS descriptor varchar;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS preview bytea;
ALTER TABLE resource ADD COLUMN IF NOT EXISTS external_id uuid;
CREATE INDEX IF NOT EXISTS idx_resource_etag ON resource(tenant_id, etag);
-- RESOURCES UPDATE END

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

@ -29,6 +29,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.service.executors.PubSubRuleNodeExecutorProvider;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.SmsService;
@ -322,6 +323,11 @@ public class ActorSystemContext {
@Getter
private NotificationExecutorService notificationExecutor;
@Lazy
@Autowired
@Getter
private PubSubRuleNodeExecutorProvider pubSubRuleNodeExecutorProvider;
@Autowired
@Getter
private SharedEventLoopGroupService sharedEventLoopGroupService;

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

@ -104,6 +104,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.service.executors.PubSubRuleNodeExecutorProvider;
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine;
@ -538,6 +539,11 @@ class DefaultTbContext implements TbContext {
return mainCtx.getNotificationExecutor();
}
@Override
public PubSubRuleNodeExecutorProvider getPubSubRuleNodeExecutorProvider() {
return mainCtx.getPubSubRuleNodeExecutorProvider();
}
@Override
@Deprecated
public ScriptEngine createJsScriptEngine(String script, String... argNames) {

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

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.asset.profile.TbAssetProfileService;
import org.thingsboard.server.service.security.permission.Operation;
@ -45,6 +46,8 @@ import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFIL
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_INFO_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.ASSET_PROFILE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
@ -64,6 +67,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class AssetProfileController extends BaseController {
private final TbAssetProfileService tbAssetProfileService;
private final ImageService imageService;
@ApiOperation(value = "Get Asset Profile (getAssetProfileById)",
notes = "Fetch the Asset Profile object based on the provided Asset Profile Id. " +
@ -74,10 +78,16 @@ public class AssetProfileController extends BaseController {
@ResponseBody
public AssetProfile getAssetProfileById(
@ApiParam(value = ASSET_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_PROFILE_ID) String strAssetProfileId) throws ThingsboardException {
@PathVariable(ASSET_PROFILE_ID) String strAssetProfileId,
@ApiParam(value = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
checkParameter(ASSET_PROFILE_ID, strAssetProfileId);
AssetProfileId assetProfileId = new AssetProfileId(toUUID(strAssetProfileId));
return checkAssetProfileId(assetProfileId, Operation.READ);
var result = checkAssetProfileId(assetProfileId, Operation.READ);
if (inlineImages) {
imageService.inlineImage(result);
}
return result;
}
@ApiOperation(value = "Get Asset Profile Info (getAssetProfileInfoById)",

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

@ -124,6 +124,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.ConstraintValidator;
@ -145,7 +146,6 @@ import org.thingsboard.server.service.entitiy.user.TbUserSettingsService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.AccessControlService;
import org.thingsboard.server.service.security.permission.Operation;
@ -272,7 +272,7 @@ public abstract class BaseController {
protected PartitionService partitionService;
@Autowired
protected TbResourceService resourceService;
protected ResourceService resourceService;
@Autowired
protected OtaPackageService otaPackageService;
@ -587,7 +587,7 @@ public abstract class BaseController {
checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation);
return;
case TB_RESOURCE:
checkResourceId(new TbResourceId(entityId.getId()), operation);
checkResourceInfoId(new TbResourceId(entityId.getId()), operation);
return;
case OTA_PACKAGE:
checkOtaPackageId(new OtaPackageId(entityId.getId()), operation);

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

@ -32,6 +32,9 @@ public class ControllerConstants {
protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " +
"The result is wrapped with PageData object that allows you to iterate over result set using pagination. " +
"See the 'Model' tab of the Response Class for more details. ";
protected static final String INLINE_IMAGES = "inlineImages";
protected static final String INLINE_IMAGES_DESCRIPTION = "Inline images as a data URL (Base64)";
protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
@ -142,6 +145,8 @@ public class ControllerConstants {
protected static final String RESOURCE_INFO_DESCRIPTION = "Resource Info is a lightweight object that includes main information about the Resource excluding the heavyweight data. ";
protected static final String RESOURCE_DESCRIPTION = "Resource is a heavyweight object that includes main information about the Resource and also data. ";
protected static final String RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION = "Use 'true' to include system images. Disabled by default. Ignored for requests by users with system administrator authority.";
protected static final String RESOURCE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the resource title.";
protected static final String RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, resourceType, tenantId";
protected static final String RESOURCE_TYPE_PROPERTY_ALLOWABLE_VALUES = "LWM2M_MODEL, JKS, PKCS_12, JS_MODULE";

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

@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.dashboard.TbDashboardService;
import org.thingsboard.server.service.security.model.SecurityUser;
@ -74,6 +75,8 @@ import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_ID_PARAM_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_ASYNC_FIRST_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.EDGE_UNASSIGN_RECEIVE_STEP_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -94,6 +97,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class DashboardController extends BaseController {
private final TbDashboardService tbDashboardService;
private final ImageService imageService;
public static final String DASHBOARD_ID = "dashboardId";
private static final String HOME_DASHBOARD_ID = "homeDashboardId";
@ -153,10 +157,16 @@ public class DashboardController extends BaseController {
@ResponseBody
public Dashboard getDashboardById(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
@PathVariable(DASHBOARD_ID) String strDashboardId,
@ApiParam(value = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId);
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
return checkDashboardId(dashboardId, Operation.READ);
var result = checkDashboardId(dashboardId, Operation.READ);
if (inlineImages) {
imageService.inlineImages(result);
}
return result;
}
@ApiOperation(value = "Create Or Update Dashboard (saveDashboard)",

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

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService;
@ -52,6 +53,8 @@ import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFI
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_INFO_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
@ -72,6 +75,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI
public class DeviceProfileController extends BaseController {
private final TbDeviceProfileService tbDeviceProfileService;
private final ImageService imageService;
@Autowired
private TimeseriesService timeseriesService;
@ -85,10 +89,16 @@ public class DeviceProfileController extends BaseController {
@ResponseBody
public DeviceProfile getDeviceProfileById(
@ApiParam(value = DEVICE_PROFILE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId,
@ApiParam(value = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkDeviceProfileId(deviceProfileId, Operation.READ);
var result = checkDeviceProfileId(deviceProfileId, Operation.READ);
if (inlineImages) {
imageService.inlineImage(result);
}
return result;
}
@ApiOperation(value = "Get Device Profile Info (getDeviceProfileInfoById)",

283
application/src/main/java/org/thingsboard/server/controller/ImageController.java

@ -0,0 +1,283 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.Base64Utils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.thingsboard.server.common.data.ImageDescriptor;
import org.thingsboard.server.common.data.ImageExportData;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.RESOURCE_TEXT_SEARCH_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION;
@Slf4j
@RestController
@TbCoreComponent
@RequiredArgsConstructor
public class ImageController extends BaseController {
private final ImageService imageService;
private final TbImageService tbImageService;
@Value("${cache.image.systemImagesBrowserTtlInMinutes:0}")
private int systemImagesBrowserTtlInMinutes;
@Value("${cache.image.tenantImagesBrowserTtlInMinutes:0}")
private int tenantImagesBrowserTtlInMinutes;
private static final String IMAGE_URL = "/api/images/{type}/{key}";
private static final String SYSTEM_IMAGE = "system";
private static final String TENANT_IMAGE = "tenant";
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PostMapping("/api/image")
public TbResourceInfo uploadImage(@RequestPart MultipartFile file,
@RequestPart(required = false) String title) throws Exception {
SecurityUser user = getCurrentUser();
TbResource image = new TbResource();
image.setTenantId(user.getTenantId());
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.CREATE, null, image);
image.setFileName(file.getOriginalFilename());
if (StringUtils.isNotEmpty(title)) {
image.setTitle(title);
} else {
image.setTitle(file.getOriginalFilename());
}
image.setResourceType(ResourceType.IMAGE);
ImageDescriptor descriptor = new ImageDescriptor();
descriptor.setMediaType(file.getContentType());
image.setDescriptorValue(descriptor);
image.setData(file.getBytes());
return tbImageService.save(image, user);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PutMapping(IMAGE_URL)
public TbResourceInfo updateImage(@PathVariable String type,
@PathVariable String key,
@RequestPart MultipartFile file) throws Exception {
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.WRITE);
TbResource image = new TbResource(imageInfo);
image.setData(file.getBytes());
image.setFileName(file.getOriginalFilename());
image.updateDescriptor(ImageDescriptor.class, descriptor -> {
descriptor.setMediaType(file.getContentType());
return descriptor;
});
return tbImageService.save(image, getCurrentUser());
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PutMapping(IMAGE_URL + "/info")
public TbResourceInfo updateImageInfo(@PathVariable String type,
@PathVariable String key,
@RequestBody TbResourceInfo newImageInfo) throws ThingsboardException {
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.WRITE);
imageInfo.setTitle(newImageInfo.getTitle());
return tbImageService.save(imageInfo, getCurrentUser());
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = IMAGE_URL, produces = "image/*")
public ResponseEntity<ByteArrayResource> downloadImage(@PathVariable String type,
@PathVariable String key,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
return downloadIfChanged(type, key, etag, false);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(value = IMAGE_URL + "/export")
public ImageExportData exportImage(@PathVariable String type, @PathVariable String key) throws Exception {
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.READ);
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
byte[] data = imageService.getImageData(imageInfo.getTenantId(), imageInfo.getId());
return new ImageExportData(descriptor.getMediaType(), imageInfo.getFileName(), imageInfo.getTitle(), imageInfo.getResourceKey(), Base64Utils.encodeToString(data));
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@PutMapping("/api/image/import")
public TbResourceInfo importImage(@RequestBody ImageExportData imageData) throws Exception {
SecurityUser user = getCurrentUser();
TbResource image = new TbResource();
image.setTenantId(user.getTenantId());
accessControlService.checkPermission(user, Resource.TB_RESOURCE, Operation.CREATE, null, image);
image.setFileName(imageData.getFileName());
if (StringUtils.isNotEmpty(imageData.getTitle())) {
image.setTitle(imageData.getTitle());
} else {
image.setTitle(imageData.getFileName());
}
image.setResourceKey(imageData.getResourceKey());
image.setResourceType(ResourceType.IMAGE);
ImageDescriptor descriptor = new ImageDescriptor();
descriptor.setMediaType(imageData.getMediaType());
image.setDescriptorValue(descriptor);
image.setData(Base64Utils.decodeFromString(imageData.getData()));
return tbImageService.save(image, user);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@GetMapping(value = IMAGE_URL + "/preview", produces = "image/png")
public ResponseEntity<ByteArrayResource> downloadImagePreview(@PathVariable String type,
@PathVariable String key,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws Exception {
return downloadIfChanged(type, key, etag, true);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping(IMAGE_URL + "/info")
public TbResourceInfo getImageInfo(@PathVariable String type,
@PathVariable String key) throws ThingsboardException {
return checkImageInfo(type, key, Operation.READ);
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@GetMapping("/api/images")
public PageData<TbResourceInfo> getImages(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = RESOURCE_INCLUDE_SYSTEM_IMAGES_DESCRIPTION)
@RequestParam(required = false) boolean includeSystemImages,
@ApiParam(value = RESOURCE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
// PE: generic permission
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TenantId tenantId = getTenantId();
if (getCurrentUser().getAuthority() == Authority.SYS_ADMIN || !includeSystemImages) {
return checkNotNull(imageService.getImagesByTenantId(tenantId, pageLink));
} else {
return checkNotNull(imageService.getAllImagesByTenantId(tenantId, pageLink));
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@DeleteMapping(IMAGE_URL)
public ResponseEntity<TbImageDeleteResult> deleteImage(@PathVariable String type,
@PathVariable String key,
@RequestParam(name = "force", required = false) boolean force) throws ThingsboardException {
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.DELETE);
TbImageDeleteResult result = tbImageService.delete(imageInfo, getCurrentUser(), force);
return (result.isSuccess() ? ResponseEntity.ok() : ResponseEntity.badRequest()).body(result);
}
private ResponseEntity<ByteArrayResource> downloadIfChanged(String type, String key, String etag, boolean preview) throws ThingsboardException, JsonProcessingException {
ImageCacheKey cacheKey = new ImageCacheKey(getTenantId(type), key, preview);
if (StringUtils.isNotEmpty(etag)) {
etag = StringUtils.remove(etag, '\"'); // etag is wrapped in double quotes due to HTTP specification
if (etag.equals(tbImageService.getETag(cacheKey))) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build();
}
}
TenantId tenantId = getTenantId();
TbResourceInfo imageInfo = checkImageInfo(type, key, Operation.READ);
String fileName = imageInfo.getFileName();
ImageDescriptor descriptor = imageInfo.getDescriptor(ImageDescriptor.class);
byte[] data;
if (preview) {
descriptor = descriptor.getPreviewDescriptor();
data = imageService.getImagePreview(tenantId, imageInfo.getId());
} else {
data = imageService.getImageData(tenantId, imageInfo.getId());
}
tbImageService.putETag(cacheKey, descriptor.getEtag());
var result = ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + fileName)
.header("x-filename", fileName)
.header("Content-Type", descriptor.getMediaType())
.contentLength(data.length)
.eTag(descriptor.getEtag());
if (systemImagesBrowserTtlInMinutes > 0 && imageInfo.getTenantId().isSysTenantId()) {
result.cacheControl(CacheControl.maxAge(systemImagesBrowserTtlInMinutes, TimeUnit.MINUTES));
} else if (tenantImagesBrowserTtlInMinutes > 0 && !imageInfo.getTenantId().isSysTenantId()) {
result.cacheControl(CacheControl.maxAge(tenantImagesBrowserTtlInMinutes, TimeUnit.MINUTES));
} else {
result.cacheControl(CacheControl.noCache());
}
return result.body(new ByteArrayResource(data));
}
private TbResourceInfo checkImageInfo(String imageType, String key, Operation operation) throws ThingsboardException {
TenantId tenantId = getTenantId(imageType);
TbResourceInfo imageInfo = imageService.getImageInfoByTenantIdAndKey(tenantId, key);
checkEntity(getCurrentUser(), checkNotNull(imageInfo), operation);
return imageInfo;
}
private TenantId getTenantId(String imageType) throws ThingsboardException {
TenantId tenantId;
if (imageType.equals(TENANT_IMAGE)) {
tenantId = getTenantId();
} else if (imageType.equals(SYSTEM_IMAGE)) {
tenantId = TenantId.SYS_TENANT_ID;
} else {
throw new IllegalArgumentException("Invalid image URL");
}
return tenantId;
}
}

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

@ -19,6 +19,7 @@ import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
@ -26,16 +27,17 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.TbResourceInfoFilter;
@ -50,8 +52,11 @@ import org.thingsboard.server.service.resource.TbResourceService;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.Base64;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.LWM2M_OBJECT_DESCRIPTION;
@ -87,15 +92,14 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Download Resource (downloadResource)", notes = "Download Resource based on the provided Resource Id." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET)
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
@GetMapping(value = "/resource/{resourceId}/download")
public ResponseEntity<ByteArrayResource> downloadResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
TbResource tbResource = checkResourceId(resourceId, Operation.READ);
ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes()));
ByteArrayResource resource = new ByteArrayResource(tbResource.getData());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName())
.header("x-filename", tbResource.getFileName())
@ -106,42 +110,38 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Download LWM2M Resource (downloadLwm2mResourceIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/lwm2m/{resourceId}/download", method = RequestMethod.GET, produces = "application/xml")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadLwm2mResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
@GetMapping(value = "/resource/lwm2m/{resourceId}/download", produces = "application/xml")
public ResponseEntity<ByteArrayResource> downloadLwm2mResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
return downloadResourceIfChanged(ResourceType.LWM2M_MODEL, strResourceId, etag);
}
@ApiOperation(value = "Download PKCS_12 Resource (downloadPkcs12ResourceIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/pkcs12/{resourceId}/download", method = RequestMethod.GET, produces = "application/x-pkcs12")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadPkcs12ResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
public ResponseEntity<ByteArrayResource> downloadPkcs12ResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
return downloadResourceIfChanged(ResourceType.PKCS_12, strResourceId, etag);
}
@ApiOperation(value = "Download JKS Resource (downloadJksResourceIfChanged)",
notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/jks/{resourceId}/download", method = RequestMethod.GET, produces = "application/x-java-keystore")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadJksResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
@GetMapping(value = "/resource/jks/{resourceId}/download", produces = "application/x-java-keystore")
public ResponseEntity<ByteArrayResource> downloadJksResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
return downloadResourceIfChanged(ResourceType.JKS, strResourceId, etag);
}
@ApiOperation(value = "Download JS Resource (downloadJsResourceIfChanged)", notes = DOWNLOAD_RESOURCE_IF_NOT_CHANGED + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/resource/js/{resourceId}/download", method = RequestMethod.GET, produces = "application/javascript")
@ResponseBody
public ResponseEntity<org.springframework.core.io.Resource> downloadJsResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
@GetMapping(value = "/resource/js/{resourceId}/download", produces = "application/javascript")
public ResponseEntity<ByteArrayResource> downloadJsResourceIfChanged(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId,
@RequestHeader(name = HttpHeaders.IF_NONE_MATCH, required = false) String etag) throws ThingsboardException {
return downloadResourceIfChanged(ResourceType.JS_MODULE, strResourceId, etag);
}
@ -150,8 +150,7 @@ public class TbResourceController extends BaseController {
RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/info/{resourceId}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/resource/info/{resourceId}")
public TbResourceInfo getResourceInfoById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
@ -162,10 +161,10 @@ public class TbResourceController extends BaseController {
@ApiOperation(value = "Get Resource (getResourceById)",
notes = "Fetch the Resource object based on the provided Resource Id. " +
RESOURCE_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
produces = "application/json", hidden = true)
@Deprecated // resource's data should be fetched with a download request
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/resource/{resourceId}")
public TbResource getResourceById(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable(RESOURCE_ID) String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
@ -184,13 +183,12 @@ public class TbResourceController extends BaseController {
produces = "application/json",
consumes = "application/json")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.POST)
@ResponseBody
public TbResource saveResource(@ApiParam(value = "A JSON value representing the Resource.")
@RequestBody TbResource resource) throws Exception {
@PostMapping(value = "/resource")
public TbResourceInfo saveResource(@ApiParam(value = "A JSON value representing the Resource.")
@RequestBody TbResource resource) throws Exception {
resource.setTenantId(getTenantId());
checkEntity(resource.getId(), resource, Resource.TB_RESOURCE);
return tbResourceService.save(resource, getCurrentUser());
return new TbResourceInfo(tbResourceService.save(resource, getCurrentUser()));
}
@ApiOperation(value = "Get Resource Infos (getResources)",
@ -198,8 +196,7 @@ public class TbResourceController extends BaseController {
PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/resource")
public PageData<TbResourceInfo> getResources(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -215,9 +212,14 @@ public class TbResourceController extends BaseController {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TbResourceInfoFilter.TbResourceInfoFilterBuilder filter = TbResourceInfoFilter.builder();
filter.tenantId(getTenantId());
Set<ResourceType> resourceTypes = new HashSet<>();
if (StringUtils.isNotEmpty(resourceType)) {
filter.resourceType(ResourceType.valueOf(resourceType));
resourceTypes.add(ResourceType.valueOf(resourceType));
} else {
Collections.addAll(resourceTypes, ResourceType.values());
resourceTypes.remove(ResourceType.IMAGE);
}
filter.resourceTypes(resourceTypes);
if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) {
return checkNotNull(resourceService.findTenantResourcesByTenantId(filter.build(), pageLink));
} else {
@ -225,13 +227,36 @@ public class TbResourceController extends BaseController {
}
}
@ApiOperation(value = "Get All Resource Infos (getAllResources)",
notes = "Returns a page of Resource Info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + RESOURCE_INFO_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@GetMapping(value = "/resource/tenant")
public PageData<TbResourceInfo> getTenantResources(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
@ApiParam(value = RESOURCE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = RESOURCE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TbResourceInfoFilter filter = TbResourceInfoFilter.builder()
.tenantId(getTenantId())
.resourceTypes(EnumSet.allOf(ResourceType.class))
.build();
return checkNotNull(resourceService.findTenantResourcesByTenantId(filter, pageLink));
}
@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. " +
PAGE_DATA_PARAMETERS + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/resource/lwm2m/page", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/resource/lwm2m/page")
public List<LwM2mObject> getLwm2mListObjectsPage(@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@ -243,7 +268,7 @@ public class TbResourceController extends BaseController {
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
PageLink pageLink = new PageLink(pageSize, page, textSearch);
return checkNotNull(resourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink));
return checkNotNull(tbResourceService.findLwM2mObjectPage(getTenantId(), sortProperty, sortOrder, pageLink));
}
@ApiOperation(value = "Get LwM2M Objects (getLwm2mListObjects)",
@ -251,22 +276,20 @@ public class TbResourceController extends BaseController {
"You can specify parameters to filter the results. " + LWM2M_OBJECT_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH,
produces = "application/json")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/resource/lwm2m", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/resource/lwm2m")
public List<LwM2mObject> getLwm2mListObjects(@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES, required = true)
@RequestParam String sortOrder,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = LWM2M_OBJECT_SORT_PROPERTY_ALLOWABLE_VALUES, required = true)
@RequestParam String sortProperty,
@ApiParam(value = "LwM2M Object ids.", required = true)
@RequestParam(required = false) String[] objectIds) throws ThingsboardException {
return checkNotNull(resourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds));
return checkNotNull(tbResourceService.findLwM2mObject(getTenantId(), sortOrder, sortProperty, objectIds));
}
@ApiOperation(value = "Delete Resource (deleteResource)",
notes = "Deletes the Resource. Referencing non-existing Resource Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE)
@ResponseBody
@DeleteMapping(value = "/resource/{resourceId}")
public void deleteResource(@ApiParam(value = RESOURCE_ID_PARAM_DESCRIPTION)
@PathVariable("resourceId") String strResourceId) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
@ -275,12 +298,12 @@ public class TbResourceController extends BaseController {
tbResourceService.delete(tbResource, getCurrentUser());
}
private ResponseEntity<org.springframework.core.io.Resource> downloadResourceIfChanged(ResourceType type, String strResourceId, String etag) throws ThingsboardException {
private ResponseEntity<ByteArrayResource> downloadResourceIfChanged(ResourceType resourceType, String strResourceId, String etag) throws ThingsboardException {
checkParameter(RESOURCE_ID, strResourceId);
TbResourceId resourceId = new TbResourceId(toUUID(strResourceId));
if (etag != null) {
TbResourceInfo tbResourceInfo = checkResourceInfoId(resourceId, Operation.READ);
etag = StringUtils.remove(etag, '\"'); // etag is wrapped in double quotes due to HTTP specification
if (etag.equals(tbResourceInfo.getEtag())) {
return ResponseEntity.status(HttpStatus.NOT_MODIFIED)
.eTag(tbResourceInfo.getEtag())
@ -289,15 +312,16 @@ public class TbResourceController extends BaseController {
}
TbResource tbResource = checkResourceId(resourceId, Operation.READ);
ByteArrayResource resource = new ByteArrayResource(Base64.getDecoder().decode(tbResource.getData().getBytes()));
ByteArrayResource resource = new ByteArrayResource(tbResource.getData());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + tbResource.getFileName())
.header("x-filename", tbResource.getFileName())
.contentLength(resource.contentLength())
.header("Content-Type", type.getMediaType())
.header("Content-Type", resourceType.getMediaType())
.cacheControl(CacheControl.noCache())
.eTag(tbResource.getEtag())
.body(resource);
}
}

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

@ -74,7 +74,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.exception.InvalidParametersException;
import org.thingsboard.server.exception.UncheckedApiException;

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

@ -133,6 +133,7 @@ public class TenantProfileController extends BaseController {
" \"maxRuleChains\": 0,\n" +
" \"maxResourcesInBytes\": 0,\n" +
" \"maxOtaPackagesInBytes\": 0,\n" +
" \"maxResourceSize\": 0,\n" +
" \"transportTenantMsgRateLimit\": \"1000:1,20000:60\",\n" +
" \"transportTenantTelemetryMsgRateLimit\": \"1000:1,20000:60\",\n" +
" \"transportTenantTelemetryDataPointsRateLimit\": \"1000:1,20000:60\",\n" +

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

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.widgets.type.TbWidgetTypeService;
import org.thingsboard.server.service.security.permission.Operation;
@ -53,6 +54,8 @@ import java.util.Collections;
import java.util.List;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -72,6 +75,7 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_TYPE_
public class WidgetTypeController extends AutoCommitController {
private final TbWidgetTypeService tbWidgetTypeService;
private final ImageService imageService;
private static final String WIDGET_TYPE_DESCRIPTION = "Widget Type represents the template for widget creation. Widget Type and Widget are similar to class and object in OOP theory.";
private static final String WIDGET_TYPE_DETAILS_DESCRIPTION = "Widget Type Details extend Widget Type and add image and description properties. " +
@ -92,10 +96,16 @@ public class WidgetTypeController extends AutoCommitController {
@ResponseBody
public WidgetTypeDetails getWidgetTypeById(
@ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException {
@PathVariable("widgetTypeId") String strWidgetTypeId,
@ApiParam(value = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
checkParameter("widgetTypeId", strWidgetTypeId);
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
return checkWidgetTypeId(widgetTypeId, Operation.READ);
var result = checkWidgetTypeId(widgetTypeId, Operation.READ);
if (inlineImages) {
imageService.inlineImages(result);
}
return result;
}
@ApiOperation(value = "Get Widget Type Info (getWidgetTypeInfoById)",
@ -253,9 +263,16 @@ public class WidgetTypeController extends AutoCommitController {
@ResponseBody
public List<WidgetTypeDetails> getBundleWidgetTypesDetails(
@ApiParam(value = "Widget Bundle Id", required = true)
@RequestParam("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
@RequestParam("widgetsBundleId") String strWidgetsBundleId,
@ApiParam(value = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages
) throws ThingsboardException {
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
return checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
var result = checkNotNull(widgetTypeService.findWidgetTypesDetailsByWidgetsBundleId(getTenantId(), widgetsBundleId));
if (inlineImages) {
result.forEach(imageService::inlineImages);
}
return result;
}
@ApiOperation(value = "Get all Widget type fqns for specified Bundle (getBundleWidgetTypeFqns)",
@ -369,6 +386,7 @@ public class WidgetTypeController extends AutoCommitController {
String typeFqn = fqn.substring(scopeQualifier.length() + 1);
WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdAndFqn(tenantId, typeFqn);
checkNotNull(widgetType);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType);
return widgetType;
}

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

@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.widgets.bundle.TbWidgetsBundleService;
import org.thingsboard.server.service.security.permission.Operation;
@ -47,6 +48,8 @@ import java.util.List;
import java.util.Set;
import static org.thingsboard.server.controller.ControllerConstants.AVAILABLE_FOR_ANY_AUTHORIZED_USER;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES;
import static org.thingsboard.server.controller.ControllerConstants.INLINE_IMAGES_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION;
import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION;
@ -66,6 +69,7 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_BUNDL
public class WidgetsBundleController extends BaseController {
private final TbWidgetsBundleService tbWidgetsBundleService;
private final ImageService imageService;
private static final String WIDGET_BUNDLE_DESCRIPTION = "Widget Bundle represents a group(bundle) of widgets. Widgets are grouped into bundle by type or use case. ";
private static final String FULL_SEARCH_PARAM_DESCRIPTION = "Optional boolean parameter indicating extended search of widget bundles by description and by name / description of related widget types";
@ -78,10 +82,16 @@ public class WidgetsBundleController extends BaseController {
@ResponseBody
public WidgetsBundle getWidgetsBundleById(
@ApiParam(value = WIDGET_BUNDLE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetsBundleId") String strWidgetsBundleId) throws ThingsboardException {
@PathVariable("widgetsBundleId") String strWidgetsBundleId,
@ApiParam(value = INLINE_IMAGES_DESCRIPTION)
@RequestParam(value = INLINE_IMAGES, required = false) boolean inlineImages) throws ThingsboardException {
checkParameter("widgetsBundleId", strWidgetsBundleId);
WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
return checkWidgetsBundleId(widgetsBundleId, Operation.READ);
var result = checkWidgetsBundleId(widgetsBundleId, Operation.READ);
if (inlineImages) {
imageService.inlineImage(result);
}
return result;
}
@ApiOperation(value = "Create Or Update Widget Bundle (saveWidgetsBundle)",
@ -196,9 +206,9 @@ public class WidgetsBundleController extends BaseController {
} else {
TenantId tenantId = getCurrentUser().getTenantId();
if (tenantOnly != null && tenantOnly) {
return checkNotNull(widgetsBundleService.findTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, fullSearch != null && fullSearch, pageLink));
return checkNotNull(widgetsBundleService.findTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, fullSearch != null && fullSearch, pageLink));
} else {
return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, fullSearch != null && fullSearch, pageLink));
return checkNotNull(widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, fullSearch != null && fullSearch, pageLink));
}
}
}

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

@ -112,6 +112,8 @@ public class ThingsboardInstallService {
} else if ("3.0.1-cassandra".equals(upgradeFromVersion)) {
log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ...");
latestMigrateService.migrate();
} else if (upgradeFromVersion.equals("3.6.2-images")) {
installScripts.updateImages();
} else {
switch (upgradeFromVersion) {
case "1.2.3": //NOSONAR, Need to execute gradual upgrade starting from upgradeFromVersion
@ -268,6 +270,13 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("3.6.0");
case "3.6.1":
log.info("Upgrading ThingsBoard from version 3.6.1 to 3.6.2 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.1");
installScripts.loadSystemImages();
if (!getEnv("SKIP_IMAGES_MIGRATION", false)) {
installScripts.updateImages();
} else {
log.info("Skipping images migration. Run the upgrade with fromVersion as '3.6.2-images' to migrate");
}
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
break;
default:
@ -321,6 +330,7 @@ public class ThingsboardInstallService {
// systemDataLoaderService.loadSystemPlugins();
// systemDataLoaderService.loadSystemRules();
installScripts.loadSystemLwm2mResources();
installScripts.loadSystemImages();
if (loadDemo) {
log.info("Loading demo data...");

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/resource/ResourceMsgConstructorV1.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.edge.rpc.constructor.resource;
import com.google.protobuf.ByteString;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TenantId;
@ -37,7 +38,7 @@ public class ResourceMsgConstructorV1 extends BaseResourceMsgConstructor {
.setResourceType(tbResource.getResourceType().name())
.setFileName(tbResource.getFileName());
if (tbResource.getData() != null) {
builder.setData(tbResource.getData());
builder.setData(tbResource.getEncodedData());
}
if (tbResource.getEtag() != null) {
builder.setEtag(tbResource.getEtag());

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/telemetry/EntityDataMsgConstructor.java

@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.transport.TransportProtos;

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java

@ -57,7 +57,7 @@ public abstract class ResourceEdgeProcessor extends BaseResourceProcessor implem
return handleUnsupportedMsgType(resourceUpdateMsg.getMsgType());
}
} catch (DataValidationException e) {
if (e.getMessage().contains("files size limit is exhausted")) {
if (e.getMessage().contains("exceeds the maximum")) {
log.warn("[{}] Resource data size has been exhausted {}", tenantId, resourceUpdateMsg, e);
return Futures.immediateFuture(null);
} else {

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/BaseTelemetryProcessor.java

@ -52,8 +52,8 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.transport.util.JsonUtils;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;

1
application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java

@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;

3
application/src/main/java/org/thingsboard/server/service/entitiy/widgets/type/DefaultWidgetTypeService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.entitiy.widgets.type;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils;
@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@ -33,6 +35,7 @@ import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@AllArgsConstructor
public class DefaultWidgetTypeService extends AbstractTbEntityService implements TbWidgetTypeService {
private final WidgetTypeService widgetTypeService;
@Override

63
application/src/main/java/org/thingsboard/server/service/executors/PubSubRuleNodeExecutorProvider.java

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2023 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.executors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ExecutorProvider;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.queue.util.TbRuleEngineComponent;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
@Lazy
@TbRuleEngineComponent
@Component
public class PubSubRuleNodeExecutorProvider implements ExecutorProvider {
@Value("${service.rule_engine.pubsub.executor_thread_pool_size}")
private Integer threadPoolSize;
/**
* Refers to com.google.cloud.pubsub.v1.Publisher default executor configuration
*/
private static final int THREADS_PER_CPU = 5;
private ScheduledExecutorService executor;
@PostConstruct
public void init() {
if (threadPoolSize == null) {
threadPoolSize = THREADS_PER_CPU * Runtime.getRuntime().availableProcessors();
}
executor = Executors.newScheduledThreadPool(threadPoolSize, ThingsBoardThreadFactory.forName("pubsub-rule-nodes"));
}
@Override
public ScheduledExecutorService getExecutor() {
return executor;
}
@PreDestroy
private void destroy() {
if (executor != null) {
executor.shutdownNow();
}
}
}

1
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -530,6 +530,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("humidityAlarmThreshold", (long) 30))));
installScripts.loadDashboards(demoTenant.getId(), null);
installScripts.createDefaultTenantDashboards(demoTenant.getId(), null);
}
@Override

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

@ -16,14 +16,15 @@
package org.thingsboard.server.service.install;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
@ -44,6 +45,7 @@ import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.service.install.update.ImagesUpdater;
import java.io.IOException;
import java.nio.file.DirectoryStream;
@ -51,12 +53,12 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource;
@ -109,6 +111,9 @@ public class InstallScripts {
@Autowired
private ResourceService resourceService;
@Autowired
private ImagesUpdater imagesUpdater;
Path getTenantRuleChainsDir() {
return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
}
@ -153,16 +158,14 @@ public class InstallScripts {
}
private void loadRuleChainsFromPath(TenantId tenantId, Path ruleChainsPath) throws IOException {
findRuleChainsFromPath(ruleChainsPath)
.forEach(
path -> {
try {
createRuleChainFromFile(tenantId, path, null);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
}
});
findRuleChainsFromPath(ruleChainsPath).forEach(path -> {
try {
createRuleChainFromFile(tenantId, path, null);
} catch (Exception e) {
log.error("Unable to load rule chain from json: [{}]", path.toString());
throw new RuntimeException("Unable to load rule chain from json", e);
}
});
}
List<Path> findRuleChainsFromPath(Path ruleChainsPath) throws IOException {
@ -195,6 +198,7 @@ public class InstallScripts {
}
public void loadSystemWidgets() throws Exception {
log.info("Loading system widgets");
Map<Path, JsonNode> widgetsBundlesMap = new HashMap<>();
Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
@ -297,6 +301,30 @@ public class InstallScripts {
}
}
public void updateImages() {
imagesUpdater.updateWidgetsBundlesImages();
imagesUpdater.updateWidgetTypesImages();
imagesUpdater.updateDashboardsImages();
imagesUpdater.updateDeviceProfilesImages();
imagesUpdater.updateAssetProfilesImages();
}
@SneakyThrows
public void loadSystemImages() {
log.info("Loading system images...");
Stream<Path> dashboardsFiles = Files.list(Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR));
try (dashboardsFiles) {
dashboardsFiles.forEach(file -> {
try {
Dashboard dashboard = JacksonUtil.OBJECT_MAPPER.readValue(file.toFile(), Dashboard.class);
imagesUpdater.createSystemImages(dashboard);
} catch (Exception e) {
log.error("Failed to create system images for default dashboard {}", file.getFileName(), e);
}
});
}
}
public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
loadDashboardsFromDir(tenantId, customerId, dashboardsDir);
@ -368,7 +396,7 @@ public class InstallScripts {
dirStream.forEach(
path -> {
try {
String data = Base64.getEncoder().encodeToString(Files.readAllBytes(path));
byte[] data = Files.readAllBytes(path);
TbResource tbResource = new TbResource();
tbResource.setTenantId(TenantId.SYS_TENANT_ID);
tbResource.setData(data);
@ -389,15 +417,13 @@ public class InstallScripts {
private void doSaveLwm2mResource(TbResource resource) throws ThingsboardException {
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
if (resource.getData() == null || resource.getData().length == 0) {
throw new DataValidationException("Resource data should be specified!");
}
toLwm2mResource(resource);
TbResource foundResource =
resourceService.getResource(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey());
TbResource foundResource = resourceService.findResourceByTenantIdAndKey(TenantId.SYS_TENANT_ID, ResourceType.LWM2M_MODEL, resource.getResourceKey());
if (foundResource == null) {
resourceService.saveResource(resource);
}
}
}

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

@ -63,6 +63,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO;
import static org.thingsboard.server.service.install.DatabaseHelper.ASSIGNED_CUSTOMERS;
@ -717,86 +718,80 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
}
break;
case "3.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, 3005000)) {
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3005001;");
}
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
updateSchema("3.5.0", 3005000, "3.5.1", 3005001, null);
break;
case "3.5.1":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
if (isOldSchema(conn, 3005001)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.5.1", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
String[] entityNames = new String[]{"device", "component_descriptor", "customer", "dashboard", "rule_chain", "rule_node", "ota_package",
"asset_profile", "asset", "device_profile", "tb_user", "tenant_profile", "tenant", "widgets_bundle", "entity_view", "edge"};
for (String entityName : entityNames) {
try {
conn.createStatement().execute("ALTER TABLE " + entityName + " DROP COLUMN " + SEARCH_TEXT + " CASCADE");
} catch (Exception e) {
}
}
try {
conn.createStatement().execute("ALTER TABLE component_descriptor ADD COLUMN IF NOT EXISTS configuration_version int DEFAULT 0;");
} catch (Exception e) {
}
try {
conn.createStatement().execute("ALTER TABLE rule_node ADD COLUMN IF NOT EXISTS configuration_version int DEFAULT 0;");
} catch (Exception e) {
}
try {
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_rule_node_type_configuration_version ON rule_node(type, configuration_version);");
} catch (Exception e) {
}
updateSchema("3.5.1", 3005001, "3.6.0", 3006000, conn -> {
String[] entityNames = new String[]{"device", "component_descriptor", "customer", "dashboard", "rule_chain", "rule_node", "ota_package",
"asset_profile", "asset", "device_profile", "tb_user", "tenant_profile", "tenant", "widgets_bundle", "entity_view", "edge"};
for (String entityName : entityNames) {
try {
conn.createStatement().execute("UPDATE rule_node SET " +
"configuration = (configuration::jsonb || '{\"updateAttributesOnlyOnValueChange\": \"false\"}'::jsonb)::varchar, " +
"configuration_version = 1 " +
"WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version < 1;");
conn.createStatement().execute("ALTER TABLE " + entityName + " DROP COLUMN " + SEARCH_TEXT + " CASCADE");
} catch (Exception e) {
}
try {
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';");
} catch (Exception e) {
}
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3006000;");
log.info("Schema updated to version 3.6.0.");
} else {
log.info("Skip schema re-update to version 3.6.0. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update.");
}
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
try {
conn.createStatement().execute("ALTER TABLE component_descriptor ADD COLUMN IF NOT EXISTS configuration_version int DEFAULT 0;");
} catch (Exception e) {
}
try {
conn.createStatement().execute("ALTER TABLE rule_node ADD COLUMN IF NOT EXISTS configuration_version int DEFAULT 0;");
} catch (Exception e) {
}
try {
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_rule_node_type_configuration_version ON rule_node(type, configuration_version);");
} catch (Exception e) {
}
try {
conn.createStatement().execute("UPDATE rule_node SET " +
"configuration = (configuration::jsonb || '{\"updateAttributesOnlyOnValueChange\": \"false\"}'::jsonb)::varchar, " +
"configuration_version = 1 " +
"WHERE type = 'org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode' AND configuration_version < 1;");
} catch (Exception e) {
}
try {
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_notification_recipient_id_unread ON notification(recipient_id) WHERE status <> 'READ';");
} catch (Exception e) {
}
});
break;
case "3.6.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
if (isOldSchema(conn, 3006000)) {
log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.0", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3006001;");
log.info("Schema updated to version 3.6.1.");
} else {
log.info("Skip schema re-update to version 3.6.1. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update.");
updateSchema("3.6.0", 3006000, "3.6.1", 3006001, null);
break;
case "3.6.1":
updateSchema("3.6.1", 3006001, "3.6.2", 3006002, connection -> {
try {
Path saveAttributesNodeUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.6.1", "save_attributes_node_update.sql");
loadSql(saveAttributesNodeUpdateFile, connection);
} catch (Exception e) {
log.warn("Failed to execute update script for save attributes rule nodes due to: ", e);
}
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
});
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
}
private void updateSchema(String oldVersionStr, int oldVersion, String newVersionStr, int newVersion, Consumer<Connection> additionalAction) {
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
if (isOldSchema(conn, oldVersion)) {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", oldVersionStr, SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
if (additionalAction != null) {
additionalAction.accept(conn);
}
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = " + newVersion + ";");
log.info("Schema updated to version {}", newVersionStr);
} else {
log.info("Skip schema re-update to version {}. Use env flag 'SKIP_SCHEMA_VERSION_CHECK' to force the re-update.", newVersionStr);
}
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
}
private void runSchemaUpdateScript(Connection connection, String version) throws Exception {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", version, SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, connection);

133
application/src/main/java/org/thingsboard/server/service/install/update/ImagesUpdater.java

@ -0,0 +1,133 @@
/**
* Copyright © 2016-2023 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.install.update;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.asset.AssetProfileDao;
import org.thingsboard.server.dao.dashboard.DashboardDao;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.dao.widget.WidgetTypeDao;
import org.thingsboard.server.dao.widget.WidgetsBundleDao;
import java.util.function.BiFunction;
import java.util.function.Function;
@Component
@RequiredArgsConstructor
@Slf4j
public class ImagesUpdater {
private final ImageService imageService;
private final WidgetsBundleDao widgetsBundleDao;
private final WidgetTypeDao widgetTypeDao;
private final DashboardDao dashboardDao;
private final DeviceProfileDao deviceProfileDao;
private final AssetProfileDao assetProfileDao;
public void updateWidgetsBundlesImages() {
log.info("Updating widgets bundles images...");
var widgetsBundles = new PageDataIterable<>(widgetsBundleDao::findAllWidgetsBundles, 128);
updateImages(widgetsBundles, "bundle", imageService::replaceBase64WithImageUrl, widgetsBundleDao);
}
public void updateWidgetTypesImages() {
log.info("Updating widget types images...");
var widgetTypesIds = new PageDataIterable<>(widgetTypeDao::findAllWidgetTypesIds, 1024);
updateImages(widgetTypesIds, "widget type", imageService::replaceBase64WithImageUrl, widgetTypeDao);
}
public void updateDashboardsImages() {
log.info("Updating dashboards images...");
var dashboardsIds = new PageDataIterable<>(dashboardDao::findAllIds, 1024);
updateImages(dashboardsIds, "dashboard", imageService::replaceBase64WithImageUrl, dashboardDao);
}
public void createSystemImages(Dashboard defaultDashboard) {
defaultDashboard.setTenantId(TenantId.SYS_TENANT_ID);
boolean created = imageService.replaceBase64WithImageUrl(defaultDashboard);
if (created) {
log.debug("Created system images for default dashboard '{}'", defaultDashboard.getTitle());
}
}
public void updateDeviceProfilesImages() {
log.info("Updating device profiles images...");
var deviceProfiles = new PageDataIterable<>(deviceProfileDao::findAllWithImages, 256);
updateImages(deviceProfiles, "device profile", imageService::replaceBase64WithImageUrl, deviceProfileDao);
}
public void updateAssetProfilesImages() {
log.info("Updating asset profiles images...");
var assetProfiles = new PageDataIterable<>(assetProfileDao::findAllWithImages, 256);
updateImages(assetProfiles, "asset profile", imageService::replaceBase64WithImageUrl, assetProfileDao);
}
private <E extends HasImage> void updateImages(Iterable<E> entities, String type,
BiFunction<E, String, Boolean> updater, Dao<E> dao) {
int updatedCount = 0;
int totalCount = 0;
for (E entity : entities) {
totalCount++;
try {
boolean updated = updater.apply(entity, type);
if (updated) {
dao.save(entity.getTenantId(), entity);
log.debug("[{}][{}] Updated {} images", entity.getTenantId(), entity.getName(), type);
updatedCount++;
}
} catch (Exception e) {
log.error("[{}][{}] Failed to update {} images", entity.getTenantId(), entity.getName(), type, e);
}
if (totalCount % 100 == 0) {
log.info("Processed {} {}s so far", totalCount, type);
}
}
log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
}
private <E extends HasImage> void updateImages(Iterable<? extends EntityId> entitiesIds, String type,
Function<E, Boolean> updater, Dao<E> dao) {
int updatedCount = 0;
int totalCount = 0;
for (EntityId id : entitiesIds) {
totalCount++;
E entity = dao.findById(TenantId.SYS_TENANT_ID, id.getId());
try {
boolean updated = updater.apply(entity);
if (updated) {
dao.save(entity.getTenantId(), entity);
log.debug("[{}][{}] Updated {} images", entity.getTenantId(), entity.getName(), type);
updatedCount++;
}
} catch (Exception e) {
log.error("[{}][{}] Failed to update {} images", entity.getTenantId(), entity.getName(), type, e);
}
if (totalCount % 100 == 0) {
log.info("Processed {} {}s so far", totalCount, type);
}
}
log.info("Updated {} {}s out of {}", updatedCount, type, totalCount);
}
}

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

@ -81,7 +81,7 @@ import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import static org.thingsboard.server.service.queue.ProtoUtils.toProto;
import static org.thingsboard.server.common.util.ProtoUtils.toProto;
@Service
@Slf4j

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

@ -45,6 +45,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
@ -82,6 +83,8 @@ import org.thingsboard.server.service.profile.TbAssetProfileCache;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.service.resource.TbImageService;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService;
@ -140,6 +143,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final TbCoreConsumerStats stats;
protected final TbQueueConsumer<TbProtoQueueMsg<ToUsageStatsServiceMsg>> usageStatsConsumer;
private final TbQueueConsumer<TbProtoQueueMsg<ToOtaPackageStateServiceMsg>> firmwareStatesConsumer;
private final TbImageService imageService;
protected volatile ExecutorService consumersExecutor;
protected volatile ExecutorService usageStatsExecutor;
@ -165,7 +169,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
ApplicationEventPublisher eventPublisher,
Optional<JwtSettingsService> jwtSettingsService,
NotificationSchedulerService notificationSchedulerService,
NotificationRuleProcessor notificationRuleProcessor) {
NotificationRuleProcessor notificationRuleProcessor,
TbImageService imageService) {
super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, eventPublisher, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService);
this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer();
this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer();
@ -181,6 +186,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
this.vcQueueService = vcQueueService;
this.notificationSchedulerService = notificationSchedulerService;
this.notificationRuleProcessor = notificationRuleProcessor;
this.imageService = imageService;
}
@PostConstruct
@ -407,6 +413,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
.getNotificationRuleProcessorMsg().getTrigger().toByteArray());
notificationRuleTrigger.ifPresent(notificationRuleProcessor::process);
callback.onSuccess();
} else if (toCoreNotification.hasResourceCacheInvalidateMsg()) {
forwardToResourceService(toCoreNotification.getResourceCacheInvalidateMsg(), callback);
}
if (statsEnabled) {
stats.log(toCoreNotification);
@ -550,6 +558,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
callback.onSuccess();
}
private void forwardToResourceService(TransportProtos.ResourceCacheInvalidateMsg msg, TbCallback callback) {
var tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
imageService.evictETag(new ImageCacheKey(tenantId, msg.getResourceKey(), false));
imageService.evictETag(new ImageCacheKey(tenantId, msg.getResourceKey(), true));
callback.onSuccess();
}
private void forwardToSubMgrService(SubscriptionMgrMsgProto msg, TbCallback callback) {
if (msg.hasSubEvent()) {
TbEntitySubEventProto subEvent = msg.getSubEvent();

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

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;

155
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbImageService.java

@ -0,0 +1,155 @@
/**
* Copyright © 2016-2023 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.resource;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ImageDescriptor;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.dao.resource.ImageService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@Service
@Slf4j
@TbCoreComponent
public class DefaultTbImageService extends AbstractTbEntityService implements TbImageService {
private final TbClusterService clusterService;
private final ImageService imageService;
private final Cache<ImageCacheKey, String> etagCache;
public DefaultTbImageService(TbClusterService clusterService, ImageService imageService,
@Value("${cache.image.etag.timeToLiveInMinutes:44640}") int cacheTtl,
@Value("${cache.image.etag.maxSize:10000}") int cacheMaxSize) {
this.clusterService = clusterService;
this.imageService = imageService;
this.etagCache = Caffeine.newBuilder()
.expireAfterAccess(cacheTtl, TimeUnit.MINUTES)
.maximumSize(cacheMaxSize)
.build();
}
@Override
public String getETag(ImageCacheKey imageCacheKey) {
return etagCache.getIfPresent(imageCacheKey);
}
@Override
public void putETag(ImageCacheKey imageCacheKey, String etag) {
etagCache.put(imageCacheKey, etag);
}
@Override
public void evictETag(ImageCacheKey imageCacheKey) {
etagCache.invalidate(imageCacheKey);
}
@Override
public TbResourceInfo save(TbResource image, User user) throws Exception {
ActionType actionType = image.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = image.getTenantId();
try {
var oldEtag = getEtag(image);
if (image.getId() == null && StringUtils.isNotEmpty(image.getResourceKey())) {
var existingImage = imageService.getImageInfoByTenantIdAndKey(tenantId, image.getResourceKey());
if (existingImage != null) {
image.setId(existingImage.getId());
}
}
TbResourceInfo savedImage = imageService.saveImage(image);
notificationEntityService.logEntityAction(tenantId, savedImage.getId(), savedImage, actionType, user);
if (oldEtag.isPresent()) {
var newEtag = getEtag(savedImage);
if (newEtag.isPresent() && !oldEtag.get().equals(newEtag.get())) {
evictETag(new ImageCacheKey(image.getTenantId(), image.getResourceKey(), false));
evictETag(new ImageCacheKey(image.getTenantId(), image.getResourceKey(), true));
clusterService.broadcastToCore(TransportProtos.ToCoreNotificationMsg.newBuilder()
.setResourceCacheInvalidateMsg(TransportProtos.ResourceCacheInvalidateMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceKey(image.getResourceKey())
.build())
.build());
}
}
return savedImage;
} catch (Exception e) {
image.setData(null);
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), image, actionType, user, e);
throw e;
}
}
private Optional<String> getEtag(TbResourceInfo image) throws JsonProcessingException {
var descriptor = image.getDescriptor(ImageDescriptor.class);
return Optional.ofNullable(descriptor != null ? descriptor.getEtag() : null);
}
private Optional<String> getPreviewEtag(TbResourceInfo image) throws JsonProcessingException {
var descriptor = image.getDescriptor(ImageDescriptor.class);
descriptor = descriptor != null ? descriptor.getPreviewDescriptor() : null;
return Optional.ofNullable(descriptor != null ? descriptor.getEtag() : null);
}
@Override
public TbResourceInfo save(TbResourceInfo imageInfo, User user) {
TenantId tenantId = imageInfo.getTenantId();
TbResourceId imageId = imageInfo.getId();
try {
imageInfo = imageService.saveImageInfo(imageInfo);
notificationEntityService.logEntityAction(tenantId, imageId, imageInfo, ActionType.UPDATED, user);
return imageInfo;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, imageId, imageInfo, ActionType.UPDATED, user, e);
throw e;
}
}
@Override
public TbImageDeleteResult delete(TbResourceInfo imageInfo, User user, boolean force) {
TenantId tenantId = imageInfo.getTenantId();
TbResourceId imageId = imageInfo.getId();
try {
TbImageDeleteResult result = imageService.deleteImage(imageInfo, force);
if (result.isSuccess()) {
notificationEntityService.logEntityAction(tenantId, imageId, imageInfo, ActionType.DELETED, user, imageId.toString());
}
return result;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, imageId, ActionType.DELETED, user, e, imageId.toString());
throw e;
}
}
}

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

@ -15,34 +15,24 @@
*/
package org.thingsboard.server.service.resource;
import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.TbResourceInfoFilter;
import org.thingsboard.server.common.data.User;
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.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.BaseWidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
import java.util.Base64;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
@ -56,39 +46,51 @@ import static org.thingsboard.server.utils.LwM2mObjectModelUtils.toLwm2mResource
@Slf4j
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class DefaultTbResourceService extends AbstractTbEntityService implements TbResourceService {
private final ResourceService resourceService;
private final WidgetTypeService widgetTypeService;
public DefaultTbResourceService(ResourceService resourceService, WidgetTypeService widgetTypeService) {
this.resourceService = resourceService;
this.widgetTypeService = widgetTypeService;
}
@Override
public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) {
return resourceService.getResource(tenantId, resourceType, resourceId);
}
@Override
public TbResource findResourceById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceById(tenantId, resourceId);
}
@Override
public TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId) {
return resourceService.findResourceInfoById(tenantId, resourceId);
}
@Override
public PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) {
return resourceService.findAllTenantResourcesByTenantId(filter, pageLink);
public TbResource save(TbResource resource, User user) throws ThingsboardException {
if (resource.getResourceType() == ResourceType.IMAGE) {
throw new IllegalArgumentException("Image resource type is not supported");
}
ActionType actionType = resource.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = resource.getTenantId();
try {
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
toLwm2mResource(resource);
} else if (resource.getResourceKey() == null) {
resource.setResourceKey(resource.getFileName());
}
TbResource savedResource = resourceService.saveResource(resource);
tbClusterService.onResourceChange(savedResource, null);
notificationEntityService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user);
return savedResource;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
resource, actionType, user, e);
throw e;
}
}
@Override
public PageData<TbResourceInfo> findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink) {
return resourceService.findTenantResourcesByTenantId(filter, pageLink);
public void delete(TbResource tbResource, User user) {
if (tbResource.getResourceType() == ResourceType.IMAGE) {
throw new IllegalArgumentException("Image resource type is not supported");
}
TbResourceId resourceId = tbResource.getId();
TenantId tenantId = tbResource.getTenantId();
try {
resourceService.deleteResource(tenantId, resourceId);
tbClusterService.onResourceDeleted(tbResource, null);
notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString());
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
ActionType.DELETED, user, e, resourceId.toString());
throw e;
}
}
@Override
@ -114,16 +116,6 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
.collect(Collectors.toList());
}
@Override
public void deleteResourcesByTenantId(TenantId tenantId) {
resourceService.deleteResourcesByTenantId(tenantId);
}
@Override
public long sumDataSizeByTenantId(TenantId tenantId) {
return resourceService.sumDataSizeByTenantId(tenantId);
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {
@ -134,50 +126,4 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements
return "DESC".equals(sortOrder) ? comparator.reversed() : comparator;
}
@Override
public TbResource save(TbResource tbResource, User user) throws ThingsboardException {
ActionType actionType = tbResource.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = tbResource.getTenantId();
try {
TbResource savedResource = checkNotNull(doSave(tbResource));
tbClusterService.onResourceChange(savedResource, null);
notificationEntityService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user);
return savedResource;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
tbResource, actionType, user, e);
throw e;
}
}
@Override
public void delete(TbResource tbResource, User user) {
TbResourceId resourceId = tbResource.getId();
TenantId tenantId = tbResource.getTenantId();
try {
resourceService.deleteResource(tenantId, resourceId);
tbClusterService.onResourceDeleted(tbResource, null);
notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString());
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE),
ActionType.DELETED, user, e, resourceId.toString());
throw e;
}
}
private TbResource doSave(TbResource resource) throws ThingsboardException {
log.trace("Executing saveResource [{}]", resource);
if (StringUtils.isEmpty(resource.getData())) {
throw new DataValidationException("Resource data should be specified!");
}
if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) {
toLwm2mResource(resource);
} else {
resource.setResourceKey(resource.getFileName());
}
HashCode hashCode = Hashing.sha256().hashBytes(Base64.getDecoder().decode(resource.getData().getBytes()));
resource.setEtag(hashCode.toString());
return resourceService.saveResource(resource);
}
}

38
application/src/main/java/org/thingsboard/server/service/resource/TbImageService.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2023 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.resource;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.dao.resource.ImageCacheKey;
public interface TbImageService {
TbResourceInfo save(TbResource image, User user) throws Exception;
TbResourceInfo save(TbResourceInfo imageInfo, User user);
TbImageDeleteResult delete(TbResourceInfo imageInfo, User user, boolean force);
String getETag(ImageCacheKey imageCacheKey);
void putETag(ImageCacheKey imageCacheKey, String etag);
void evictETag(ImageCacheKey imageCacheKey);
}

18
application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -15,14 +15,9 @@
*/
package org.thingsboard.server.service.resource;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.TbResourceInfoFilter;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
@ -30,16 +25,6 @@ import java.util.List;
public interface TbResourceService extends SimpleTbEntityService<TbResource> {
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
PageData<TbResourceInfo> findAllTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
PageData<TbResourceInfo> findTenantResourcesByTenantId(TbResourceInfoFilter filter, PageLink pageLink);
List<LwM2mObject> findLwM2mObject(TenantId tenantId,
String sortOrder,
String sortProperty,
@ -50,7 +35,4 @@ public interface TbResourceService extends SimpleTbEntityService<TbResource> {
String sortOrder,
PageLink pageLink);
void deleteResourcesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

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

@ -19,7 +19,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode;
@ -405,21 +404,16 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
if (fromVersion < toVersion) {
log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, fromVersion, toVersion);
try {
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, ruleNodeClass);
log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion);
} catch (TbNodeException e) {
log.warn("Failed to upgrade rule node with id: {} type: {} rule chain id: {} fromVersion: {} toVersion: {} due to: ",
ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion, e);
}
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, ruleNodeClass);
log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}",
ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion);
} else {
log.debug("Rule node with id: {} type: {} ruleChainId: {} already set to latest version!",
ruleNodeId, ruleChainId, ruleNodeType);
}
}
} catch (Exception e) {
log.error("Failed to update the rule node with id: {} type: {}, rule chain id: {}",
log.error("Failed to upgrade rule node with id: {} type: {}, rule chain id: {}",
ruleNodeId, ruleNodeType, ruleChainId, e);
}
return node;

10
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java

@ -118,6 +118,16 @@ public class DefaultExportableEntitiesService implements ExportableEntitiesServi
}
}
@Override
public <I extends EntityId> PageData<I> findEntitiesIdsByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) {
ExportableEntityDao<I, ?> dao = getExportableEntityDao(entityType);
if (dao != null) {
return dao.findIdsByTenantId(tenantId.getId(), pageLink);
} else {
return new PageData<>();
}
}
@Override
public <I extends EntityId> I getExternalIdByInternal(I internalId) {
ExportableEntityDao<I, ?> dao = getExportableEntityDao(internalId.getEntityType());

2
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java

@ -35,6 +35,8 @@ public interface ExportableEntitiesService {
<E extends ExportableEntity<I>, I extends EntityId> PageData<E> findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink);
<I extends EntityId> PageData<I> findEntitiesIdsByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink);
<I extends EntityId> I getExternalIdByInternal(I internalId);
<I extends EntityId> void removeById(TenantId tenantId, I id);

36
application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/ResourceExportService.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2023 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.sync.ie.exporting.impl;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.queue.util.TbCoreComponent;
import java.util.Set;
@Service
@TbCoreComponent
public class ResourceExportService extends BaseEntityExportService<TbResourceId, TbResource, EntityExportData<TbResource>> {
@Override
public Set<EntityType> getSupportedEntityTypes() {
return Set.of(EntityType.TB_RESOURCE);
}
}

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

@ -47,7 +47,7 @@ import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumn
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.controller.BaseController;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.service.action.EntityActionService;

83
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ResourceImportService.java

@ -0,0 +1,83 @@
/**
* Copyright © 2016-2023 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.sync.ie.importing.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.sync.ie.EntityExportData;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;
@Service
@TbCoreComponent
@RequiredArgsConstructor
public class ResourceImportService extends BaseEntityImportService<TbResourceId, TbResource, EntityExportData<TbResource>> {
private final ResourceService resourceService;
@Override
protected void setOwner(TenantId tenantId, TbResource resource, IdProvider idProvider) {
resource.setTenantId(tenantId);
}
@Override
protected TbResource prepare(EntitiesImportCtx ctx, TbResource resource, TbResource oldResource, EntityExportData<TbResource> exportData, IdProvider idProvider) {
return resource;
}
@Override
protected TbResource findExistingEntity(EntitiesImportCtx ctx, TbResource resource, IdProvider idProvider) {
TbResource existingResource = super.findExistingEntity(ctx, resource, idProvider);
if (existingResource == null && ctx.isFindExistingByName()) {
existingResource = resourceService.findResourceByTenantIdAndKey(ctx.getTenantId(), resource.getResourceType(), resource.getResourceKey());
}
return existingResource;
}
@Override
protected boolean compare(EntitiesImportCtx ctx, EntityExportData<TbResource> exportData, TbResource prepared, TbResource existing) {
return true;
}
@Override
protected TbResource deepCopy(TbResource resource) {
return new TbResource(resource);
}
@Override
protected TbResource saveOrUpdate(EntitiesImportCtx ctx, TbResource resource, EntityExportData<TbResource> exportData, IdProvider idProvider) {
return resourceService.saveResource(resource);
}
@Override
protected void onEntitySaved(User user, TbResource savedResource, TbResource oldResource) throws ThingsboardException {
super.onEntitySaved(user, savedResource, oldResource);
clusterService.onResourceChange(savedResource, null);
}
@Override
public EntityType getEntityType() {
return EntityType.TB_RESOURCE;
}
}

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

@ -20,13 +20,9 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.sync.ie.WidgetTypeExportData;
import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx;

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

@ -208,10 +208,10 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont
}
if (config.isAllEntities()) {
DaoUtil.processInBatches(pageLink -> exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink)
, 100, entity -> {
DaoUtil.processInBatches(pageLink -> exportableEntitiesService.findEntitiesIdsByTenantId(ctx.getTenantId(), entityType, pageLink),
100, entityId -> {
try {
ctx.add(saveEntityData(ctx, entity.getId()));
ctx.add(saveEntityData(ctx, entityId));
} catch (Exception e) {
throw new RuntimeException(e);
}

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

@ -82,6 +82,7 @@ import org.thingsboard.server.dao.exception.EntitiesLimitException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
@ -145,7 +146,7 @@ public class DefaultTransportApiService implements TransportApiService {
private final TbClusterService tbClusterService;
private final DataDecodingEncodingService dataDecodingEncodingService;
private final DeviceProvisionService deviceProvisionService;
private final TbResourceService resourceService;
private final ResourceService resourceService;
private final OtaPackageService otaPackageService;
private final OtaPackageDataCache otaPackageDataCache;
private final QueueService queueService;
@ -520,10 +521,10 @@ public class DefaultTransportApiService implements TransportApiService {
ResourceType resourceType = ResourceType.valueOf(requestMsg.getResourceType());
String resourceKey = requestMsg.getResourceKey();
TransportProtos.GetResourceResponseMsg.Builder builder = TransportProtos.GetResourceResponseMsg.newBuilder();
TbResource resource = resourceService.getResource(tenantId, resourceType, resourceKey);
TbResource resource = resourceService.findResourceByTenantIdAndKey(tenantId, resourceType, resourceKey);
if (resource == null && !tenantId.equals(TenantId.SYS_TENANT_ID)) {
resource = resourceService.getResource(TenantId.SYS_TENANT_ID, resourceType, resourceKey);
resource = resourceService.findResourceByTenantIdAndKey(TenantId.SYS_TENANT_ID, resourceType, resourceKey);
}
if (resource != null) {

10
application/src/main/java/org/thingsboard/server/utils/LwM2mObjectModelUtils.java

@ -39,13 +39,13 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA
@Slf4j
public class LwM2mObjectModelUtils {
private static final TbDDFFileParser ddfFileParser = new TbDDFFileParser();
public static void toLwm2mResource (TbResource resource) throws ThingsboardException {
public static void toLwm2mResource(TbResource resource) throws ThingsboardException {
try {
List<ObjectModel> objectModels =
ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
ddfFileParser.parse(new ByteArrayInputStream(resource.getData()), resource.getSearchText());
if (!objectModels.isEmpty()) {
ObjectModel objectModel = objectModels.get(0);
@ -73,7 +73,7 @@ public class LwM2mObjectModelUtils {
public static LwM2mObject toLwM2mObject(TbResource resource, boolean isSave) {
try {
List<ObjectModel> objectModels =
ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText());
ddfFileParser.parse(new ByteArrayInputStream(resource.getData()), resource.getSearchText());
if (objectModels.size() == 0) {
return null;
} else {

49
application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java

@ -16,27 +16,60 @@
package org.thingsboard.server.utils;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.util.TbPair;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
@Slf4j
public class TbNodeUpgradeUtils {
public static void upgradeConfigurationAndVersion(RuleNode node, RuleNodeClassInfo nodeInfo) throws Exception {
public static void upgradeConfigurationAndVersion(RuleNode node, RuleNodeClassInfo nodeInfo) {
JsonNode oldConfiguration = node.getConfiguration();
int configurationVersion = node.getConfigurationVersion();
int currentVersion = nodeInfo.getCurrentVersion();
var configClass = nodeInfo.getAnnotation().configClazz();
if (oldConfiguration == null || !oldConfiguration.isObject()) {
var configClass = nodeInfo.getAnnotation().configClazz();
node.setConfiguration(JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration()));
log.warn("Failed to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}. " +
"Current configuration is null or not a json object. " +
"Going to set default configuration ... ",
node.getId(), node.getType(), configurationVersion, currentVersion);
node.setConfiguration(getDefaultConfig(configClass));
} else {
var tbVersionedNode = (TbNode) nodeInfo.getClazz().getDeclaredConstructor().newInstance();
TbPair<Boolean, JsonNode> upgradeResult = tbVersionedNode.upgrade(node.getConfigurationVersion(), oldConfiguration);
if (upgradeResult.getFirst()) {
node.setConfiguration(upgradeResult.getSecond());
var tbVersionedNode = getTbVersionedNode(nodeInfo);
try {
TbPair<Boolean, JsonNode> upgradeResult = tbVersionedNode.upgrade(configurationVersion, oldConfiguration);
if (upgradeResult.getFirst()) {
node.setConfiguration(upgradeResult.getSecond());
}
} catch (Exception e) {
try {
JacksonUtil.treeToValue(oldConfiguration, configClass);
} catch (Exception ex) {
log.warn("Failed to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}. " +
"Going to set default configuration ... ",
node.getId(), node.getType(), configurationVersion, currentVersion, e);
node.setConfiguration(getDefaultConfig(configClass));
}
}
}
node.setConfigurationVersion(nodeInfo.getCurrentVersion());
node.setConfigurationVersion(currentVersion);
}
@SneakyThrows
private static TbNode getTbVersionedNode(RuleNodeClassInfo nodeInfo) {
return (TbNode) nodeInfo.getClazz().getDeclaredConstructor().newInstance();
}
@SneakyThrows
private static JsonNode getDefaultConfig(Class<? extends NodeConfiguration> configClass) {
return JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration());
}
}

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

@ -586,6 +586,12 @@ cache:
entityLimits:
timeToLiveInMinutes: "${CACHE_SPECS_ENTITY_LIMITS_TTL:5}" # Entity limits cache TTL
maxSize: "${CACHE_SPECS_ENTITY_LIMITS_MAX_SIZE:100000}" # 0 means the cache is disabled
image:
etag:
timeToLiveInMinutes: "${CACHE_SPECS_IMAGE_ETAGS_TTL:44640}" # Image ETags cache TTL
maxSize: "${CACHE_SPECS_IMAGE_ETAGS_MAX_SIZE:10000}" # 0 means the cache is disabled
systemImagesBrowserTtlInMinutes: "${CACHE_SPECS_IMAGE_SYSTEM_BROWSER_TTL:0}" # Browser cache TTL for system images in minutes. 0 means the cache is disabled
tenantImagesBrowserTtlInMinutes: "${CACHE_SPECS_IMAGE_TENANT_BROWSER_TTL:0}" # Browser cache TTL for tenant images in minutes. 0 means the cache is disabled
# Spring data parameters
spring.data.redis.repositories.enabled: false # Disable this because it is not required.
@ -1414,6 +1420,8 @@ queue:
max_msg_size: "${TB_QUEUE_PUBSUB_MAX_MSG_SIZE:1048576}"
# Number of messages per consumer
max_messages: "${TB_QUEUE_PUBSUB_MAX_MESSAGES:1000}"
# Thread pool size for pubsub queue executor provider. If set to 0 - default pubsub executor provider value will be used (5 * number of available processors)
executor_thread_pool_size: "${TB_QUEUE_PUBSUB_EXECUTOR_THREAD_POOL_SIZE:0}"
queue-properties:
# Pub/Sub properties for Rule Engine subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
rule-engine: "${TB_QUEUE_PUBSUB_RE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
@ -1642,6 +1650,9 @@ service:
# Comma-separated list of tenant profile ids assigned to this Rule Engine.
# This Rule Engine will only be responsible for tenants with these profiles (in case 'isolation' option is enabled in the profile).
assigned_tenant_profiles: "${TB_RULE_ENGINE_ASSIGNED_TENANT_PROFILES:}"
pubsub:
# Thread pool size for pubsub rule node executor provider. If not set - default pubsub executor provider value will be used (5 * number of available processors)
executor_thread_pool_size: "${TB_RULE_ENGINE_PUBSUB_EXECUTOR_THREAD_POOL_SIZE:0}"
# Metrics parameters
metrics:

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

@ -802,7 +802,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass);
}
protected <T> T doPut(String urlTemplate, T content, Class<T> responseClass, String... params) {
protected <T> T doPut(String urlTemplate, Object content, Class<T> responseClass, String... params) {
try {
return readResponse(doPut(urlTemplate, content, params).andExpect(status().isOk()), responseClass);
} catch (Exception e) {

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

@ -239,12 +239,12 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h host.docker.internal" +
" -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"",
credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h host.docker.internal -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
credentials.getCredentialsId()));
JsonNode linuxCoapCommands = commands.get(COAP);
@ -254,10 +254,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
" -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway" +
" thingsboard/coap-clients coap-client -v 6 -m POST coap://host.docker.internal:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway" +
" thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://host.docker.internal:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
}
@Test
@ -283,19 +283,20 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t %s -u \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h host.docker.internal" +
" -p 1883 -t %s -u \"%s\" -m \"{temperature:25}\"",
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -u \"%s\" -m \"{temperature:25}\"\"",
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h host.docker.internal -p 8883 -t %s -u \"%s\" -m \"{temperature:25}\"\"",
DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId()));
}
@Test
public void testFetchGatewayLaunchCommands() throws Exception {
String deviceName = "My device";
Device device = new Device();
device.setName("My device");
device.setName(deviceName);
device.setType("default");
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
additionalInfo.put("gateway", true);
@ -308,13 +309,15 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
doGetTyped("/api/device-connectivity/gateway-launch/" + savedDevice.getId().getId(), new TypeReference<>() {
});
String expectedContainerName = deviceName.replaceAll("[^A-Za-z0-9_.-]", "");;
JsonNode dockerMqttCommands = commands.get(MQTT);
assertThat(dockerMqttCommands.get(LINUX).asText()).isEqualTo(String.format("docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=1883 -e accessToken=%s --restart always thingsboard/tb-gateway", credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(WINDOWS).asText()).isEqualTo("docker run -it -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs -v %HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=1883 -e accessToken=" + credentials.getCredentialsId() + " --restart always thingsboard/tb-gateway");
assertThat(dockerMqttCommands.get(LINUX).asText()).isEqualTo(String.format("docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --name " + expectedContainerName + " --add-host=host.docker.internal:host-gateway -p 60000-61000:60000-61000 -e host=host.docker.internal -e port=1883 -e accessToken=%s --restart always thingsboard/tb-gateway", credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(WINDOWS).asText()).isEqualTo("docker run -it -v %HOMEDRIVE%%HOMEPATH%\\tb-gateway\\logs:/thingsboard_gateway/logs -v %HOMEDRIVE%%HOMEPATH%\\tb-gateway\\extensions:/thingsboard_gateway/extensions -v %HOMEDRIVE%%HOMEPATH%\\tb-gateway\\config:/thingsboard_gateway/config --name " + expectedContainerName + " --add-host=host.docker.internal:host-gateway -p 60000-61000:60000-61000 -e host=host.docker.internal -e port=1883 -e accessToken=" + credentials.getCredentialsId() + " --restart always thingsboard/tb-gateway");
JsonNode dockerMqttsCommands = commands.get(MQTTS);
assertThat(dockerMqttsCommands.get(LINUX).asText()).isEqualTo(String.format("docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=8883 -e accessToken=%s --restart always thingsboard/tb-gateway", credentials.getCredentialsId()));
assertThat(dockerMqttsCommands.get(WINDOWS).asText()).isEqualTo("docker run -it -v %HOMEPATH%/tb-gateway/logs:/thingsboard_gateway/logs -v %HOMEPATH%/tb-gateway/extensions:/thingsboard_gateway/extensions -v %HOMEPATH%/tb-gateway/config:/thingsboard_gateway/config --network=host -p 5000:5000 --name tbGatewayLocalhost -e host=localhost -e port=8883 -e accessToken=" + credentials.getCredentialsId() + " --restart always thingsboard/tb-gateway");
assertThat(dockerMqttsCommands.get(LINUX).asText()).isEqualTo(String.format("docker run -it -v ~/.tb-gateway/logs:/thingsboard_gateway/logs -v ~/.tb-gateway/extensions:/thingsboard_gateway/extensions -v ~/.tb-gateway/config:/thingsboard_gateway/config --name " + expectedContainerName + " --add-host=host.docker.internal:host-gateway -p 60000-61000:60000-61000 -e host=host.docker.internal -e port=8883 -e accessToken=%s --restart always thingsboard/tb-gateway", credentials.getCredentialsId()));
assertThat(dockerMqttsCommands.get(WINDOWS).asText()).isEqualTo("docker run -it -v %HOMEDRIVE%%HOMEPATH%\\tb-gateway\\logs:/thingsboard_gateway/logs -v %HOMEDRIVE%%HOMEPATH%\\tb-gateway\\extensions:/thingsboard_gateway/extensions -v %HOMEDRIVE%%HOMEPATH%\\tb-gateway\\config:/thingsboard_gateway/config --name " + expectedContainerName + " --add-host=host.docker.internal:host-gateway -p 60000-61000:60000-61000 -e host=host.docker.internal -e port=8883 -e accessToken=" + credentials.getCredentialsId() + " --restart always thingsboard/tb-gateway");
}
@Test
@ -351,11 +354,11 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h ::1" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h host.docker.internal" +
" -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h ::1 -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h host.docker.internal -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"",
credentials.getCredentialsId()));
JsonNode linuxCoapCommands = commands.get(COAP);
@ -365,10 +368,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
" -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER);
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client -v 6 -m POST coap://[::1]:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" +
" thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://[::1]:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway" +
" thingsboard/coap-clients coap-client -v 6 -m POST coap://host.docker.internal:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway" +
" thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://host.docker.internal:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId()));
}
@Test
@ -461,12 +464,12 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"-t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER);
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" +
assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h host.docker.internal" +
" -p 1883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"",
DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " +
assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --add-host=host.docker.internal:host-gateway thingsboard/mosquitto-clients " +
"/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " +
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"\"",
"mosquitto_pub -d -q 1 --cafile ca-root.pem -h host.docker.internal -p 8883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"\"",
DEVICE_TELEMETRY_TOPIC, clientId, userName, password));
}

277
application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java

File diff suppressed because one or more lines are too long

118
application/src/test/java/org/thingsboard/server/controller/TbResourceControllerTest.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.lwm2m.LwM2mObject;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -48,6 +49,7 @@ import java.util.Base64;
import java.util.Collections;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -101,7 +103,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -116,9 +118,10 @@ public class TbResourceControllerTest extends AbstractControllerTest {
Assert.assertEquals(resource.getTitle(), savedResource.getTitle());
Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getFileName());
Assert.assertEquals(DEFAULT_FILE_NAME, savedResource.getResourceKey());
Assert.assertEquals(resource.getData(), savedResource.getData());
Assert.assertArrayEquals(resource.getData(), download(savedResource.getId()));
savedResource.setTitle("My new resource");
savedResource.setData(null);
save(savedResource);
@ -136,7 +139,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle(StringUtils.randomAlphabetic(300));
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
Mockito.reset(tbClusterService, auditLogService);
@ -155,7 +158,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -184,13 +187,14 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class);
TbResource foundResource = doGet("/api/resource/" + savedResource.getUuidId(), TbResource.class);
Assert.assertNotNull(foundResource);
Assert.assertEquals(savedResource, foundResource);
Assert.assertEquals(savedResource.getId(), foundResource.getId());
Assert.assertEquals(savedResource.getFileName(), foundResource.getFileName());
}
@Test
@ -199,7 +203,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -213,7 +217,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.DELETED, ActionType.DELETED, resourceIdStr);
doGet("/api/resource/" + savedResource.getId().getId().toString())
doGet("/api/resource/" + savedResource.getUuidId())
.andExpect(status().isNotFound())
.andExpect(statusReason(containsString(msgErrorNoFound("Resource", resourceIdStr))));
}
@ -224,7 +228,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle("My first resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -255,7 +259,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
resources.add(new TbResourceInfo(save(resource)));
}
List<TbResourceInfo> loadedResources = new ArrayList<>();
@ -292,7 +296,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("JKS Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
resources.add(new TbResourceInfo(save(resource)));
}
@ -302,7 +306,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("LWM2M Resource" + i);
resource.setResourceType(ResourceType.PKCS_12);
resource.setFileName(i + DEFAULT_FILE_NAME_2);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
save(resource);
}
@ -339,7 +343,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
resources.add(new TbResourceInfo(save(resource)));
}
List<TbResourceInfo> loadedResources = new ArrayList<>();
@ -399,7 +403,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("JKS Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResourceInfo saved = new TbResourceInfo(save(resource));
jksResources.add(saved);
}
@ -410,7 +414,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("LWM2M Resource" + i);
resource.setResourceType(ResourceType.PKCS_12);
resource.setFileName(i + DEFAULT_FILE_NAME_2);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource saved = save(resource);
lwm2mesources.add(saved);
}
@ -476,7 +480,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
expectedResources.add(new TbResourceInfo(save(resource)));
}
@ -487,7 +491,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setTitle("Resource" + i);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(i + DEFAULT_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResourceInfo savedResource = new TbResourceInfo(save(resource));
systemResources.add(savedResource);
if (i >= 73) {
@ -531,7 +535,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JS_MODULE);
resource.setTitle("Js resource");
resource.setFileName(JS_TEST_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -562,7 +566,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JS_MODULE);
resource.setTitle("Js resource");
resource.setFileName(JS_TEST_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -606,7 +610,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
resource.setResourceType(ResourceType.JS_MODULE);
resource.setTitle("Js resource");
resource.setFileName(JS_TEST_FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
@ -616,6 +620,52 @@ public class TbResourceControllerTest extends AbstractControllerTest {
.andExpect(status().isForbidden());
}
@Test
public void testUpdateResourceData_nonUpdatableResourceType() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.PKCS_12);
resource.setTitle("My resource");
resource.setFileName("3.pks");
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
resource.setEtag(savedResource.getEtag());
savedResource.setEncodedData(TEST_DATA);
doPost("/api/resource", savedResource)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("can't be updated")));
savedResource.setData(null);
savedResource.setTitle("Updated resource");
savedResource = doPost("/api/resource", savedResource, TbResource.class);
assertThat(savedResource.getTitle()).isEqualTo("Updated resource");
assertThat(savedResource.getFileName()).isEqualTo(resource.getFileName());
assertThat(savedResource.getEtag()).isEqualTo(resource.getEtag());
assertThat(download(savedResource.getId())).asBase64Encoded().isEqualTo(TEST_DATA);
}
@Test
public void testUpdateResourceData_updatableResourceType() throws Exception {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.JS_MODULE);
resource.setTitle("My resource");
resource.setFileName("module.js");
resource.setEncodedData(TEST_DATA);
TbResource savedResource = save(resource);
resource.setEtag(savedResource.getEtag());
String newData = Base64.getEncoder().encodeToString(new byte[]{1, 2, 3});
savedResource.setEncodedData(newData);
savedResource.setFileName("new-module.js");
savedResource.setTitle("Updated title");
savedResource = save(savedResource);
assertThat(savedResource.getTitle()).isEqualTo("Updated title");
assertThat(savedResource.getFileName()).isEqualTo("new-module.js");
assertThat(savedResource.getEtag()).isNotEqualTo(resource.getEtag());
assertThat(download(savedResource.getId())).asBase64Encoded().isEqualTo(newData);
}
@Test
public void testGetLwm2mListObjectsPage() throws Exception {
loginTenantAdmin();
@ -623,7 +673,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
List<TbResource> resources = loadLwm2mResources();
List<LwM2mObject> objects =
doGetTyped("/api/resource/lwm2m/page?pageSize=100&page=0", new TypeReference<>(){});
doGetTyped("/api/resource/lwm2m/page?pageSize=100&page=0", new TypeReference<>() {});
Assert.assertNotNull(objects);
Assert.assertEquals(resources.size(), objects.size());
@ -637,13 +687,29 @@ public class TbResourceControllerTest extends AbstractControllerTest {
List<TbResource> resources = loadLwm2mResources();
List<LwM2mObject> objects =
doGetTyped("/api/resource/lwm2m?sortProperty=id&sortOrder=ASC&objectIds=3_1.0,5_1.0,19_1.1", new TypeReference<>(){});
doGetTyped("/api/resource/lwm2m?sortProperty=id&sortOrder=ASC&objectIds=3_1.0,5_1.0,19_1.1", new TypeReference<>() {});
Assert.assertNotNull(objects);
Assert.assertEquals(3, objects.size());
removeLoadResources(resources);
}
private TbResource save(TbResource tbResource) throws Exception {
return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>() {
});
}
private byte[] download(TbResourceId resourceId) throws Exception {
return doGet("/api/resource/" + resourceId + "/download")
.andExpect(status().isOk())
.andReturn().getResponse().getContentAsByteArray();
}
private String getImageLink(TbResourceInfo resourceInfo) {
return "/api/images/" + (resourceInfo.getTenantId().isSysTenantId() ? "system/" : "") + resourceInfo.getResourceKey();
}
private List<TbResource> loadLwm2mResources() throws Exception {
var models = List.of("1", "2", "3", "5", "6", "9", "19", "3303");
@ -656,7 +722,7 @@ public class TbResourceControllerTest extends AbstractControllerTest {
TbResource resource = new TbResource();
resource.setResourceType(ResourceType.LWM2M_MODEL);
resource.setFileName(fileName);
resource.setData(Base64.getEncoder().encodeToString(bytes));
resource.setData(bytes);
resources.add(save(resource));
}
@ -670,8 +736,4 @@ public class TbResourceControllerTest extends AbstractControllerTest {
}
}
private TbResource save(TbResource tbResource) throws Exception {
return doPostWithTypedResponse("/api/resource", tbResource, new TypeReference<>() {
});
}
}

2
application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java

@ -52,7 +52,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.common.adaptor.JsonConverter;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;

7
application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java

@ -47,7 +47,7 @@ public class ResourceEdgeTest extends AbstractEdgeTest {
resource.setResourceType(ResourceType.JKS);
resource.setTitle("Edge Test Resource");
resource.setFileName(FILE_NAME);
resource.setData(TEST_DATA);
resource.setEncodedData(TEST_DATA);
edgeImitator.expectMessageAmount(1);
TbResource savedResource = doPost("/api/resource", resource, TbResource.class);
@ -65,7 +65,7 @@ public class ResourceEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(ResourceType.JKS, tbResource.getResourceType());
Assert.assertEquals(FILE_NAME, tbResource.getResourceKey());
Assert.assertEquals(FILE_NAME, tbResource.getFileName());
Assert.assertEquals(TEST_DATA, tbResource.getData());
Assert.assertEquals(TEST_DATA, tbResource.getEncodedData());
Assert.assertTrue(StringUtils.isNotBlank(tbResource.getEtag()));
// update resource
@ -121,6 +121,7 @@ public class ResourceEdgeTest extends AbstractEdgeTest {
TbResource tb = doGet("/api/resource/" + uuid, TbResource.class);
Assert.assertNotNull(tb);
Assert.assertEquals("Edge Test Resource", tb.getName());
Assert.assertEquals(TEST_DATA, tb.getEncodedData());
}
@Test
@ -173,7 +174,7 @@ public class ResourceEdgeTest extends AbstractEdgeTest {
tbResource.setResourceType(ResourceType.JKS);
tbResource.setResourceKey(FILE_NAME);
tbResource.setFileName(FILE_NAME);
tbResource.setData(TEST_DATA);
tbResource.setEncodedData(TEST_DATA);
return tbResource;
}
}

3
application/src/test/java/org/thingsboard/server/service/install/InstallScriptsTest.java

@ -36,6 +36,7 @@ import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.service.install.update.ImagesUpdater;
import java.io.IOException;
import java.nio.file.Path;
@ -63,6 +64,8 @@ class InstallScriptsTest {
OAuth2ConfigTemplateService oAuth2TemplateService;
@MockBean
ResourceService resourceService;
@MockBean
ImagesUpdater imagesUpdater;
@SpyBean
InstallScripts installScripts;

90
application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java

File diff suppressed because one or more lines are too long

4
application/src/test/java/org/thingsboard/server/service/transport/DefaultTransportApiServiceTest.java

@ -44,13 +44,13 @@ import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.queue.QueueService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.service.resource.TbResourceService;
import java.io.IOException;
import java.nio.file.Files;
@ -96,7 +96,7 @@ public class DefaultTransportApiServiceTest {
@MockBean
protected DeviceProvisionService deviceProvisionService;
@MockBean
protected TbResourceService resourceService;
protected ResourceService resourceService;
@MockBean
protected OtaPackageService otaPackageService;
@MockBean

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

@ -206,7 +206,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
lwModel.setFileName(resourceName);
lwModel.setTenantId(tenantId);
byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName));
lwModel.setData(Base64.getEncoder().encodeToString(bytes));
lwModel.setData(bytes);
lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>() {
});
Assert.assertNotNull(lwModel);

59
application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java

@ -21,6 +21,8 @@ import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNode;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetCustomerAttributeNode;
import org.thingsboard.rule.engine.metadata.TbGetEntityDataNodeConfiguration;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.service.component.RuleNodeClassInfo;
@ -38,7 +40,7 @@ public class TbNodeUpgradeUtilsTest {
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getClazz()).thenReturn((Class) TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
@ -60,7 +62,7 @@ public class TbNodeUpgradeUtilsTest {
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getClazz()).thenReturn((Class) TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
@ -81,7 +83,7 @@ public class TbNodeUpgradeUtilsTest {
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class);
when(nodeInfo.getClazz()).thenReturn((Class) TbGetAttributesNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
@ -102,4 +104,55 @@ public class TbNodeUpgradeUtilsTest {
}
@Test
public void testUpgradeRuleNodeConfigurationWithNewConfigAndOldConfigVersion() throws Exception {
// GIVEN
var node = new RuleNode();
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetEntityDataNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class) TbGetCustomerAttributeNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
String versionOneDefaultConfig = "{\"fetchTo\":\"METADATA\"," +
"\"dataMapping\":{\"alarmThreshold\":\"threshold\"}," +
"\"dataToFetch\":\"ATTRIBUTES\"}";
node.setConfiguration(JacksonUtil.toJsonNode(versionOneDefaultConfig));
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
@Test
public void testUpgradeRuleNodeConfigurationWithInvalidConfigAndOldConfigVersion() throws Exception {
// GIVEN
var node = new RuleNode();
var nodeInfo = mock(RuleNodeClassInfo.class);
var nodeConfigClazz = TbGetEntityDataNodeConfiguration.class;
var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class);
var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration());
when(nodeInfo.getClazz()).thenReturn((Class) TbGetCustomerAttributeNode.class);
when(nodeInfo.getCurrentVersion()).thenReturn(1);
when(nodeInfo.getAnnotation()).thenReturn(annotation);
when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz);
// missing telemetry field
String oldConfig = "{\"attrMapping\":{\"alarmThreshold\":\"threshold\"}}";;
node.setConfiguration(JacksonUtil.toJsonNode(oldConfig));
// WHEN
TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo);
// THEN
Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig);
Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1);
}
}

4
common/cluster-api/pom.xml

@ -40,6 +40,10 @@
<groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>proto</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId>

2
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -29,7 +29,7 @@ import org.thingsboard.server.dao.entity.EntityDaoService;
import java.util.List;
public interface DashboardService extends EntityDaoService {
Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId);
ListenableFuture<Dashboard> findDashboardByIdAsync(TenantId tenantId, DashboardId dashboardId);

58
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ImageService.java

@ -0,0 +1,58 @@
/**
* Copyright © 2016-2023 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.resource;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.TbImageDeleteResult;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
public interface ImageService {
TbResourceInfo saveImage(TbResource image);
TbResourceInfo saveImageInfo(TbResourceInfo imageInfo);
TbResourceInfo getImageInfoByTenantIdAndKey(TenantId tenantId, String key);
PageData<TbResourceInfo> getImagesByTenantId(TenantId tenantId, PageLink pageLink);
PageData<TbResourceInfo> getAllImagesByTenantId(TenantId tenantId, PageLink pageLink);
byte[] getImageData(TenantId tenantId, TbResourceId imageId);
byte[] getImagePreview(TenantId tenantId, TbResourceId imageId);
TbImageDeleteResult deleteImage(TbResourceInfo imageInfo, boolean force);
TbResourceInfo findSystemOrTenantImageByEtag(TenantId tenantId, String etag);
boolean replaceBase64WithImageUrl(HasImage entity, String type);
boolean replaceBase64WithImageUrl(Dashboard dashboard);
boolean replaceBase64WithImageUrl(WidgetTypeDetails widgetType);
void inlineImage(HasImage entity);
void inlineImages(Dashboard dashboard);
void inlineImages(WidgetTypeDetails widgetTypeDetails);
}

7
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -34,12 +34,14 @@ public interface ResourceService extends EntityDaoService {
TbResource saveResource(TbResource resource, boolean doValidate);
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
TbResource findResourceByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
TbResource findResourceById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId);
TbResourceInfo findResourceInfoByTenantIdAndKey(TenantId tenantId, ResourceType resourceType, String resourceKey);
PageData<TbResource> findAllTenantResources(TenantId tenantId, PageLink pageLink);
ListenableFuture<TbResourceInfo> findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId);
@ -54,7 +56,10 @@ public interface ResourceService extends EntityDaoService {
void deleteResource(TenantId tenantId, TbResourceId resourceId);
void deleteResource(TenantId tenantId, TbResourceId resourceId, boolean force);
void deleteResourcesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

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

@ -30,7 +30,7 @@ import java.util.Objects;
import java.util.Set;
@ApiModel
public class DashboardInfo extends BaseData<DashboardId> implements HasName, HasTenantId, HasTitle {
public class DashboardInfo extends BaseData<DashboardId> implements HasName, HasTenantId, HasTitle, HasImage {
private static final long serialVersionUID = -9080404114760433799L;

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

@ -50,6 +50,7 @@ public class DataConstants {
public static final String HTTP_TRANSPORT_NAME = "HTTP";
public static final String SNMP_TRANSPORT_NAME = "SNMP";
public static final String MAXIMUM_NUMBER_OF_DEVICES_REACHED = "Maximum number of devices reached!";
public static final String TB_IMAGE_PREFIX = "tb-image;";
public static final String[] allScopes() {

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

@ -41,7 +41,7 @@ import java.io.IOException;
@ToString(exclude = {"image", "profileDataBytes"})
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class DeviceProfile extends BaseData<DeviceProfileId> implements HasName, HasTenantId, HasOtaPackage, HasRuleEngineProfile, ExportableEntity<DeviceProfileId> {
public class DeviceProfile extends BaseData<DeviceProfileId> implements HasName, HasTenantId, HasOtaPackage, HasRuleEngineProfile, ExportableEntity<DeviceProfileId>, HasImage {
private static final long serialVersionUID = 6998485460273302018L;

24
common/data/src/main/java/org/thingsboard/server/common/data/HasImage.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2023 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;
public interface HasImage extends HasTenantId, HasName {
String getImage();
void setImage(String image);
}

30
common/data/src/main/java/org/thingsboard/server/common/data/ImageDescriptor.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class ImageDescriptor {
private String mediaType;
private int width;
private int height;
private long size;
private String etag;
private ImageDescriptor previewDescriptor;
}

33
common/data/src/main/java/org/thingsboard/server/common/data/ImageExportData.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@ApiModel
@Slf4j
@Data
public class ImageExportData {
private final String mediaType;
private final String fileName;
private final String title;
private final String resourceKey;
private final String data;
}

17
common/data/src/main/java/org/thingsboard/server/common/data/ResourceType.java

@ -16,20 +16,21 @@
package org.thingsboard.server.common.data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum ResourceType {
LWM2M_MODEL("application/xml", false),
JKS("application/x-java-keystore", false),
PKCS_12("application/x-pkcs12", false),
JS_MODULE("application/javascript", true);
LWM2M_MODEL("application/xml", false, false),
JKS("application/x-java-keystore", false, false),
PKCS_12("application/x-pkcs12", false, false),
JS_MODULE("application/javascript", true, true),
IMAGE(null, true, true);
@Getter
private final String mediaType;
@Getter
private final boolean customerAccess;
@Getter
private final boolean updatable;
ResourceType(String mediaType, boolean customerAccess) {
this.mediaType = mediaType;
this.customerAccess = customerAccess;
}
}

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

@ -173,6 +173,10 @@ public class StringUtils {
return false;
}
public static String substringBeforeLast(String str, String separator) {
return org.apache.commons.lang3.StringUtils.substringBeforeLast(str, separator);
}
public static String substringAfterLast(String str, String sep) {
return org.apache.commons.lang3.StringUtils.substringAfterLast(str, sep);
}

32
common/data/src/main/java/org/thingsboard/server/common/data/TbImageDeleteResult.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2023 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.HasId;
import java.util.List;
import java.util.Map;
@Data
@Builder
public class TbImageDeleteResult {
private boolean success;
private Map<String, List<? extends HasId<?>>> references;
}

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

File diff suppressed because one or more lines are too long

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

@ -16,6 +16,9 @@
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -26,11 +29,13 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.validation.Length;
import org.thingsboard.server.common.data.validation.NoXss;
import java.util.function.UnaryOperator;
@ApiModel
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, HasTenantId {
public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, HasTenantId, ExportableEntity<TbResourceId> {
private static final long serialVersionUID = 7282664529021651736L;
@ -48,8 +53,16 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
private String resourceKey;
@ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String searchText;
@ApiModelProperty(position = 8, value = "Resource etag.", example = "33a64df551425fcc55e4d42a148795d9f25f89d4", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String etag;
@NoXss
@Length(fieldName = "file name")
@ApiModelProperty(position = 9, value = "Resource file name.", example = "19.xml", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String fileName;
private JsonNode descriptor;
private TbResourceId externalId;
public TbResourceInfo() {
super();
@ -61,18 +74,21 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
public TbResourceInfo(TbResourceInfo resourceInfo) {
super(resourceInfo);
this.tenantId = resourceInfo.getTenantId();
this.title = resourceInfo.getTitle();
this.resourceType = resourceInfo.getResourceType();
this.resourceKey = resourceInfo.getResourceKey();
this.searchText = resourceInfo.getSearchText();
this.etag = resourceInfo.getEtag();
this.tenantId = resourceInfo.tenantId;
this.title = resourceInfo.title;
this.resourceType = resourceInfo.resourceType;
this.resourceKey = resourceInfo.resourceKey;
this.searchText = resourceInfo.searchText;
this.etag = resourceInfo.etag;
this.fileName = resourceInfo.fileName;
this.descriptor = resourceInfo.descriptor != null ? resourceInfo.descriptor.deepCopy() : null;
this.externalId = resourceInfo.externalId;
}
@ApiModelProperty(position = 1, value = "JSON object with the Resource Id. " +
"Specify this field to update the Resource. " +
"Referencing non-existing Resource Id will cause error. " +
"Omit this field to create new Resource." )
"Omit this field to create new Resource.")
@Override
public TbResourceId getId() {
return super.getId();
@ -85,34 +101,37 @@ public class TbResourceInfo extends BaseData<TbResourceId> implements HasName, H
}
@Override
@JsonIgnore
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return title;
}
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getLink() {
if (resourceType == ResourceType.IMAGE) {
return "/api/images/" + ((tenantId == null || !tenantId.isSysTenantId()) ? "tenant" : "system") + "/" + resourceKey; // tenantId is null in case of export to git
}
return null;
}
@JsonIgnore
public String getSearchText() {
return searchText != null ? searchText : title;
return title;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("ResourceInfo [tenantId=");
builder.append(tenantId);
builder.append(", id=");
builder.append(getUuidId());
builder.append(", createdTime=");
builder.append(createdTime);
builder.append(", title=");
builder.append(title);
builder.append(", resourceType=");
builder.append(resourceType);
builder.append(", resourceKey=");
builder.append(resourceKey);
builder.append(", hashCode=");
builder.append(etag);
builder.append("]");
return builder.toString();
public <T> T getDescriptor(Class<T> type) throws JsonProcessingException {
return descriptor != null ? mapper.treeToValue(descriptor, type) : null;
}
public <T> void updateDescriptor(Class<T> type, UnaryOperator<T> updater) throws JsonProcessingException {
T descriptor = getDescriptor(type);
descriptor = updater.apply(descriptor);
setDescriptorValue(descriptor);
}
@JsonIgnore
public void setDescriptorValue(Object value) {
this.descriptor = value != null ? mapper.valueToTree(value) : null;
}
}

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

@ -19,11 +19,13 @@ import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.Set;
@Data
@Builder
public class TbResourceInfoFilter {
private TenantId tenantId;
private ResourceType resourceType;
private Set<ResourceType> resourceTypes;
}

6
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetProfile.java

@ -22,8 +22,8 @@ import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasRuleEngineProfile;
import org.thingsboard.server.common.data.HasTenantId;
@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@ToString(exclude = {"image"})
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class AssetProfile extends BaseData<AssetProfileId> implements HasName, HasTenantId, HasRuleEngineProfile, ExportableEntity<AssetProfileId> {
public class AssetProfile extends BaseData<AssetProfileId> implements HasName, HasTenantId, HasRuleEngineProfile, ExportableEntity<AssetProfileId>, HasImage {
private static final long serialVersionUID = 6998485460273302018L;
@ -114,7 +114,7 @@ public class AssetProfile extends BaseData<AssetProfileId> implements HasName, H
}
@ApiModelProperty(position = 5, value = "Used to mark the default profile. Default profile is used when the asset profile is not specified during asset creation.")
public boolean isDefault(){
public boolean isDefault() {
return isDefault;
}

4
common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetProfile;
import org.thingsboard.server.common.data.notification.rule.NotificationRule;
@ -56,7 +57,8 @@ import java.lang.annotation.Target;
@Type(name = "WIDGET_TYPE", value = WidgetTypeDetails.class),
@Type(name = "NOTIFICATION_TEMPLATE", value = NotificationTemplate.class),
@Type(name = "NOTIFICATION_TARGET", value = NotificationTarget.class),
@Type(name = "NOTIFICATION_RULE", value = NotificationRule.class)
@Type(name = "NOTIFICATION_RULE", value = NotificationRule.class),
@Type(name = "TB_RESOURCE", value = TbResource.class)
})
@JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true)
public @interface JsonTbEntity {

1
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -39,6 +39,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxRuleChains;
private long maxResourcesInBytes;
private long maxOtaPackagesInBytes;
private long maxResourceSize;
private String transportTenantMsgRateLimit;
private String transportTenantTelemetryMsgRateLimit;

5
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java

@ -21,6 +21,7 @@ import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
@ -29,10 +30,10 @@ import org.thingsboard.server.common.data.validation.NoXss;
@Data
@JsonPropertyOrder({ "fqn", "name", "deprecated", "image", "description", "descriptor", "externalId" })
public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantId, ExportableEntity<WidgetTypeId> {
public class WidgetTypeDetails extends WidgetType implements HasName, HasTenantId, HasImage, ExportableEntity<WidgetTypeId> {
@Length(fieldName = "image", max = 1000000)
@ApiModelProperty(position = 9, value = "Base64 encoded thumbnail")
@ApiModelProperty(position = 9, value = "Relative or external image URL. Replaced with image data URL (Base64) in case of relative URL and 'inlineImages' option enabled.")
private String image;
@NoXss
@Length(fieldName = "description", max = 1024)

7
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java

@ -22,8 +22,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.BaseDataWithAdditionalInfo;
import org.thingsboard.server.common.data.ExportableEntity;
import org.thingsboard.server.common.data.HasImage;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.HasTitle;
@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true)
public class WidgetsBundle extends BaseData<WidgetsBundleId> implements HasName, HasTenantId, ExportableEntity<WidgetsBundleId>, HasTitle {
public class WidgetsBundle extends BaseData<WidgetsBundleId> implements HasName, HasTenantId, ExportableEntity<WidgetsBundleId>, HasTitle, HasImage {
private static final long serialVersionUID = -7627368878362410489L;
@ -60,12 +60,13 @@ public class WidgetsBundle extends BaseData<WidgetsBundleId> implements HasName,
@Length(fieldName = "image", max = 1000000)
@Getter
@Setter
@ApiModelProperty(position = 6, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
@ApiModelProperty(position = 6, value = "Relative or external image URL. Replaced with image data URL (Base64) in case of relative URL and 'inlineImages' option enabled.", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String image;
@NoXss
@Length(fieldName = "description", max = 1024)
@Getter
@Setter
@ApiModelProperty(position = 7, value = "Description", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String description;

1
common/pom.xml

@ -48,6 +48,7 @@
<module>edge-api</module>
<module>version-control</module>
<module>script</module>
<module>proto</module>
</modules>
</project>

94
common/proto/pom.xml

@ -0,0 +1,94 @@
<!--
Copyright © 2016-2023 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.6.2-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
<artifactId>proto</artifactId>
<packaging>jar</packaging>
<name>Thingsboard Server Common Protobuf and gRPC structures</name>
<url>https://thingsboard.io</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/../..</main.dir>
</properties>
<dependencies>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId>
</dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>message</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<distributionManagement>
<repository>
<id>thingsboard-repo-deploy</id>
<name>ThingsBoard Repo Deployment</name>
<url>https://repo.thingsboard.io/artifactory/libs-release-public</url>
</repository>
</distributionManagement>
</project>

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java → common/proto/src/main/java/org/thingsboard/server/common/adaptor/AdaptorException.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.adaptor;
package org.thingsboard.server.common.adaptor;
public class AdaptorException extends Exception {

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java → common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverter.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.adaptor;
package org.thingsboard.server.common.adaptor;
import com.google.gson.Gson;
import com.google.gson.JsonArray;

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java → common/proto/src/main/java/org/thingsboard/server/common/adaptor/JsonConverterConfig.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.adaptor;
package org.thingsboard.server.common.adaptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java → common/proto/src/main/java/org/thingsboard/server/common/adaptor/ProtoConverter.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.transport.adaptor;
package org.thingsboard.server.common.adaptor;
import com.google.gson.Gson;
import com.google.gson.JsonElement;

2
application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java → common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
package org.thingsboard.server.common.util;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;

0
common/cluster-api/src/main/proto/jsinvoke.proto → common/proto/src/main/proto/jsinvoke.proto

7
common/cluster-api/src/main/proto/queue.proto → common/proto/src/main/proto/queue.proto

@ -306,6 +306,12 @@ message CoreStartupMsg {
int64 ts = 3;
}
message ResourceCacheInvalidateMsg {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
string resourceKey = 3;
}
message LwM2MRegistrationRequestMsg {
string tenantId = 1;
string endpoint = 2;
@ -1281,6 +1287,7 @@ message ToCoreNotificationMsg {
EdgeEventUpdateMsgProto edgeEventUpdate = 14;
ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 15;
FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 16;
ResourceCacheInvalidateMsg resourceCacheInvalidateMsg = 17;
}
/* Messages that are handled by ThingsBoard RuleEngine Service */

0
common/transport/transport-api/src/main/proto/transport.proto → common/proto/src/main/proto/transport.proto

3
common/transport/transport-api/src/test/java/JsonConverterTest.java → common/proto/src/test/java/org/thingsboard/server/common/adaptor/JsonConverterTest.java

@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.adaptor;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import org.junit.Assert;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import java.util.ArrayList;

2
application/src/test/java/org/thingsboard/server/service/queue/ProtoUtilsTest.java → common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.queue;
package org.thingsboard.server.common.util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

2
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.pubsub;
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutures;
import com.google.api.gax.core.FixedExecutorProvider;
import com.google.cloud.pubsub.v1.stub.GrpcSubscriberStub;
import com.google.cloud.pubsub.v1.stub.SubscriberStub;
import com.google.cloud.pubsub.v1.stub.SubscriberStubSettings;
@ -76,6 +77,7 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> extends AbstractPara
SubscriberStubSettings.defaultGrpcTransportProviderBuilder()
.setMaxInboundMessageSize(pubSubSettings.getMaxMsgSize())
.build())
.setExecutorProvider(pubSubSettings.getExecutorProvider())
.build();
this.subscriber = GrpcSubscriberStub.create(subscriberStubSettings);
} catch (IOException e) {

6
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubProducerTemplate.java

@ -18,6 +18,7 @@ package org.thingsboard.server.queue.pubsub;
import com.google.api.core.ApiFuture;
import com.google.api.core.ApiFutureCallback;
import com.google.api.core.ApiFutures;
import com.google.api.gax.core.FixedExecutorProvider;
import com.google.cloud.pubsub.v1.Publisher;
import com.google.gson.Gson;
import com.google.protobuf.ByteString;
@ -120,7 +121,10 @@ public class TbPubSubProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
try {
admin.createTopicIfNotExists(topic);
ProjectTopicName topicName = ProjectTopicName.of(pubSubSettings.getProjectId(), topic);
Publisher publisher = Publisher.newBuilder(topicName).setCredentialsProvider(pubSubSettings.getCredentialsProvider()).build();
Publisher publisher = Publisher.newBuilder(topicName)
.setCredentialsProvider(pubSubSettings.getCredentialsProvider())
.setExecutorProvider(pubSubSettings.getExecutorProvider())
.build();
publisherMap.put(topic, publisher);
return publisher;
} catch (IOException e) {

25
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSettings.java

@ -17,16 +17,20 @@ package org.thingsboard.server.queue.pubsub;
import com.google.api.gax.core.CredentialsProvider;
import com.google.api.gax.core.FixedCredentialsProvider;
import com.google.api.gax.core.FixedExecutorProvider;
import com.google.auth.oauth2.ServiceAccountCredentials;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.concurrent.Executors;
@Slf4j
@ConditionalOnExpression("'${queue.type:null}'=='pubsub'")
@ -46,6 +50,16 @@ public class TbPubSubSettings {
@Value("${queue.pubsub.max_messages}")
private int maxMessages;
@Value("${queue.pubsub.executor_thread_pool_size:0}")
private int threadPoolSize;
/**
* Refers to com.google.cloud.pubsub.v1.Publisher default executor configuration
*/
private static final int THREADS_PER_CPU = 5;
private FixedExecutorProvider executorProvider;
private CredentialsProvider credentialsProvider;
@PostConstruct
@ -53,6 +67,17 @@ public class TbPubSubSettings {
ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(
new ByteArrayInputStream(serviceAccount.getBytes()));
credentialsProvider = FixedCredentialsProvider.create(credentials);
if (threadPoolSize == 0) {
threadPoolSize = THREADS_PER_CPU * Runtime.getRuntime().availableProcessors();
}
executorProvider = FixedExecutorProvider
.create(Executors.newScheduledThreadPool(threadPoolSize, ThingsBoardThreadFactory.forName("pubsub-queue-executor")));
}
@PreDestroy
private void destroy() {
if (executorProvider != null) {
executorProvider.getExecutor().shutdownNow();
}
}
}

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

Loading…
Cancel
Save