From 2bcc82f682741246e0e7d14f83a2330cd960842b Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 10 Mar 2026 09:19:58 +0200 Subject: [PATCH 01/17] eliminated usage of DefaultNotificationRuleRecipientsConfig and EntityExportData instances --- .../impl/BaseEntityExportService.java | 4 - .../impl/DefaultEntityExportService.java | 7 +- .../exporting/impl/DeviceExportService.java | 5 - .../impl/OtaPackageExportService.java | 5 - .../impl/RuleChainExportService.java | 5 - .../impl/WidgetTypeExportService.java | 5 - .../impl/WidgetsBundleExportService.java | 5 - .../AbstractNotificationApiTest.java | 3 +- .../notification/NotificationRuleApiTest.java | 8 +- ...faultNotificationRuleRecipientsConfig.java | 79 ++++++++++-- ...latedNotificationRuleRecipientsConfig.java | 6 + .../NotificationRuleRecipientsConfig.java | 6 +- .../common/data/sync/ie/DeviceExportData.java | 4 + .../common/data/sync/ie/EntityExportData.java | 115 ++++++++++++++++-- .../data/sync/ie/OtaPackageExportData.java | 4 + .../data/sync/ie/RuleChainExportData.java | 4 + .../data/sync/ie/WidgetTypeExportData.java | 4 + .../data/sync/ie/WidgetsBundleExportData.java | 4 + .../notification/DefaultNotifications.java | 4 +- 19 files changed, 207 insertions(+), 70 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java index d6ed0754e3..586febd686 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java @@ -42,10 +42,6 @@ public abstract class BaseEntityExportService ctx, E mainEntity, D exportData) { } - protected D newExportData() { - return (D) new EntityExportData(); - } - public abstract Set getSupportedEntityTypes(); protected void replaceUuidsRecursively(EntitiesExportCtx ctx, JsonNode node, Set skippedRootFields, Pattern includedFieldsPattern) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java index a488ad165e..21497e0f89 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java @@ -70,7 +70,7 @@ public class DefaultEntityExportService ctx, I entityId) throws ThingsboardException { - D exportData = newExportData(); + D exportData = (D) EntityExportData.newInstance(entityId.getEntityType()); E entity = exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entityId); if (entity == null) { @@ -78,7 +78,6 @@ public class DefaultEntityExportService(); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java index 57beddef59..4a18a23b80 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java @@ -48,11 +48,6 @@ public class DeviceExportService extends BaseEntityExportService getSupportedEntityTypes() { return Set.of(EntityType.DEVICE); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/OtaPackageExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/OtaPackageExportService.java index af29e3c5df..775ab87fcb 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/OtaPackageExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/OtaPackageExportService.java @@ -36,11 +36,6 @@ public class OtaPackageExportService extends BaseEntityExportService getSupportedEntityTypes() { return Set.of(EntityType.OTA_PACKAGE); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java index 52452bcca4..ce93574103 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java @@ -61,11 +61,6 @@ public class RuleChainExportService extends BaseEntityExportService getSupportedEntityTypes() { return Set.of(EntityType.RULE_CHAIN); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java index cb2df97a57..7e381b39bb 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetTypeExportService.java @@ -38,11 +38,6 @@ public class WidgetTypeExportService extends BaseEntityExportService getSupportedEntityTypes() { return Set.of(EntityType.WIDGET_TYPE); diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java index 3f579bbc43..3d4b80ce1c 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java @@ -45,11 +45,6 @@ public class WidgetsBundleExportService extends BaseEntityExportService getSupportedEntityTypes() { return Set.of(EntityType.WIDGETS_BUNDLE); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java index f5607415fa..cf99669777 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java @@ -188,8 +188,7 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest rule.setTriggerType(triggerConfig.getTriggerType()); rule.setTriggerConfig(triggerConfig); - DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(triggerConfig.getTriggerType()); + DefaultNotificationRuleRecipientsConfig recipientsConfig = DefaultNotificationRuleRecipientsConfig.forTriggerType(triggerConfig.getTriggerType()); recipientsConfig.setTargets(DaoUtil.toUUIDs(targets)); rule.setRecipientsConfig(recipientsConfig); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index e3342dbf43..94af26d3d7 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -211,7 +211,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { notificationRule.setTriggerConfig(triggerConfig); EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(NotificationRuleTriggerType.ALARM); Map> escalationTable = new HashMap<>(); recipientsConfig.setEscalationTable(escalationTable); Map clients = new HashMap<>(); @@ -329,7 +328,6 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { notificationRule.setTriggerConfig(triggerConfig); EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(NotificationRuleTriggerType.ALARM); Map> escalationTable = new HashMap<>(); recipientsConfig.setEscalationTable(escalationTable); @@ -640,8 +638,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig(); rule.setTriggerConfig(triggerConfig); - DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); + DefaultNotificationRuleRecipientsConfig recipientsConfig = DefaultNotificationRuleRecipientsConfig.forTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); recipientsConfig.setTargets(List.of(createNotificationTarget(tenantAdminUserId).getUuidId())); rule.setRecipientsConfig(recipientsConfig); rule = saveNotificationRule(rule); @@ -671,8 +668,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { triggerConfig.setCreated(true); rule.setTriggerConfig(triggerConfig); NotificationTarget target = createNotificationTarget(tenantAdminUserId); - DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); + DefaultNotificationRuleRecipientsConfig recipientsConfig = DefaultNotificationRuleRecipientsConfig.forTriggerType(NotificationRuleTriggerType.ENTITY_ACTION); recipientsConfig.setTargets(List.of(target.getUuidId())); rule.setRecipientsConfig(recipientsConfig); rule = saveNotificationRule(rule); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java index 2211ca8bfd..7bb3c367ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java @@ -28,59 +28,124 @@ import java.util.UUID; @Schema(description = "Default notification rule recipients configuration", allOf = NotificationRuleRecipientsConfig.class) @Data @EqualsAndHashCode(callSuper = true) -public class DefaultNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig { +public abstract class DefaultNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig { @NotEmpty private List targets; - @Override - @Schema(hidden = true) - public NotificationRuleTriggerType getTriggerType() { - return super.getTriggerType(); - } - @Override public Map> getTargetsTable() { return Map.of(0, targets); } + public static DefaultNotificationRuleRecipientsConfig forTriggerType(NotificationRuleTriggerType triggerType) { + return switch (triggerType) { + case ENTITY_ACTION -> new EntityActionRecipientsConfig(); + case ALARM_COMMENT -> new AlarmCommentRecipientsConfig(); + case ALARM_ASSIGNMENT -> new AlarmAssignmentRecipientsConfig(); + case DEVICE_ACTIVITY -> new DeviceActivityRecipientsConfig(); + case RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT -> new RuleEngineComponentLifecycleEventRecipientsConfig(); + case EDGE_CONNECTION -> new EdgeConnectionRecipientsConfig(); + case EDGE_COMMUNICATION_FAILURE -> new EdgeCommunicationFailureRecipientsConfig(); + case NEW_PLATFORM_VERSION -> new NewPlatformVersionRecipientsConfig(); + case ENTITIES_LIMIT -> new EntitiesLimitRecipientsConfig(); + case API_USAGE_LIMIT -> new ApiUsageLimitRecipientsConfig(); + case RATE_LIMITS -> new RateLimitsRecipientsConfig(); + case TASK_PROCESSING_FAILURE -> new TaskProcessingFailureRecipientsConfig(); + case RESOURCES_SHORTAGE -> new ResourceShortageRecipientsConfig(); + default -> throw new IllegalArgumentException("Unsupported trigger type for default recipients config: " + triggerType); + }; + } + public static class EntityActionRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ENTITY_ACTION; + } } public static class AlarmCommentRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM_COMMENT; + } } public static class AlarmAssignmentRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM_ASSIGNMENT; + } } public static class DeviceActivityRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.DEVICE_ACTIVITY; + } } public static class RuleEngineComponentLifecycleEventRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RULE_ENGINE_COMPONENT_LIFECYCLE_EVENT; + } } public static class EdgeConnectionRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.EDGE_CONNECTION; + } } public static class EdgeCommunicationFailureRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.EDGE_COMMUNICATION_FAILURE; + } } public static class NewPlatformVersionRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.NEW_PLATFORM_VERSION; + } } public static class EntitiesLimitRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ENTITIES_LIMIT; + } } public static class ApiUsageLimitRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.API_USAGE_LIMIT; + } } public static class RateLimitsRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RATE_LIMITS; + } } public static class TaskProcessingFailureRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.TASK_PROCESSING_FAILURE; + } } public static class ResourceShortageRecipientsConfig extends DefaultNotificationRuleRecipientsConfig { + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.RESOURCES_SHORTAGE; + } } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java index 37837564da..df13e60e93 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java @@ -19,6 +19,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import java.util.List; import java.util.Map; @@ -32,6 +33,11 @@ public class EscalatedNotificationRuleRecipientsConfig extends NotificationRuleR @NotEmpty private Map> escalationTable; + @Override + public NotificationRuleTriggerType getTriggerType() { + return NotificationRuleTriggerType.ALARM; + } + @Override public Map> getTargetsTable() { return escalationTable; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java index 3e44b5086e..ba968841c8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; import lombok.Data; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; @@ -50,7 +49,7 @@ import java.util.UUID; @DiscriminatorMapping(value = "RESOURCES_SHORTAGE", schema = DefaultNotificationRuleRecipientsConfig.ResourceShortageRecipientsConfig.class) }) @JsonIgnoreProperties -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType", visible = true, include = JsonTypeInfo.As.EXISTING_PROPERTY) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType", include = JsonTypeInfo.As.EXISTING_PROPERTY) @JsonSubTypes({ @Type(name = "ALARM", value = EscalatedNotificationRuleRecipientsConfig.class), @Type(name = "ENTITY_ACTION", value = DefaultNotificationRuleRecipientsConfig.EntityActionRecipientsConfig.class), @@ -70,8 +69,7 @@ import java.util.UUID; @Data public abstract class NotificationRuleRecipientsConfig implements Serializable { - @NotNull - private NotificationRuleTriggerType triggerType; + public abstract NotificationRuleTriggerType getTriggerType(); @JsonIgnore public abstract Map> getTargetsTable(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java index 9a3834061c..2373f53283 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java @@ -23,6 +23,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.security.DeviceCredentials; @Schema @@ -31,6 +32,9 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; @Data public class DeviceExportData extends EntityExportData { + @Override + public EntityType getEntityType() { return EntityType.DEVICE; } + @JsonProperty(index = 3) @JsonIgnoreProperties({"id", "deviceId", "createdTime", "version"}) private DeviceCredentials credentials; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java index 2baf5a4369..d7062199d1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java @@ -65,6 +65,7 @@ import java.util.Map; @Type(name = "DEVICE_PROFILE", value = EntityExportData.DeviceProfileExportData.class), @Type(name = "ENTITY_VIEW", value = EntityExportData.EntityViewExportData.class), @Type(name = "NOTIFICATION_TEMPLATE", value = EntityExportData.NotificationTemplateExportData.class), + @Type(name = "NOTIFICATION_TARGET", value = EntityExportData.NotificationTargetExportData.class), @Type(name = "NOTIFICATION_RULE", value = EntityExportData.NotificationRuleExportData.class), @Type(name = "AI_MODEL", value = EntityExportData.AiModelExportData.class) }) @@ -92,7 +93,7 @@ import java.util.Map; } ) @Data -public class EntityExportData> { +public abstract class EntityExportData> { public static final Comparator relationsComparator = Comparator .comparing(EntityRelation::getFrom, Comparator.comparing(EntityId::getId)) @@ -109,9 +110,10 @@ public class EntityExportData> { @JsonTbEntity @Schema(implementation = ExportableEntity.class) private E entity; + @JsonProperty(index = 1) @Schema(requiredMode = Schema.RequiredMode.REQUIRED) - private EntityType entityType; + public abstract EntityType getEntityType(); @JsonProperty(index = 100) @ArraySchema(schema = @Schema(implementation = EntityRelation.class)) @@ -124,6 +126,28 @@ public class EntityExportData> { @ArraySchema(schema = @Schema(implementation = CalculatedField.class)) private List calculatedFields; + public static EntityExportData newInstance(EntityType entityType) { + return switch (entityType) { + case DEVICE -> new DeviceExportData(); + case RULE_CHAIN -> new RuleChainExportData(); + case WIDGET_TYPE -> new WidgetTypeExportData(); + case WIDGETS_BUNDLE -> new WidgetsBundleExportData(); + case OTA_PACKAGE -> new OtaPackageExportData(); + case CUSTOMER -> new CustomerExportData(); + case TB_RESOURCE -> new TbResourceExportData(); + case DASHBOARD -> new DashboardExportData(); + case ASSET_PROFILE -> new AssetProfileExportData(); + case ASSET -> new AssetExportData(); + case DEVICE_PROFILE -> new DeviceProfileExportData(); + case ENTITY_VIEW -> new EntityViewExportData(); + case NOTIFICATION_TEMPLATE -> new NotificationTemplateExportData(); + case NOTIFICATION_TARGET -> new NotificationTargetExportData(); + case NOTIFICATION_RULE -> new NotificationRuleExportData(); + case AI_MODEL -> new AiModelExportData(); + default -> throw new IllegalArgumentException("Unsupported entity type: " + entityType); + }; + } + public EntityExportData sort() { if (relations != null && !relations.isEmpty()) { relations.sort(relationsComparator); @@ -163,26 +187,91 @@ public class EntityExportData> { } @Schema - public static class CustomerExportData extends EntityExportData {} + public static class CustomerExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.CUSTOMER; + } + } + @Schema - public static class TbResourceExportData extends EntityExportData {} + public static class TbResourceExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.TB_RESOURCE; + } + } + @Schema - public static class DashboardExportData extends EntityExportData {} + public static class DashboardExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.DASHBOARD; + } + } + @Schema - public static class AssetProfileExportData extends EntityExportData {} + public static class AssetProfileExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.ASSET_PROFILE; + } + } + @Schema - public static class AssetExportData extends EntityExportData {} + public static class AssetExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.ASSET; + } + } + @Schema - public static class DeviceProfileExportData extends EntityExportData {} + public static class DeviceProfileExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.DEVICE_PROFILE; + } + } + @Schema - public static class EntityViewExportData extends EntityExportData {} + public static class EntityViewExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.ENTITY_VIEW; + } + } + @Schema - public static class NotificationTemplateExportData extends EntityExportData {} + public static class NotificationTemplateExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_TEMPLATE; + } + } + @Schema - public static class NotificationTargetExportData extends EntityExportData {} + public static class NotificationTargetExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_TARGET; + } + } + @Schema - public static class NotificationRuleExportData extends EntityExportData {} + public static class NotificationRuleExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.NOTIFICATION_RULE; + } + } + @Schema - public static class AiModelExportData extends EntityExportData {} + public static class AiModelExportData extends EntityExportData { + @Override + public EntityType getEntityType() { + return EntityType.AI_MODEL; + } + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/OtaPackageExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/OtaPackageExportData.java index 5b10cb6fb8..727c026bc3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/OtaPackageExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/OtaPackageExportData.java @@ -18,12 +18,16 @@ package org.thingsboard.server.common.data.sync.ie; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import io.swagger.v3.oas.annotations.media.Schema; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; @Schema @EqualsAndHashCode(callSuper = true) public class OtaPackageExportData extends EntityExportData { + @Override + public EntityType getEntityType() { return EntityType.OTA_PACKAGE; } + /* * OtaPackage is not a versioned entity; its 'version' field is part of the domain model (not used for optimistic locking) * We override both methods to ensure 'version' is not ignored during (de)serialization. diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java index 9607415797..7b9ec8e0a5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java @@ -21,6 +21,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -30,6 +31,9 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; @Data public class RuleChainExportData extends EntityExportData { + @Override + public EntityType getEntityType() { return EntityType.RULE_CHAIN; } + @JsonProperty(index = 3) @JsonIgnoreProperties({"ruleChainId", "version"}) private RuleChainMetaData metaData; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetTypeExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetTypeExportData.java index b3b1130bea..f69b3603db 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetTypeExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetTypeExportData.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.sync.ie; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; @Schema @@ -25,4 +26,7 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails; @EqualsAndHashCode(callSuper = true) public class WidgetTypeExportData extends EntityExportData { + @Override + public EntityType getEntityType() { return EntityType.WIDGET_TYPE; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java index e725d20404..62140abdd1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java @@ -22,6 +22,7 @@ import io.swagger.v3.oas.annotations.media.ArraySchema; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.widget.WidgetsBundle; import java.util.ArrayList; @@ -32,6 +33,9 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class WidgetsBundleExportData extends EntityExportData { + @Override + public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; } + @ArraySchema(arraySchema = @Schema(description = "List of widgets in the bundle"), schema = @Schema(implementation = JsonNode.class)) @JsonProperty(index = 3) private List widgets; diff --git a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java index b46540aa55..fb8d5eb1d8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java +++ b/dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java @@ -511,12 +511,10 @@ public class DefaultNotifications { rule.setTriggerConfig(defaultRule.getTriggerConfig()); if (rule.getTriggerType() == NotificationRuleTriggerType.ALARM) { EscalatedNotificationRuleRecipientsConfig recipientsConfig = new EscalatedNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(rule.getTriggerType()); recipientsConfig.setEscalationTable(Map.of(0, toUUIDs(List.of(targets)))); rule.setRecipientsConfig(recipientsConfig); } else { - DefaultNotificationRuleRecipientsConfig recipientsConfig = new DefaultNotificationRuleRecipientsConfig(); - recipientsConfig.setTriggerType(rule.getTriggerType()); + DefaultNotificationRuleRecipientsConfig recipientsConfig = DefaultNotificationRuleRecipientsConfig.forTriggerType(rule.getTriggerType()); recipientsConfig.setTargets(toUUIDs(List.of(targets))); rule.setRecipientsConfig(recipientsConfig); } From 12b812ed1be30b2498e18a2ab425572fc86e5724 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 10 Mar 2026 12:02:36 +0200 Subject: [PATCH 02/17] EntityId openspec improvements --- .../server/config/SwaggerConfiguration.java | 9 +++- .../common/data/id/AdminSettingsId.java | 3 +- .../server/common/data/id/AiModelId.java | 2 + .../server/common/data/id/AlarmId.java | 4 +- .../server/common/data/id/ApiKeyId.java | 3 +- .../common/data/id/ApiUsageStateId.java | 4 +- .../server/common/data/id/AssetId.java | 4 +- .../server/common/data/id/AssetProfileId.java | 3 +- .../common/data/id/CalculatedFieldId.java | 4 +- .../server/common/data/id/CustomerId.java | 4 +- .../server/common/data/id/DashboardId.java | 4 +- .../server/common/data/id/DeviceId.java | 4 +- .../common/data/id/DeviceProfileId.java | 3 +- .../server/common/data/id/DomainId.java | 3 ++ .../server/common/data/id/EdgeId.java | 3 +- .../server/common/data/id/EntityId.java | 43 ++++++++++++++++++- .../server/common/data/id/EntityViewId.java | 3 +- .../server/common/data/id/JobId.java | 3 +- .../common/data/id/MobileAppBundleId.java | 3 ++ .../server/common/data/id/MobileAppId.java | 3 ++ .../server/common/data/id/NotificationId.java | 3 +- .../common/data/id/NotificationRequestId.java | 3 +- .../common/data/id/NotificationRuleId.java | 3 +- .../common/data/id/NotificationTargetId.java | 4 +- .../data/id/NotificationTemplateId.java | 3 +- .../server/common/data/id/OAuth2ClientId.java | 3 ++ .../server/common/data/id/OtaPackageId.java | 3 +- .../server/common/data/id/QueueId.java | 3 +- .../server/common/data/id/QueueStatsId.java | 3 +- .../server/common/data/id/RpcId.java | 3 +- .../server/common/data/id/RuleChainId.java | 3 +- .../server/common/data/id/RuleNodeId.java | 3 +- .../server/common/data/id/TbResourceId.java | 3 +- .../server/common/data/id/TenantId.java | 3 +- .../common/data/id/TenantProfileId.java | 3 +- .../server/common/data/id/UserId.java | 4 +- .../server/common/data/id/WidgetTypeId.java | 3 +- .../common/data/id/WidgetsBundleId.java | 3 +- .../common/data/sync/ie/EntityExportData.java | 1 - 39 files changed, 126 insertions(+), 43 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 12a1be5713..8b3bb67eef 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -680,9 +680,14 @@ public class SwaggerConfiguration { // Map backing field names to their JSON property names (respects @JsonProperty) Map fieldToJsonName = new LinkedHashMap<>(); LinkedHashSet getterOnlyNames = new LinkedHashSet<>(); + LinkedHashSet writeOnlyNames = new LinkedHashSet<>(); for (var prop : beanDesc.findProperties()) { if (prop.getField() != null) { - fieldToJsonName.put(prop.getField().getName(), prop.getName()); + if (prop.couldSerialize()) { + fieldToJsonName.put(prop.getField().getName(), prop.getName()); + } else { + writeOnlyNames.add(prop.getName()); + } } else { getterOnlyNames.add(prop.getName()); } @@ -704,6 +709,8 @@ public class SwaggerConfiguration { // Append getter-only properties (no backing field) at the end ordered.addAll(getterOnlyNames); + // Append write-only properties (e.g. deprecated setter-only fields) last + ordered.addAll(writeOnlyNames); return ordered; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java index da7ade45e1..5923b8c02c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public class AdminSettingsId extends UUIDBased implements EntityId { @Serial @@ -33,7 +34,7 @@ public class AdminSettingsId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "ADMIN_SETTINGS", allowableValues = "ADMIN_SETTINGS") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "ADMIN_SETTINGS", allowableValues = "ADMIN_SETTINGS") @Override public EntityType getEntityType() { return EntityType.ADMIN_SETTINGS; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java index dbb8b25635..70476564d5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AiModelId.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public final class AiModelId extends UUIDBased implements EntityId { @Serial @@ -36,6 +37,7 @@ public final class AiModelId extends UUIDBased implements EntityId { @Override @Schema( requiredMode = Schema.RequiredMode.REQUIRED, + accessMode = Schema.AccessMode.READ_ONLY, description = "Entity type of the AI model", example = "AI_MODEL", allowableValues = "AI_MODEL" diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java index 444492c328..db2c63d614 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class AlarmId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -36,7 +36,7 @@ public class AlarmId extends UUIDBased implements EntityId { return new AlarmId(UUID.fromString(alarmId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "ALARM", allowableValues = "ALARM") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "ALARM", allowableValues = "ALARM") @Override public EntityType getEntityType() { return EntityType.ALARM; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiKeyId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiKeyId.java index 514c29665c..186dcaca40 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiKeyId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiKeyId.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public class ApiKeyId extends UUIDBased implements EntityId { @Serial @@ -38,7 +39,7 @@ public class ApiKeyId extends UUIDBased implements EntityId { } @Override - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "API_KEY", allowableValues = "API_KEY") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "API_KEY", allowableValues = "API_KEY") public EntityType getEntityType() { return EntityType.API_KEY; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java index 78e5c90bee..02d6d387a2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class ApiUsageStateId extends UUIDBased implements EntityId { @JsonCreator @@ -34,7 +34,7 @@ public class ApiUsageStateId extends UUIDBased implements EntityId { return new ApiUsageStateId(UUID.fromString(userId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "API_USAGE_STATE", allowableValues = "API_USAGE_STATE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "API_USAGE_STATE", allowableValues = "API_USAGE_STATE") @Override public EntityType getEntityType() { return EntityType.API_USAGE_STATE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java index dc42fffeb5..2d57cc365d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class AssetId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -36,7 +36,7 @@ public class AssetId extends UUIDBased implements EntityId { return new AssetId(UUID.fromString(assetId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "ASSET", allowableValues = "ASSET") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "ASSET", allowableValues = "ASSET") @Override public EntityType getEntityType() { return EntityType.ASSET; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java index f4f7a3de70..981f381a4a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetProfileId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class AssetProfileId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -35,7 +36,7 @@ public class AssetProfileId extends UUIDBased implements EntityId { return new AssetProfileId(UUID.fromString(assetProfileId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "ASSET_PROFILE", allowableValues = "ASSET_PROFILE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "ASSET_PROFILE", allowableValues = "ASSET_PROFILE") @Override public EntityType getEntityType() { return EntityType.ASSET_PROFILE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/CalculatedFieldId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/CalculatedFieldId.java index 5168515cd8..094df0e935 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/CalculatedFieldId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/CalculatedFieldId.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class CalculatedFieldId extends UUIDBased implements EntityId { @Serial @@ -38,7 +38,7 @@ public class CalculatedFieldId extends UUIDBased implements EntityId { return new CalculatedFieldId(UUID.fromString(calculatedFieldId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "CALCULATED_FIELD", allowableValues = "CALCULATED_FIELD") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "CALCULATED_FIELD", allowableValues = "CALCULATED_FIELD") @Override public EntityType getEntityType() { return EntityType.CALCULATED_FIELD; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java index a3b1e28147..4de326f26c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public final class CustomerId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -32,7 +32,7 @@ public final class CustomerId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "CUSTOMER", allowableValues = "CUSTOMER") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "CUSTOMER", allowableValues = "CUSTOMER") @Override public EntityType getEntityType() { return EntityType.CUSTOMER; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java index bd326257a6..c98e70177e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class DashboardId extends UUIDBased implements EntityId { @JsonCreator @@ -34,7 +34,7 @@ public class DashboardId extends UUIDBased implements EntityId { return new DashboardId(UUID.fromString(dashboardId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "DASHBOARD", allowableValues = "DASHBOARD") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "DASHBOARD", allowableValues = "DASHBOARD") @Override public EntityType getEntityType() { return EntityType.DASHBOARD; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java index 7dd840815b..a7b9c12e19 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class DeviceId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -37,7 +37,7 @@ public class DeviceId extends UUIDBased implements EntityId { } @Override - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "DEVICE", allowableValues = "DEVICE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "DEVICE", allowableValues = "DEVICE") public EntityType getEntityType() { return EntityType.DEVICE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java index 2adba3640c..dea0dd2a65 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class DeviceProfileId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -35,7 +36,7 @@ public class DeviceProfileId extends UUIDBased implements EntityId { return new DeviceProfileId(UUID.fromString(deviceProfileId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "DEVICE_PROFILE", allowableValues = "DEVICE_PROFILE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "DEVICE_PROFILE", allowableValues = "DEVICE_PROFILE") @Override public EntityType getEntityType() { return EntityType.DEVICE_PROFILE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java index 0768eec45e..223ea8d8b5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/DomainId.java @@ -17,10 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class DomainId extends UUIDBased implements EntityId { @JsonCreator @@ -32,6 +34,7 @@ public class DomainId extends UUIDBased implements EntityId { return new DomainId(UUID.fromString(oauth2DomainId)); } + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "DOMAIN", allowableValues = "DOMAIN") @Override public EntityType getEntityType() { return EntityType.DOMAIN; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java index 05019a608f..45a95d6632 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public class EdgeId extends UUIDBased implements EntityId { @Serial @@ -43,7 +44,7 @@ public class EdgeId extends UUIDBased implements EntityId { return new EdgeId(UUID.fromString(edgeId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "EDGE", allowableValues = "EDGE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "EDGE", allowableValues = "EDGE") @Override public EntityType getEntityType() { return EntityType.EDGE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java index c57dc79514..c96b4c38d5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; @@ -26,7 +27,47 @@ import java.util.UUID; @JsonDeserialize(using = EntityIdDeserializer.class) @JsonSerialize(using = EntityIdSerializer.class) -@Schema +@Schema( + discriminatorProperty = "entityType", + discriminatorMapping = { + @DiscriminatorMapping(value = "ADMIN_SETTINGS", schema = AdminSettingsId.class), + @DiscriminatorMapping(value = "AI_MODEL", schema = AiModelId.class), + @DiscriminatorMapping(value = "ALARM", schema = AlarmId.class), + @DiscriminatorMapping(value = "API_KEY", schema = ApiKeyId.class), + @DiscriminatorMapping(value = "API_USAGE_STATE", schema = ApiUsageStateId.class), + @DiscriminatorMapping(value = "ASSET", schema = AssetId.class), + @DiscriminatorMapping(value = "ASSET_PROFILE", schema = AssetProfileId.class), + @DiscriminatorMapping(value = "CALCULATED_FIELD", schema = CalculatedFieldId.class), + @DiscriminatorMapping(value = "CUSTOMER", schema = CustomerId.class), + @DiscriminatorMapping(value = "DASHBOARD", schema = DashboardId.class), + @DiscriminatorMapping(value = "DEVICE", schema = DeviceId.class), + @DiscriminatorMapping(value = "DEVICE_PROFILE", schema = DeviceProfileId.class), + @DiscriminatorMapping(value = "DOMAIN", schema = DomainId.class), + @DiscriminatorMapping(value = "EDGE", schema = EdgeId.class), + @DiscriminatorMapping(value = "ENTITY_VIEW", schema = EntityViewId.class), + @DiscriminatorMapping(value = "JOB", schema = JobId.class), + @DiscriminatorMapping(value = "MOBILE_APP", schema = MobileAppId.class), + @DiscriminatorMapping(value = "MOBILE_APP_BUNDLE", schema = MobileAppBundleId.class), + @DiscriminatorMapping(value = "NOTIFICATION", schema = NotificationId.class), + @DiscriminatorMapping(value = "NOTIFICATION_REQUEST", schema = NotificationRequestId.class), + @DiscriminatorMapping(value = "NOTIFICATION_RULE", schema = NotificationRuleId.class), + @DiscriminatorMapping(value = "NOTIFICATION_TARGET", schema = NotificationTargetId.class), + @DiscriminatorMapping(value = "NOTIFICATION_TEMPLATE", schema = NotificationTemplateId.class), + @DiscriminatorMapping(value = "OAUTH2_CLIENT", schema = OAuth2ClientId.class), + @DiscriminatorMapping(value = "OTA_PACKAGE", schema = OtaPackageId.class), + @DiscriminatorMapping(value = "QUEUE", schema = QueueId.class), + @DiscriminatorMapping(value = "QUEUE_STATS", schema = QueueStatsId.class), + @DiscriminatorMapping(value = "RPC", schema = RpcId.class), + @DiscriminatorMapping(value = "RULE_CHAIN", schema = RuleChainId.class), + @DiscriminatorMapping(value = "RULE_NODE", schema = RuleNodeId.class), + @DiscriminatorMapping(value = "TB_RESOURCE", schema = TbResourceId.class), + @DiscriminatorMapping(value = "TENANT", schema = TenantId.class), + @DiscriminatorMapping(value = "TENANT_PROFILE", schema = TenantProfileId.class), + @DiscriminatorMapping(value = "USER", schema = UserId.class), + @DiscriminatorMapping(value = "WIDGETS_BUNDLE", schema = WidgetsBundleId.class), + @DiscriminatorMapping(value = "WIDGET_TYPE", schema = WidgetTypeId.class) + } +) public interface EntityId extends HasUUID, Serializable { //NOSONAR, the constant is closely related to EntityId UUID NULL_UUID = UUID.fromString("13814000-1dd2-11b2-8080-808080808080"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java index cd5b0164db..8bd357a4fc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java @@ -25,6 +25,7 @@ import java.util.UUID; /** * Created by Victor Basanets on 8/27/2017. */ +@Schema(allOf = EntityId.class) public class EntityViewId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -38,7 +39,7 @@ public class EntityViewId extends UUIDBased implements EntityId { return new EntityViewId(UUID.fromString(entityViewID)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "ENTITY_VIEW", allowableValues = "ENTITY_VIEW") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "ENTITY_VIEW", allowableValues = "ENTITY_VIEW") @Override public EntityType getEntityType() { return EntityType.ENTITY_VIEW; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/JobId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/JobId.java index 51e6b8e539..2d369e8b71 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/JobId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/JobId.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public class JobId extends UUIDBased implements EntityId { @Serial @@ -33,7 +34,7 @@ public class JobId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "JOB", allowableValues = "JOB") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "JOB", allowableValues = "JOB") @Override public EntityType getEntityType() { return EntityType.JOB; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java index 6a9c11207a..cfd0fd109f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppBundleId.java @@ -17,10 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class MobileAppBundleId extends UUIDBased implements EntityId{ @JsonCreator @@ -32,6 +34,7 @@ public class MobileAppBundleId extends UUIDBased implements EntityId{ return new MobileAppBundleId(UUID.fromString(mobileAppId)); } + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "MOBILE_APP_BUNDLE", allowableValues = "MOBILE_APP_BUNDLE") @Override public EntityType getEntityType() { return EntityType.MOBILE_APP_BUNDLE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java index f19e52cc46..bf3571bf7e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/MobileAppId.java @@ -17,10 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class MobileAppId extends UUIDBased implements EntityId{ @JsonCreator @@ -32,6 +34,7 @@ public class MobileAppId extends UUIDBased implements EntityId{ return new MobileAppId(UUID.fromString(mobileAppId)); } + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "MOBILE_APP", allowableValues = "MOBILE_APP") @Override public EntityType getEntityType() { return EntityType.MOBILE_APP; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java index 3ba6bf4ca4..a702648a52 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class NotificationId extends UUIDBased implements EntityId { @JsonCreator @@ -29,7 +30,7 @@ public class NotificationId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "NOTIFICATION", allowableValues = "NOTIFICATION") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "NOTIFICATION", allowableValues = "NOTIFICATION") @Override public EntityType getEntityType() { return EntityType.NOTIFICATION; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java index 5ff42bc0e2..3caa5f1f8c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRequestId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class NotificationRequestId extends UUIDBased implements EntityId { @JsonCreator @@ -29,7 +30,7 @@ public class NotificationRequestId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "NOTIFICATION_REQUEST", allowableValues = "NOTIFICATION_REQUEST") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "NOTIFICATION_REQUEST", allowableValues = "NOTIFICATION_REQUEST") @Override public EntityType getEntityType() { return EntityType.NOTIFICATION_REQUEST; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java index ff57e4d252..39e2ccf54c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationRuleId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class NotificationRuleId extends UUIDBased implements EntityId { @JsonCreator @@ -29,7 +30,7 @@ public class NotificationRuleId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "NOTIFICATION_RULE", allowableValues = "NOTIFICATION_RULE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "NOTIFICATION_RULE", allowableValues = "NOTIFICATION_RULE") @Override public EntityType getEntityType() { return EntityType.NOTIFICATION_RULE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java index 9287de6248..593ce1c1aa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTargetId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class NotificationTargetId extends UUIDBased implements EntityId { @JsonCreator @@ -30,7 +30,7 @@ public class NotificationTargetId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "NOTIFICATION_TARGET", allowableValues = "NOTIFICATION_TARGET") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "NOTIFICATION_TARGET", allowableValues = "NOTIFICATION_TARGET") @Override public EntityType getEntityType() { return EntityType.NOTIFICATION_TARGET; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java index ae2ea582fd..5a1635044a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/NotificationTemplateId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class NotificationTemplateId extends UUIDBased implements EntityId { @JsonCreator @@ -29,7 +30,7 @@ public class NotificationTemplateId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "NOTIFICATION_TEMPLATE", allowableValues = "NOTIFICATION_TEMPLATE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "NOTIFICATION_TEMPLATE", allowableValues = "NOTIFICATION_TEMPLATE") @Override public EntityType getEntityType() { return EntityType.NOTIFICATION_TEMPLATE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java index 9ef5871135..491643b921 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientId.java @@ -17,10 +17,12 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class OAuth2ClientId extends UUIDBased implements EntityId { @JsonCreator @@ -32,6 +34,7 @@ public class OAuth2ClientId extends UUIDBased implements EntityId { return new OAuth2ClientId(UUID.fromString(oauth2ClientId)); } + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "OAUTH2_CLIENT", allowableValues = "OAUTH2_CLIENT") @Override public EntityType getEntityType() { return EntityType.OAUTH2_CLIENT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java index 0261b2973e..f9f508f13f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public class OtaPackageId extends UUIDBased implements EntityId { @Serial @@ -37,7 +38,7 @@ public class OtaPackageId extends UUIDBased implements EntityId { return new OtaPackageId(UUID.fromString(firmwareId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "OTA_PACKAGE", allowableValues = "OTA_PACKAGE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "OTA_PACKAGE", allowableValues = "OTA_PACKAGE") @Override public EntityType getEntityType() { return EntityType.OTA_PACKAGE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueId.java index c7ee46da2a..3c00c08bc1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class QueueId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -35,7 +36,7 @@ public class QueueId extends UUIDBased implements EntityId { return new QueueId(UUID.fromString(queueId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "QUEUE", allowableValues = "QUEUE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "QUEUE", allowableValues = "QUEUE") @Override public EntityType getEntityType() { return EntityType.QUEUE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java index 7a921e8755..7869b7bbfe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/QueueStatsId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class QueueStatsId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -35,7 +36,7 @@ public class QueueStatsId extends UUIDBased implements EntityId { return new QueueStatsId(UUID.fromString(queueId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "QUEUE_STATS", allowableValues = "QUEUE_STATS") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "QUEUE_STATS", allowableValues = "QUEUE_STATS") @Override public EntityType getEntityType() { return EntityType.QUEUE_STATS; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java index 559855e194..a03b0138d6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public final class RpcId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -31,7 +32,7 @@ public final class RpcId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "RPC", allowableValues = "RPC") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "RPC", allowableValues = "RPC") @Override public EntityType getEntityType() { return EntityType.RPC; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java index 0512f518af..c77cec808e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class RuleChainId extends UUIDBased implements EntityId { @JsonCreator @@ -29,7 +30,7 @@ public class RuleChainId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "RULE_CHAIN", allowableValues = "RULE_CHAIN") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "RULE_CHAIN", allowableValues = "RULE_CHAIN") @Override public EntityType getEntityType() { return EntityType.RULE_CHAIN; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java index 645acfe5e2..6be3371969 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class RuleNodeId extends UUIDBased implements EntityId { @JsonCreator @@ -29,7 +30,7 @@ public class RuleNodeId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "RULE_NODE", allowableValues = "RULE_NODE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "RULE_NODE", allowableValues = "RULE_NODE") @Override public EntityType getEntityType() { return EntityType.RULE_NODE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java index cf171855b2..b08dcb56b4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class TbResourceId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -31,7 +32,7 @@ public class TbResourceId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "TB_RESOURCE", allowableValues = "TB_RESOURCE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "TB_RESOURCE", allowableValues = "TB_RESOURCE") @Override public EntityType getEntityType() { return EntityType.TB_RESOURCE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java index f219452c07..47be35fb84 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityType; import java.io.Serial; import java.util.UUID; +@Schema(allOf = EntityId.class) public final class TenantId extends UUIDBased implements EntityId { @JsonIgnore @@ -54,7 +55,7 @@ public final class TenantId extends UUIDBased implements EntityId { return this.equals(SYS_TENANT_ID); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "TENANT", allowableValues = "TENANT") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "TENANT", allowableValues = "TENANT") @Override public EntityType getEntityType() { return EntityType.TENANT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java index 5b46b772ba..7ced9faa43 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public class TenantProfileId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -35,7 +36,7 @@ public class TenantProfileId extends UUIDBased implements EntityId { return new TenantProfileId(UUID.fromString(tenantProfileId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "TENANT_PROFILE", allowableValues = "TENANT_PROFILE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "TENANT_PROFILE", allowableValues = "TENANT_PROFILE") @Override public EntityType getEntityType() { return EntityType.TENANT_PROFILE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java index d4f113bef5..0bb939d958 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -@Schema +@Schema(allOf = EntityId.class) public class UserId extends UUIDBased implements EntityId { @JsonCreator @@ -34,7 +34,7 @@ public class UserId extends UUIDBased implements EntityId { return new UserId(UUID.fromString(userId)); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "USER", allowableValues = "USER") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "USER", allowableValues = "USER") @Override public EntityType getEntityType() { return EntityType.USER; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java index fe8b40a7b9..cd9dcec8b3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public final class WidgetTypeId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -31,7 +32,7 @@ public final class WidgetTypeId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "WIDGET_TYPE", allowableValues = "WIDGET_TYPE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "WIDGET_TYPE", allowableValues = "WIDGET_TYPE") @Override public EntityType getEntityType() { return EntityType.WIDGET_TYPE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java index f88d81bf74..295ac8c59d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; +@Schema(allOf = EntityId.class) public final class WidgetsBundleId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @@ -31,7 +32,7 @@ public final class WidgetsBundleId extends UUIDBased implements EntityId { super(id); } - @Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "WIDGETS_BUNDLE", allowableValues = "WIDGETS_BUNDLE") + @Schema(requiredMode = Schema.RequiredMode.REQUIRED, accessMode = Schema.AccessMode.READ_ONLY, description = "string", example = "WIDGETS_BUNDLE", allowableValues = "WIDGETS_BUNDLE") @Override public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java index d7062199d1..3e04074236 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java @@ -110,7 +110,6 @@ public abstract class EntityExportData Date: Tue, 10 Mar 2026 15:52:56 +0200 Subject: [PATCH 03/17] NotificationRuleRecipientsConfig refactoring, added dedulication logic so that fields not being dulicated in children --- .../server/config/SwaggerConfiguration.java | 54 +++++++++++++++++++ ...faultNotificationRuleRecipientsConfig.java | 4 +- ...latedNotificationRuleRecipientsConfig.java | 4 +- .../NotificationRuleRecipientsConfig.java | 10 ++-- 4 files changed, 61 insertions(+), 11 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 8b3bb67eef..fadf26abea 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -76,6 +76,7 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; @@ -406,6 +407,10 @@ public class SwaggerConfiguration { } }); + // Deduplicate allOf child schemas: remove properties that are already defined + // in the referenced parent schema to avoid duplication (e.g. EntityId children) + schemas.values().forEach(schema -> deduplicateAllOfProperties(schema, schemas)); + // Fix polymorphic request/response bodies: replace inline oneOf with base type $ref paths.values().stream() .flatMap(pathItem -> pathItem.readOperationsMap().values().stream()) @@ -676,6 +681,55 @@ public class SwaggerConfiguration { return new ApiResponse().description(description).content(content); } + @SuppressWarnings("unchecked") + private void deduplicateAllOfProperties(Schema schema, Map allSchemas) { + if (schema.getAllOf() == null) { + return; + } + + // Collect properties defined in any $ref'd parent within the allOf + Set parentProperties = new LinkedHashSet<>(); + for (Schema allOfElement : schema.getAllOf()) { + String ref = allOfElement.get$ref(); + if (ref != null) { + String refName = ref.substring(ref.lastIndexOf('/') + 1); + Schema parent = allSchemas.get(refName); + if (parent != null && parent.getProperties() != null) { + parentProperties.addAll(parent.getProperties().keySet()); + } + } + } + + if (parentProperties.isEmpty()) { + return; + } + + // Strip those properties from inline (non-$ref) allOf elements + schema.getAllOf().removeIf(allOfElement -> { + if (allOfElement.get$ref() != null) { + return false; + } + if (allOfElement.getProperties() != null) { + parentProperties.forEach(prop -> allOfElement.getProperties().remove(prop)); + if (allOfElement.getProperties().isEmpty()) { + allOfElement.setProperties(null); + } + } + // Remove the inline element entirely if it has nothing left + return allOfElement.getProperties() == null + && allOfElement.getRequired() == null + && allOfElement.getType() == null; + }); + + // Remove required entries at the schema level that are already required by the parent + if (schema.getRequired() != null) { + schema.getRequired().removeAll(parentProperties); + if (schema.getRequired().isEmpty()) { + schema.setRequired(null); + } + } + } + private static List resolvePropertyOrder(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { // Map backing field names to their JSON property names (respects @JsonProperty) Map fieldToJsonName = new LinkedHashMap<>(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java index 7bb3c367ec..ba4f8c9c56 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java @@ -27,8 +27,8 @@ import java.util.UUID; @Schema(description = "Default notification rule recipients configuration", allOf = NotificationRuleRecipientsConfig.class) @Data -@EqualsAndHashCode(callSuper = true) -public abstract class DefaultNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig { +@EqualsAndHashCode +public abstract class DefaultNotificationRuleRecipientsConfig implements NotificationRuleRecipientsConfig { @NotEmpty private List targets; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java index df13e60e93..fa5a10f983 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/EscalatedNotificationRuleRecipientsConfig.java @@ -27,8 +27,8 @@ import java.util.UUID; @Schema(description = "Escalated notification rule recipients configuration") @Data -@EqualsAndHashCode(callSuper = true) -public class EscalatedNotificationRuleRecipientsConfig extends NotificationRuleRecipientsConfig { +@EqualsAndHashCode +public class EscalatedNotificationRuleRecipientsConfig implements NotificationRuleRecipientsConfig { @NotEmpty private Map> escalationTable; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java index ba968841c8..0b383a6d22 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/NotificationRuleRecipientsConfig.java @@ -16,13 +16,11 @@ package org.thingsboard.server.common.data.notification.rule; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerType; import java.io.Serializable; @@ -48,7 +46,6 @@ import java.util.UUID; @DiscriminatorMapping(value = "TASK_PROCESSING_FAILURE", schema = DefaultNotificationRuleRecipientsConfig.TaskProcessingFailureRecipientsConfig.class), @DiscriminatorMapping(value = "RESOURCES_SHORTAGE", schema = DefaultNotificationRuleRecipientsConfig.ResourceShortageRecipientsConfig.class) }) -@JsonIgnoreProperties @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "triggerType", include = JsonTypeInfo.As.EXISTING_PROPERTY) @JsonSubTypes({ @Type(name = "ALARM", value = EscalatedNotificationRuleRecipientsConfig.class), @@ -66,12 +63,11 @@ import java.util.UUID; @Type(name = "TASK_PROCESSING_FAILURE", value = DefaultNotificationRuleRecipientsConfig.TaskProcessingFailureRecipientsConfig.class), @Type(name = "RESOURCES_SHORTAGE", value = DefaultNotificationRuleRecipientsConfig.ResourceShortageRecipientsConfig.class) }) -@Data -public abstract class NotificationRuleRecipientsConfig implements Serializable { +public interface NotificationRuleRecipientsConfig extends Serializable { - public abstract NotificationRuleTriggerType getTriggerType(); + NotificationRuleTriggerType getTriggerType(); @JsonIgnore - public abstract Map> getTargetsTable(); + Map> getTargetsTable(); } From eaf69d1aa9908b720ddb7d5387dba155bce1b5af Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 11:01:18 +0200 Subject: [PATCH 04/17] fixed schema fields order --- .../server/config/SwaggerConfiguration.java | 167 ++++++++++++++---- 1 file changed, 135 insertions(+), 32 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index fadf26abea..10742290ac 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -78,6 +78,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.TreeSet; import java.util.stream.Collectors; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -305,23 +306,58 @@ public class SwaggerConfiguration { schema.setProperties(null); } } - } else if (schema != null && schema.getProperties() != null && !schema.getProperties().isEmpty()) { - try { - var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); - var orderedNames = resolvePropertyOrder(cls, beanDesc); - if (!orderedNames.isEmpty()) { - @SuppressWarnings("unchecked") - Map current = schema.getProperties(); - var reordered = new LinkedHashMap(); - for (String name : orderedNames) { - Schema prop = current.get(name); - if (prop != null) reordered.put(name, prop); + } else if (schema != null) { + boolean hasAllOf = schema.getAllOf() != null; + boolean hasProps = schema.getProperties() != null && !schema.getProperties().isEmpty(); + if (hasAllOf || hasProps) { + try { + var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); + var orderedNames = resolvePropertyOrder(cls, beanDesc); + // Reorder top-level properties if present. + // When orderedNames is empty (e.g. for interfaces where Jackson + // returns no properties from beanDesc), fall through to the + // TreeMap fallback which sorts remaining properties alphabetically. + if (hasProps) { + @SuppressWarnings("unchecked") + Map current = schema.getProperties(); + var reordered = new LinkedHashMap(); + for (String name : orderedNames) { + Schema prop = current.get(name); + if (prop != null) reordered.put(name, prop); + } + // Any properties not covered by orderedNames are appended + // alphabetically to guarantee a deterministic stable order. + new TreeMap<>(current).forEach((k, v) -> reordered.putIfAbsent(k, v)); + schema.setProperties(reordered); } - current.forEach((k, v) -> reordered.putIfAbsent(k, v)); - schema.setProperties(reordered); + // Also reorder properties inside allOf inline elements, and mark + // which properties are declared in cls itself (not inherited from + // a superclass) so deduplicateAllOfProperties can strip inherited + // ones without touching own-class properties. + if (hasAllOf) { + Set ownProps = computeOwnPropNames(cls, beanDesc); + if (!ownProps.isEmpty()) { + schema.addExtension("x-tb-own-props", List.copyOf(ownProps)); + } + @SuppressWarnings("unchecked") + List allOfList = schema.getAllOf(); + for (Schema allOfElement : allOfList) { + if (allOfElement.get$ref() != null) continue; + @SuppressWarnings("unchecked") + Map inlineProps = allOfElement.getProperties(); + if (inlineProps == null || inlineProps.isEmpty()) continue; + var reordered = new LinkedHashMap(); + for (String name : orderedNames) { + Schema prop = inlineProps.get(name); + if (prop != null) reordered.put(name, prop); + } + new TreeMap<>(inlineProps).forEach((k, v) -> reordered.putIfAbsent(k, v)); + allOfElement.setProperties(reordered); + } + } + } catch (Exception ignored) { + log.trace("Failed to resolve property order for {}", cls.getName(), ignored); } - } catch (Exception ignored) { - log.trace("Failed to resolve property order for {}", cls.getName(), ignored); } } } @@ -411,6 +447,16 @@ public class SwaggerConfiguration { // in the referenced parent schema to avoid duplication (e.g. EntityId children) schemas.values().forEach(schema -> deduplicateAllOfProperties(schema, schemas)); + // Clean up internal marker extension used by deduplicateAllOfProperties + schemas.values().forEach(schema -> { + if (schema.getExtensions() != null) { + schema.getExtensions().remove("x-tb-own-props"); + if (schema.getExtensions().isEmpty()) { + schema.setExtensions(null); + } + } + }); + // Fix polymorphic request/response bodies: replace inline oneOf with base type $ref paths.values().stream() .flatMap(pathItem -> pathItem.readOperationsMap().values().stream()) @@ -687,6 +733,17 @@ public class SwaggerConfiguration { return; } + // Properties declared in the class's own fields (not inherited from a superclass). + // These must NOT be stripped from the inline even if they also appear in a parent + // schema (e.g. a field that also has a corresponding interface getter in the parent). + Set ownProps = new LinkedHashSet<>(); + if (schema.getExtensions() != null + && schema.getExtensions().get("x-tb-own-props") instanceof List list) { + for (Object v : list) { + if (v instanceof String s) ownProps.add(s); + } + } + // Collect properties defined in any $ref'd parent within the allOf Set parentProperties = new LinkedHashSet<>(); for (Schema allOfElement : schema.getAllOf()) { @@ -704,45 +761,70 @@ public class SwaggerConfiguration { return; } - // Strip those properties from inline (non-$ref) allOf elements + // Properties to strip: in parent schema AND not declared as own-class fields. + // This removes inherited properties (from superclasses or pure interface getters) + // while keeping properties the class declares as its own fields. + Set toStrip = new LinkedHashSet<>(parentProperties); + toStrip.removeAll(ownProps); + + if (toStrip.isEmpty()) { + return; + } + + // Strip from inline (non-$ref) allOf elements schema.getAllOf().removeIf(allOfElement -> { if (allOfElement.get$ref() != null) { return false; } if (allOfElement.getProperties() != null) { - parentProperties.forEach(prop -> allOfElement.getProperties().remove(prop)); + allOfElement.getProperties().keySet().removeAll(toStrip); if (allOfElement.getProperties().isEmpty()) { allOfElement.setProperties(null); } } - // Remove the inline element entirely if it has nothing left return allOfElement.getProperties() == null && allOfElement.getRequired() == null && allOfElement.getType() == null; }); - // Remove required entries at the schema level that are already required by the parent + // Remove stripped properties from the schema's required list if (schema.getRequired() != null) { - schema.getRequired().removeAll(parentProperties); + schema.getRequired().removeAll(toStrip); if (schema.getRequired().isEmpty()) { schema.setRequired(null); } } } + /** + * Returns the JSON property names that are backed by fields declared directly in {@code cls} + * (not inherited from a superclass). Used to distinguish "own" from "inherited" properties + * when deduplicating allOf inline elements. + */ + private static Set computeOwnPropNames(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { + Map allFieldToJson = new LinkedHashMap<>(); + for (var prop : beanDesc.findProperties()) { + if (prop.getField() != null && prop.couldSerialize()) { + allFieldToJson.put(prop.getField().getName(), prop.getName()); + } + } + Set own = new LinkedHashSet<>(); + for (Field f : cls.getDeclaredFields()) { + if (Modifier.isStatic(f.getModifiers())) continue; + String jsonName = allFieldToJson.get(f.getName()); + if (jsonName != null) own.add(jsonName); + } + return own; + } + private static List resolvePropertyOrder(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { // Map backing field names to their JSON property names (respects @JsonProperty) Map fieldToJsonName = new LinkedHashMap<>(); LinkedHashSet getterOnlyNames = new LinkedHashSet<>(); - LinkedHashSet writeOnlyNames = new LinkedHashSet<>(); for (var prop : beanDesc.findProperties()) { - if (prop.getField() != null) { - if (prop.couldSerialize()) { - fieldToJsonName.put(prop.getField().getName(), prop.getName()); - } else { - writeOnlyNames.add(prop.getName()); - } - } else { + if (prop.getField() != null && prop.couldSerialize()) { + fieldToJsonName.put(prop.getField().getName(), prop.getName()); + } else if (prop.getField() == null) { getterOnlyNames.add(prop.getName()); } } @@ -757,14 +839,35 @@ public class SwaggerConfiguration { for (Field f : c.getDeclaredFields()) { if (Modifier.isStatic(f.getModifiers())) continue; String jsonName = fieldToJsonName.get(f.getName()); + // Handle boolean fields with "is" prefix (e.g. field "isEnabled" → JSON "enabled"). + // Jackson derives the JSON property name from the public getter, not the field name. + // Depending on whether Jackson merges the field+getter or not, there are three cases: + // 1. jsonName == null: field invisible, getter-only property in getterOnlyNames + // 2. jsonName == fieldName: Jackson did NOT merge; field → "isEnabled" property, + // getter → "enabled" property separately in getterOnlyNames + // 3. jsonName == strippedName: Jackson merged field+getter → already correct + // Cases 1 and 2 both need to pull the stripped name from getterOnlyNames so that the + // declaration order is preserved instead of leaving it to the alphabetical fallback. + if (jsonName == null || jsonName.equals(f.getName())) { + String fn = f.getName(); + if ((f.getType() == boolean.class || f.getType() == Boolean.class) + && fn.startsWith("is") && fn.length() > 2 && Character.isUpperCase(fn.charAt(2))) { + String stripped = Character.toLowerCase(fn.charAt(2)) + fn.substring(3); + if (getterOnlyNames.remove(stripped)) { + jsonName = stripped; + } + } + } if (jsonName != null) ordered.add(jsonName); } } - // Append getter-only properties (no backing field) at the end - ordered.addAll(getterOnlyNames); - // Append write-only properties (e.g. deprecated setter-only fields) last - ordered.addAll(writeOnlyNames); + // Return only field-backed properties in declaration order. + // Getter-only properties (no backing field) are intentionally excluded: their set can vary + // between restarts (e.g. Optional-typed getters depend on Jackson module registration order), + // so including them here would make their position non-deterministic when some are in orderedNames + // and others are only in the schema map. The converter's TreeMap fallback handles ALL + // non-field-backed properties together in one alphabetical pass, guaranteeing stable order. return ordered; } From 77f5317610d634cd616ad32bd40683526fa1167b Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 11:29:47 +0200 Subject: [PATCH 05/17] minor refactoring --- .../org/thingsboard/server/config/SwaggerConfiguration.java | 1 - .../rule/DefaultNotificationRuleRecipientsConfig.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 10742290ac..b8ec75679e 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -78,7 +78,6 @@ import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; import java.util.stream.Collectors; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java index ba4f8c9c56..bff983f2f4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/notification/rule/DefaultNotificationRuleRecipientsConfig.java @@ -25,7 +25,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; -@Schema(description = "Default notification rule recipients configuration", allOf = NotificationRuleRecipientsConfig.class) +@Schema(description = "Default notification rule recipients configuration") @Data @EqualsAndHashCode public abstract class DefaultNotificationRuleRecipientsConfig implements NotificationRuleRecipientsConfig { From 0fa97af9226197e92da9f49fef25d6c2c0e2525d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 14:26:06 +0200 Subject: [PATCH 06/17] fixed FeaturesInfo class, added ability to manually set json property order --- .../server/config/SwaggerConfiguration.java | 17 +++++++++++++++++ .../server/common/data/FeaturesInfo.java | 10 +++++----- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index b8ec75679e..f2d867ddb6 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.config; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -817,6 +818,22 @@ public class SwaggerConfiguration { } private static List resolvePropertyOrder(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { + // If an explicit @JsonPropertyOrder is present on the class or any directly implemented + // interface, honour it directly. Walk up the hierarchy so annotations on superclasses + // and their interfaces are also found. + for (Class c = cls; c != null && c != Object.class; c = c.getSuperclass()) { + JsonPropertyOrder propOrder = c.getAnnotation(JsonPropertyOrder.class); + if (propOrder != null && !propOrder.alphabetic() && propOrder.value().length > 0) { + return Arrays.asList(propOrder.value()); + } + for (Class iface : c.getInterfaces()) { + propOrder = iface.getAnnotation(JsonPropertyOrder.class); + if (propOrder != null && !propOrder.alphabetic() && propOrder.value().length > 0) { + return Arrays.asList(propOrder.value()); + } + } + } + // Map backing field names to their JSON property names (respects @JsonProperty) Map fieldToJsonName = new LinkedHashMap<>(); LinkedHashSet getterOnlyNames = new LinkedHashSet<>(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java index 2a3b95ba0c..49d0228256 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java @@ -19,9 +19,9 @@ import lombok.Data; @Data public class FeaturesInfo { - boolean isEmailEnabled; - boolean isSmsEnabled; - boolean isNotificationEnabled; - boolean isOauthEnabled; - boolean isTwoFaEnabled; + boolean emailEnabled; + boolean smsEnabled; + boolean notificationEnabled; + boolean oauthEnabled; + boolean twoFaEnabled; } From 0d0c29255a52a862d7b66dfbf052bb5aa238cbe2 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 14:52:07 +0200 Subject: [PATCH 07/17] fixed resolvePropertyOrder to walk interface ancestry --- .../server/config/SwaggerConfiguration.java | 68 ++++++++++++++----- .../impl/DefaultEntityExportService.java | 1 + 2 files changed, 52 insertions(+), 17 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index f2d867ddb6..b2d160f5f3 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -69,9 +69,11 @@ import org.thingsboard.server.service.security.auth.rest.LoginResponse; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.Deque; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -727,6 +729,37 @@ public class SwaggerConfiguration { return new ApiResponse().description(description).content(content); } + /** + * Recursively collects all property names reachable from {@code schemaName}, walking the + * ancestor chain through allOf $ref entries (to handle multi-level inheritance). + * {@code visited} prevents infinite loops in case of circular references. + */ + @SuppressWarnings("unchecked") + private void collectAllProperties(String schemaName, Map allSchemas, + Set result, Set visited) { + if (!visited.add(schemaName)) { + return; + } + Schema schema = allSchemas.get(schemaName); + if (schema == null) { + return; + } + if (schema.getProperties() != null) { + result.addAll(schema.getProperties().keySet()); + } + if (schema.getAllOf() != null) { + for (Schema allOfElement : schema.getAllOf()) { + String ref = allOfElement.get$ref(); + if (ref != null) { + String refName = ref.substring(ref.lastIndexOf('/') + 1); + collectAllProperties(refName, allSchemas, result, visited); + } else if (allOfElement.getProperties() != null) { + result.addAll(allOfElement.getProperties().keySet()); + } + } + } + } + @SuppressWarnings("unchecked") private void deduplicateAllOfProperties(Schema schema, Map allSchemas) { if (schema.getAllOf() == null) { @@ -744,16 +777,14 @@ public class SwaggerConfiguration { } } - // Collect properties defined in any $ref'd parent within the allOf + // Collect properties defined in any $ref'd parent within the allOf, recursively + // walking the ancestor chain (each parent may itself use allOf to extend a grandparent). Set parentProperties = new LinkedHashSet<>(); for (Schema allOfElement : schema.getAllOf()) { String ref = allOfElement.get$ref(); if (ref != null) { String refName = ref.substring(ref.lastIndexOf('/') + 1); - Schema parent = allSchemas.get(refName); - if (parent != null && parent.getProperties() != null) { - parentProperties.addAll(parent.getProperties().keySet()); - } + collectAllProperties(refName, allSchemas, parentProperties, new LinkedHashSet<>()); } } @@ -777,10 +808,9 @@ public class SwaggerConfiguration { return false; } if (allOfElement.getProperties() != null) { - allOfElement.getProperties().keySet().removeAll(toStrip); - if (allOfElement.getProperties().isEmpty()) { - allOfElement.setProperties(null); - } + Map filtered = new LinkedHashMap<>(allOfElement.getProperties()); + filtered.keySet().removeAll(toStrip); + allOfElement.setProperties(filtered.isEmpty() ? null : filtered); } return allOfElement.getProperties() == null && allOfElement.getRequired() == null @@ -789,10 +819,9 @@ public class SwaggerConfiguration { // Remove stripped properties from the schema's required list if (schema.getRequired() != null) { - schema.getRequired().removeAll(toStrip); - if (schema.getRequired().isEmpty()) { - schema.setRequired(null); - } + List required = new ArrayList<>(schema.getRequired()); + required.removeAll(toStrip); + schema.setRequired(required.isEmpty() ? null : required); } } @@ -818,19 +847,24 @@ public class SwaggerConfiguration { } private static List resolvePropertyOrder(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { - // If an explicit @JsonPropertyOrder is present on the class or any directly implemented - // interface, honour it directly. Walk up the hierarchy so annotations on superclasses - // and their interfaces are also found. + // If an explicit @JsonPropertyOrder is present on the class or any interface in its + // ancestry, honour it directly. Walk up the class hierarchy; for each class also walk + // the full interface hierarchy (including super-interfaces) via BFS. for (Class c = cls; c != null && c != Object.class; c = c.getSuperclass()) { JsonPropertyOrder propOrder = c.getAnnotation(JsonPropertyOrder.class); if (propOrder != null && !propOrder.alphabetic() && propOrder.value().length > 0) { return Arrays.asList(propOrder.value()); } - for (Class iface : c.getInterfaces()) { + Deque> ifaceQueue = new ArrayDeque<>(Arrays.asList(c.getInterfaces())); + Set> visitedIfaces = new LinkedHashSet<>(); + while (!ifaceQueue.isEmpty()) { + Class iface = ifaceQueue.poll(); + if (!visitedIfaces.add(iface)) continue; propOrder = iface.getAnnotation(JsonPropertyOrder.class); if (propOrder != null && !propOrder.alphabetic() && propOrder.value().length > 0) { return Arrays.asList(propOrder.value()); } + ifaceQueue.addAll(Arrays.asList(iface.getInterfaces())); } } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java index 21497e0f89..a38e2e058e 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java @@ -70,6 +70,7 @@ public class DefaultEntityExportService ctx, I entityId) throws ThingsboardException { + @SuppressWarnings("unchecked") D exportData = (D) EntityExportData.newInstance(entityId.getEntityType()); E entity = exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entityId); From 622c0a113c024a34028803452259ad9f5d55e691 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 15:45:56 +0200 Subject: [PATCH 08/17] fixed resolvePropertyOrder method --- .../server/config/SwaggerConfiguration.java | 35 ++++--------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index b2d160f5f3..4e2f58e864 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -812,17 +812,16 @@ public class SwaggerConfiguration { filtered.keySet().removeAll(toStrip); allOfElement.setProperties(filtered.isEmpty() ? null : filtered); } + // Also strip inherited properties from this inline element's required list + if (allOfElement.getRequired() != null) { + List req = new ArrayList<>(allOfElement.getRequired()); + req.removeAll(toStrip); + allOfElement.setRequired(req.isEmpty() ? null : req); + } return allOfElement.getProperties() == null && allOfElement.getRequired() == null && allOfElement.getType() == null; }); - - // Remove stripped properties from the schema's required list - if (schema.getRequired() != null) { - List required = new ArrayList<>(schema.getRequired()); - required.removeAll(toStrip); - schema.setRequired(required.isEmpty() ? null : required); - } } /** @@ -870,12 +869,9 @@ public class SwaggerConfiguration { // Map backing field names to their JSON property names (respects @JsonProperty) Map fieldToJsonName = new LinkedHashMap<>(); - LinkedHashSet getterOnlyNames = new LinkedHashSet<>(); for (var prop : beanDesc.findProperties()) { if (prop.getField() != null && prop.couldSerialize()) { fieldToJsonName.put(prop.getField().getName(), prop.getName()); - } else if (prop.getField() == null) { - getterOnlyNames.add(prop.getName()); } } @@ -889,25 +885,6 @@ public class SwaggerConfiguration { for (Field f : c.getDeclaredFields()) { if (Modifier.isStatic(f.getModifiers())) continue; String jsonName = fieldToJsonName.get(f.getName()); - // Handle boolean fields with "is" prefix (e.g. field "isEnabled" → JSON "enabled"). - // Jackson derives the JSON property name from the public getter, not the field name. - // Depending on whether Jackson merges the field+getter or not, there are three cases: - // 1. jsonName == null: field invisible, getter-only property in getterOnlyNames - // 2. jsonName == fieldName: Jackson did NOT merge; field → "isEnabled" property, - // getter → "enabled" property separately in getterOnlyNames - // 3. jsonName == strippedName: Jackson merged field+getter → already correct - // Cases 1 and 2 both need to pull the stripped name from getterOnlyNames so that the - // declaration order is preserved instead of leaving it to the alphabetical fallback. - if (jsonName == null || jsonName.equals(f.getName())) { - String fn = f.getName(); - if ((f.getType() == boolean.class || f.getType() == Boolean.class) - && fn.startsWith("is") && fn.length() > 2 && Character.isUpperCase(fn.charAt(2))) { - String stripped = Character.toLowerCase(fn.charAt(2)) + fn.substring(3); - if (getterOnlyNames.remove(stripped)) { - jsonName = stripped; - } - } - } if (jsonName != null) ordered.add(jsonName); } } From 89acce4c206acc3d0fb15d99e111d06419c5c8ff Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 16:15:41 +0200 Subject: [PATCH 09/17] added test checking DiscriminatorMapping for EntityId, some java docs --- .../server/config/SwaggerConfiguration.java | 27 ++++++++-- .../server/common/data/id/EntityIdTest.java | 50 +++++++++++++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 4e2f58e864..6030f95c65 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -446,11 +446,10 @@ public class SwaggerConfiguration { }); // Deduplicate allOf child schemas: remove properties that are already defined - // in the referenced parent schema to avoid duplication (e.g. EntityId children) - schemas.values().forEach(schema -> deduplicateAllOfProperties(schema, schemas)); - - // Clean up internal marker extension used by deduplicateAllOfProperties + // in the referenced parent schema to avoid duplication (e.g. EntityId children), + // then clean up the internal marker extension used during deduplication. schemas.values().forEach(schema -> { + deduplicateAllOfProperties(schema, schemas); if (schema.getExtensions() != null) { schema.getExtensions().remove("x-tb-own-props"); if (schema.getExtensions().isEmpty()) { @@ -845,6 +844,26 @@ public class SwaggerConfiguration { return own; } + /** + * Resolves the property ordering for a schema class. + * + *

Returns a list of JSON property names in the order they should appear in the + * OpenAPI schema. The caller uses this list to reorder the schema's property map; + * any properties not present in the returned list are appended alphabetically + * by the caller's {@code TreeMap} fallback, guaranteeing a stable, deterministic order. + * + *

Resolution strategy (first match wins): + *

    + *
  1. If {@code @JsonPropertyOrder} with an explicit {@code value()} is found on the + * class or any interface in its ancestry, that list is returned as-is. Note: if the + * annotation lists only a subset of fields, those fields are ordered first and the + * remaining properties fall through to the caller's alphabetical fallback — consistent + * with Jackson's own behaviour for partial {@code @JsonPropertyOrder}.
  2. + *
  3. Otherwise, field-backed properties are returned in declaration order (superclass + * fields first). Getter-only properties are intentionally excluded to avoid + * non-deterministic ordering across restarts.
  4. + *
+ */ private static List resolvePropertyOrder(Class cls, com.fasterxml.jackson.databind.BeanDescription beanDesc) { // If an explicit @JsonPropertyOrder is present on the class or any interface in its // ancestry, honour it directly. Walk up the class hierarchy; for each class also walk diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java index d645d40dcc..9d6434a7c2 100644 --- a/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java +++ b/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java @@ -15,8 +15,18 @@ */ package org.thingsboard.server.common.data.id; +import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; +import io.swagger.v3.oas.annotations.media.Schema; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.EntityType; + +import java.util.Arrays; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; public class EntityIdTest { @@ -25,4 +35,44 @@ public class EntityIdTest { Assertions.assertEquals("13814000-1dd2-11b2-8080-808080808080", EntityId.NULL_UUID.toString()); } + @Test + public void allEntityIdImplementors_shouldBeInDiscriminatorMapping() { + Schema schemaAnnotation = EntityId.class.getAnnotation(Schema.class); + assertThat(schemaAnnotation).as("EntityId must have @Schema annotation").isNotNull(); + + DiscriminatorMapping[] mappings = schemaAnnotation.discriminatorMapping(); + Map> discriminatorMap = Arrays.stream(mappings) + .collect(Collectors.toMap(DiscriminatorMapping::value, DiscriminatorMapping::schema)); + + UUID testUuid = UUID.randomUUID(); + for (EntityType entityType : EntityType.values()) { + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, testUuid); + String typeName = entityType.name(); + + assertThat(discriminatorMap) + .as("EntityId @Schema discriminatorMapping is missing entry for EntityType." + typeName) + .containsKey(typeName); + assertThat(discriminatorMap.get(typeName)) + .as("Discriminator mapping for " + typeName + " should point to " + entityId.getClass().getSimpleName()) + .isEqualTo(entityId.getClass()); + } + } + + @Test + public void allEntityIdImplementors_shouldHaveAllOfEntityId() { + UUID testUuid = UUID.randomUUID(); + for (EntityType entityType : EntityType.values()) { + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, testUuid); + Class idClass = entityId.getClass(); + Schema schemaAnnotation = idClass.getAnnotation(Schema.class); + + assertThat(schemaAnnotation) + .as(idClass.getSimpleName() + " must have @Schema annotation") + .isNotNull(); + assertThat(schemaAnnotation.allOf()) + .as(idClass.getSimpleName() + " @Schema must include allOf = EntityId.class") + .contains(EntityId.class); + } + } + } \ No newline at end of file From 0705dd42c23ab9d5f4f810f7f067dc54a5bba8a2 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 16:16:48 +0200 Subject: [PATCH 10/17] added test checking DiscriminatorMapping for EntityExportData --- .../data/sync/ie/EntityExportDataTest.java | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/sync/ie/EntityExportDataTest.java diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/sync/ie/EntityExportDataTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/sync/ie/EntityExportDataTest.java new file mode 100644 index 0000000000..dd932fa2c5 --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/sync/ie/EntityExportDataTest.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2026 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.sync.ie; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.EntityType; + +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; + +public class EntityExportDataTest { + + @Test + public void newInstance_shouldSupportAllJsonSubTypes() { + JsonSubTypes subTypes = EntityExportData.class.getAnnotation(JsonSubTypes.class); + assertThat(subTypes).as("EntityExportData must have @JsonSubTypes annotation").isNotNull(); + + Set jsonSubTypeNames = Arrays.stream(subTypes.value()) + .map(JsonSubTypes.Type::name) + .collect(Collectors.toSet()); + + for (String typeName : jsonSubTypeNames) { + EntityType entityType = EntityType.valueOf(typeName); + EntityExportData instance = EntityExportData.newInstance(entityType); + + assertThat(instance) + .as("newInstance(%s) should not return null", typeName) + .isNotNull(); + assertThat(instance.getEntityType()) + .as("newInstance(%s).getEntityType() should return %s", typeName, entityType) + .isEqualTo(entityType); + } + } + +} From b11189f974b507820799140626c69187892c919d Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 16:33:10 +0200 Subject: [PATCH 11/17] remove parent required field from children --- .../server/config/SwaggerConfiguration.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 6030f95c65..248d43e588 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -811,16 +811,17 @@ public class SwaggerConfiguration { filtered.keySet().removeAll(toStrip); allOfElement.setProperties(filtered.isEmpty() ? null : filtered); } - // Also strip inherited properties from this inline element's required list - if (allOfElement.getRequired() != null) { - List req = new ArrayList<>(allOfElement.getRequired()); - req.removeAll(toStrip); - allOfElement.setRequired(req.isEmpty() ? null : req); - } return allOfElement.getProperties() == null && allOfElement.getRequired() == null && allOfElement.getType() == null; }); + + // Remove stripped properties from the schema's required list + if (schema.getRequired() != null) { + List required = new ArrayList<>(schema.getRequired()); + required.removeAll(toStrip); + schema.setRequired(required.isEmpty() ? null : required); + } } /** From 07f410d6050e1624920f351b2d88ec00bb4c596f Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 18:48:42 +0200 Subject: [PATCH 12/17] SwaggerConfiguration improvement: making schema order stable --- .../server/config/SwaggerConfiguration.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 248d43e588..26a0e40f05 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -357,8 +357,12 @@ public class SwaggerConfiguration { allOfElement.setProperties(reordered); } } - } catch (Exception ignored) { - log.trace("Failed to resolve property order for {}", cls.getName(), ignored); + } catch (Exception e) { + log.debug("Failed to resolve property order for {}: {}", cls.getName(), e.getMessage()); + // Fallback: at minimum sort alphabetically for determinism + if (hasProps) { + schema.setProperties(new LinkedHashMap<>(new TreeMap<>(schema.getProperties()))); + } } } } @@ -473,6 +477,12 @@ public class SwaggerConfiguration { .forEach(response -> replaceInlineOneOfInContent(response.getContent(), schemas)); } }); + + // Final safety net: ensure all schema properties are in deterministic order. + // The ModelConverter sorts properties during resolution, but springdoc may + // modify schemas afterwards (e.g. polymorphism handling, discriminator injection). + // This pass catches any properties that were added/reordered post-converter. + schemas.values().forEach(this::ensureDeterministicPropertyOrder); } // Set JsonNode schema last so model scanning cannot overwrite it @@ -824,6 +834,28 @@ public class SwaggerConfiguration { } } + /** + * Ensures all properties in a schema (top-level and inside allOf inline elements) + * are in deterministic alphabetical order. Acts as a safety net for schemas that + * were modified after the ModelConverter's sorting pass (e.g. by springdoc's + * polymorphism handling or discriminator injection for interfaces). + */ + @SuppressWarnings("unchecked") + private void ensureDeterministicPropertyOrder(Schema schema) { + if (schema.getProperties() != null && schema.getProperties().size() > 1) { + schema.setProperties(new LinkedHashMap<>(new TreeMap<>(schema.getProperties()))); + } + if (schema.getAllOf() != null) { + for (Schema allOfElement : (List) schema.getAllOf()) { + if (allOfElement.get$ref() == null + && allOfElement.getProperties() != null + && allOfElement.getProperties().size() > 1) { + allOfElement.setProperties(new LinkedHashMap<>(new TreeMap<>(allOfElement.getProperties()))); + } + } + } + } + /** * Returns the JSON property names that are backed by fields declared directly in {@code cls} * (not inherited from a superclass). Used to distinguish "own" from "inherited" properties From d081cc112f2ad7e07fb0258bafe9f85dd6f27a34 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Wed, 11 Mar 2026 19:51:23 +0200 Subject: [PATCH 13/17] reverted ensureDeterministicPropertyOrder --- .../server/config/SwaggerConfiguration.java | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 26a0e40f05..9be6a45464 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -477,12 +477,6 @@ public class SwaggerConfiguration { .forEach(response -> replaceInlineOneOfInContent(response.getContent(), schemas)); } }); - - // Final safety net: ensure all schema properties are in deterministic order. - // The ModelConverter sorts properties during resolution, but springdoc may - // modify schemas afterwards (e.g. polymorphism handling, discriminator injection). - // This pass catches any properties that were added/reordered post-converter. - schemas.values().forEach(this::ensureDeterministicPropertyOrder); } // Set JsonNode schema last so model scanning cannot overwrite it @@ -834,28 +828,6 @@ public class SwaggerConfiguration { } } - /** - * Ensures all properties in a schema (top-level and inside allOf inline elements) - * are in deterministic alphabetical order. Acts as a safety net for schemas that - * were modified after the ModelConverter's sorting pass (e.g. by springdoc's - * polymorphism handling or discriminator injection for interfaces). - */ - @SuppressWarnings("unchecked") - private void ensureDeterministicPropertyOrder(Schema schema) { - if (schema.getProperties() != null && schema.getProperties().size() > 1) { - schema.setProperties(new LinkedHashMap<>(new TreeMap<>(schema.getProperties()))); - } - if (schema.getAllOf() != null) { - for (Schema allOfElement : (List) schema.getAllOf()) { - if (allOfElement.get$ref() == null - && allOfElement.getProperties() != null - && allOfElement.getProperties().size() > 1) { - allOfElement.setProperties(new LinkedHashMap<>(new TreeMap<>(allOfElement.getProperties()))); - } - } - } - } - /** * Returns the JSON property names that are backed by fields declared directly in {@code cls} * (not inherited from a superclass). Used to distinguish "own" from "inherited" properties From fdba60ae206fbf969e5bd9036b1a7b8eeacfc471 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 12 Mar 2026 16:03:09 +0200 Subject: [PATCH 14/17] fixed ShortCustomerInfo openapi schema, refactoring --- .../server/common/data/FeaturesInfo.java | 26 +++++++++++++++---- .../server/common/data/ShortCustomerInfo.java | 11 ++++++-- .../server/common/data/SystemInfo.java | 2 ++ .../data/query/RelationsQueryFilter.java | 4 ++- 4 files changed, 35 insertions(+), 8 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java index 49d0228256..92a446fcb6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FeaturesInfo.java @@ -15,13 +15,29 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +@JsonPropertyOrder({ + "emailEnabled", + "smsEnabled", + "notificationEnabled", + "oauthEnabled", + "twoFaEnabled" +}) +@Schema @Data public class FeaturesInfo { - boolean emailEnabled; - boolean smsEnabled; - boolean notificationEnabled; - boolean oauthEnabled; - boolean twoFaEnabled; + @JsonProperty("emailEnabled") + boolean isEmailEnabled; + @JsonProperty("smsEnabled") + boolean isSmsEnabled; + @JsonProperty("notificationEnabled") + boolean isNotificationEnabled; + @JsonProperty("oauthEnabled") + boolean isOauthEnabled; + @JsonProperty("twoFaEnabled") + boolean isTwoFaEnabled; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java index 930e4dddca..dbed1026a2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -39,9 +40,15 @@ public class ShortCustomerInfo { @NoXss private String title; + @JsonProperty("isPublic") @Schema(description = "Indicates special 'Public' customer used to embed dashboards on public websites.") - @Getter @Setter - private boolean isPublic; + private boolean publicCustomer; + + @JsonProperty("isPublic") + public boolean isPublic() { return publicCustomer; } + + @JsonProperty("isPublic") + public void setPublic(boolean publicCustomer) { this.publicCustomer = publicCustomer; } @Override public boolean equals(Object o) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java index 73bd3cddc4..3fd86d1163 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SystemInfo.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -23,6 +24,7 @@ import java.util.List; @Data public class SystemInfo { @Schema(description = "Is monolith.") + @JsonProperty("monolith") private boolean isMonolith; @Schema(description = "System data.") private List systemData; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java index 488d03df63..027c4a7c5c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/RelationsQueryFilter.java @@ -15,16 +15,17 @@ */ package org.thingsboard.server.common.data.query; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import java.util.List; import java.util.Set; +@Schema @Data public class RelationsQueryFilter implements EntityFilter { @@ -34,6 +35,7 @@ public class RelationsQueryFilter implements EntityFilter { } private AliasEntityId rootEntity; + @JsonProperty("multiRoot") private boolean isMultiRoot; private EntityType multiRootEntitiesType; private Set multiRootEntityIds; From 2a3c668ec93b025a0f8d90f275b9fde229e3fb74 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 13 Mar 2026 11:55:25 +0200 Subject: [PATCH 15/17] fixed readOnly for entityId, deterministic order of tags, fixed DebugEventFilter field duplication (isError, error), fixed OtaPackage data format --- .../server/config/SwaggerConfiguration.java | 5 ++++- .../server/controller/ApiKeyController.java | 2 +- .../server/controller/DeviceController.java | 22 +++++++++---------- .../EntitiesVersionControlController.java | 6 ++--- .../server/common/data/Dashboard.java | 2 +- .../server/common/data/OtaPackage.java | 2 +- .../common/data/event/DebugEventFilter.java | 8 +++++++ 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 9be6a45464..5098009d3c 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -586,6 +586,9 @@ public class SwaggerConfiguration { if (prop.getDescription() != null) { refSchema.setDescription(prop.getDescription()); } + if (prop.getReadOnly() != null) { + refSchema.setReadOnly(prop.getReadOnly()); + } schema.getProperties().put(propName, refSchema); log.debug("Replaced oneOf with $ref to {} in property {}", baseType, propName); } @@ -596,7 +599,7 @@ public class SwaggerConfiguration { private String tagItemFromPathItem(PathItem item) { var operations = item.readOperationsMap().values(); - var operation = operations.stream().findAny(); + var operation = operations.stream().findFirst(); if (operation.isPresent()) { var tags = operation.get().getTags(); if (tags != null && !tags.isEmpty()) { diff --git a/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java b/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java index 09972bd6c2..ae3ea1c60d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java +++ b/application/src/main/java/org/thingsboard/server/controller/ApiKeyController.java @@ -68,7 +68,7 @@ public class ApiKeyController extends BaseController { private final ApiKeyService apiKeyService; @ApiOperation(value = "Save API key for user (saveApiKey)", - notes = "Creates an API key for the given user and returns the token ONCE as 'ApiKey '." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) + notes = "Creates an API key for the given user and returns the token ONCE as 'ApiKey {value}'." + AVAILABLE_FOR_ANY_AUTHORIZED_USER) @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN', 'CUSTOMER_USER')") @PostMapping(value = "/apiKey") public ApiKey saveApiKey( diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 8e5aa706c3..2f43c39f76 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -206,16 +206,16 @@ public class DeviceController extends BaseController { notes = "Create or update the Device. When creating device, platform generates Device Id as " + UUID_WIKI_LINK + "Requires to provide the Device Credentials object as well as an existing device profile ID or use \"default\".\n" + "You may find the example of device with different type of credentials below: \n\n" + - "- Credentials type: \"Access token\" with device profile ID below: \n\n" + + "- Credentials type: **\"Access token\"** with **device profile ID** below: \n\n" + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" + - "- Credentials type: \"Access token\" with device profile default below: \n\n" + + "- Credentials type: **\"Access token\"** with **device profile default** below: \n\n" + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_ACCESS_TOKEN_DEFAULT_DESCRIPTION_MARKDOWN + "\n\n" + - "- Credentials type: \"X509\" with device profile ID below: \n\n" + - "Note: credentialsId - format Sha3Hash, certificateValue - format PEM (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" + + "- Credentials type: **\"X509\"** with **device profile ID** below: \n\n" + + "Note: **credentialsId** - format **Sha3Hash**, **certificateValue** - format **PEM** (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" + - "- Credentials type: \"MQTT_BASIC\" with device profile ID below: \n\n" + + "- Credentials type: **\"MQTT_BASIC\"** with **device profile ID** below: \n\n" + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" + - "- You may find the example of LwM2M device and RPK credentials below: \n\n" + + "- You may find the example of **LwM2M** device and **RPK** credentials below: \n\n" + "Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " + @@ -322,14 +322,14 @@ public class DeviceController extends BaseController { "Then use current method to update the credentials type and value. It is not possible to create multiple device credentials for the same device.\n" + "The structure of device credentials id and value is simple for the 'ACCESS_TOKEN' but is much more complex for the 'MQTT_BASIC' or 'LWM2M_CREDENTIALS'.\n" + "You may find the example of device with different type of credentials below: \n\n" + - "- Credentials type: \"Access token\" with device ID and with device ID below: \n\n" + + "- Credentials type: **\"Access token\"** with **device ID** and with **device ID** below: \n\n" + DEVICE_UPDATE_CREDENTIALS_PARAM_ACCESS_TOKEN_DESCRIPTION_MARKDOWN + "\n\n" + - "- Credentials type: \"X509\" with device profile ID below: \n\n" + - "Note: credentialsId - format Sha3Hash, certificateValue - format PEM (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" + + "- Credentials type: **\"X509\"** with **device profile ID** below: \n\n" + + "Note: **credentialsId** - format **Sha3Hash**, **certificateValue** - format **PEM** (with \"--BEGIN CERTIFICATE----\" and -\"----END CERTIFICATE-\").\n\n" + DEVICE_UPDATE_CREDENTIALS_PARAM_X509_CERTIFICATE_DESCRIPTION_MARKDOWN + "\n\n" + - "- Credentials type: \"MQTT_BASIC\" with device profile ID below: \n\n" + + "- Credentials type: **\"MQTT_BASIC\"** with **device profile ID** below: \n\n" + DEVICE_UPDATE_CREDENTIALS_PARAM_MQTT_BASIC_DESCRIPTION_MARKDOWN + "\n\n" + - "- You may find the example of LwM2M device and RPK credentials below: \n\n" + + "- You may find the example of **LwM2M** device and **RPK** credentials below: \n\n" + "Note: LwM2M device - only existing device profile ID (Transport configuration -> Transport type: \"LWM2M\".\n\n" + DEVICE_UPDATE_CREDENTIALS_PARAM_LVM2M_RPK_DESCRIPTION_MARKDOWN + "\n\n" + "Update to real value:\n" + diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java index cf632531ce..8dd607bb6f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java @@ -214,19 +214,19 @@ public class EntitiesVersionControlController extends BaseController { " \"timestamp\": 1655198593000,\n" + " \"id\": \"fd82625bdd7d6131cf8027b44ee967012ecaf990\",\n" + " \"name\": \"Devices and assets - v2.0\",\n" + - " \"author\": \"John Doe \"\n" + + " \"author\": \"John Doe (johndoe@gmail.com)\"\n" + " },\n" + " {\n" + " \"timestamp\": 1655198528000,\n" + " \"id\": \"682adcffa9c8a2f863af6f00c4850323acbd4219\",\n" + " \"name\": \"Update my device\",\n" + - " \"author\": \"John Doe \"\n" + + " \"author\": \"John Doe (johndoe@gmail.com)\"\n" + " },\n" + " {\n" + " \"timestamp\": 1655198280000,\n" + " \"id\": \"d2a6087c2b30e18cc55e7cdda345a8d0dfb959a4\",\n" + " \"name\": \"Devices and assets - v1.0\",\n" + - " \"author\": \"John Doe \"\n" + + " \"author\": \"John Doe (johndoe@gmail.com)\"\n" + " }\n" + " ],\n" + " \"totalPages\": 1,\n" + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java index e9e1658731..2c65a3dfc8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java @@ -33,7 +33,7 @@ import java.util.Optional; import java.util.stream.Collectors; @EqualsAndHashCode(callSuper = true) -@JsonPropertyOrder({"title", "image", "mobileHide", "mobileOrder", "configuration", "name", "resources"}) +@JsonPropertyOrder({"id", "createdTime", "tenantId", "title", "name", "image", "mobileHide", "mobileOrder", "assignedCustomers", "configuration", "resources", "version"}) public class Dashboard extends DashboardInfo implements ExportableEntity { private static final long serialVersionUID = 872682138346187503L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java index e9f0871999..360d16fbbe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java @@ -31,7 +31,7 @@ public class OtaPackage extends OtaPackageInfo { @Serial private static final long serialVersionUID = 3091601761339422546L; - @Schema(description = "OTA Package data.", accessMode = Schema.AccessMode.READ_ONLY) + @Schema(description = "OTA Package data.", accessMode = Schema.AccessMode.READ_ONLY, type = "string", format = "byte") private transient ByteBuffer data; public OtaPackage() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java index 8779416106..01463dade8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEventFilter.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.event; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.StringUtils; @@ -26,10 +27,17 @@ public abstract class DebugEventFilter implements EventFilter { @Schema(description = "String value representing the server name, identifier or ip address where the platform is running", example = "ip-172-31-24-152") protected String server; @Schema(description = "Boolean value to filter the errors", allowableValues = {"false", "true"}) + @JsonProperty("isError") protected boolean isError; @Schema(description = "The case insensitive 'contains' filter based on error message", example = "not present in the DB") protected String errorStr; + @JsonProperty("isError") + public boolean isError() { + return isError; + } + + @JsonProperty("isError") public void setIsError(boolean isError) { this.isError = isError; } From 31d6b2094474c6e5e1c60fe9fcc09b23de1a785a Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 13 Mar 2026 12:48:58 +0200 Subject: [PATCH 16/17] fixed ByteBuffer fields for openapi schema --- .../org/thingsboard/server/config/SwaggerConfiguration.java | 4 ++++ .../java/org/thingsboard/server/common/data/OtaPackage.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 5098009d3c..db862e2a43 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -48,6 +48,7 @@ import lombok.extern.slf4j.Slf4j; import org.springdoc.core.customizers.OpenApiCustomizer; import org.springdoc.core.customizers.OperationCustomizer; import org.springdoc.core.discoverer.SpringDocParameterNameDiscoverer; +import org.springdoc.core.utils.SpringDocUtils; import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.properties.SpringDocConfigProperties; import org.springdoc.core.properties.SwaggerUiConfigProperties; @@ -69,6 +70,7 @@ import org.thingsboard.server.service.security.auth.rest.LoginResponse; import java.lang.reflect.Field; import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -94,6 +96,8 @@ public class SwaggerConfiguration { @PostConstruct public void configureModelResolver() { ModelResolver.enumsAsRef = true; + SpringDocUtils.getConfig().replaceWithSchema(ByteBuffer.class, + new Schema().type("string").format("byte")); } public static final String LOGIN_ENDPOINT = "/api/auth/login"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java index 360d16fbbe..e9f0871999 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java @@ -31,7 +31,7 @@ public class OtaPackage extends OtaPackageInfo { @Serial private static final long serialVersionUID = 3091601761339422546L; - @Schema(description = "OTA Package data.", accessMode = Schema.AccessMode.READ_ONLY, type = "string", format = "byte") + @Schema(description = "OTA Package data.", accessMode = Schema.AccessMode.READ_ONLY) private transient ByteBuffer data; public OtaPackage() { From ea3271af3a748e31ec6c5f14d1fa36f6e512c429 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 13 Mar 2026 13:18:06 +0200 Subject: [PATCH 17/17] fixed order for Alarm --- .../java/org/thingsboard/server/common/data/alarm/Alarm.java | 1 + 1 file changed, 1 insertion(+) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index abd1515ff2..9f9bdec49d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -84,6 +84,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha @Schema(description = "Timestamp of the alarm assignment, in milliseconds", example = "1634115928465") private long assignTs; @Schema(description = "JSON object with alarm details") + @JsonProperty private transient JsonNode details; @Schema(description = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") private boolean propagate;