From 6c200e3767a5746e160629e1957998e35aed8b44 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 17 Feb 2026 17:53:36 +0200 Subject: [PATCH 1/2] Fixed sync of AdminSettings to Edge in realtime --- .../service/edge/rpc/EdgeSyncCursor.java | 4 +- .../fetch/AdminSettingsEdgeEventFetcher.java | 34 ++------ .../rpc/fetch/OAuth2EdgeEventFetcher.java | 2 +- .../settings/AdminSettingsEdgeProcessor.java | 4 +- .../server/edge/AdminSettingsEdgeTest.java | 79 +++++++++++++++++++ .../dao/settings/AdminSettingsService.java | 4 + .../common/data/edge/EdgeEventType.java | 2 +- .../common/data/id/EntityIdFactory.java | 1 + .../server/dao/settings/AdminSettingsDao.java | 4 + .../settings/AdminSettingsServiceImpl.java | 19 ++++- .../dao/service/AdminSettingsServiceTest.java | 25 ++++++ 11 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/edge/AdminSettingsEdgeTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index bf3c94ca44..ce175957f6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -19,6 +19,7 @@ import lombok.Getter; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.service.edge.EdgeContextComponent; import org.thingsboard.server.service.edge.rpc.fetch.AdminSettingsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.AssetProfilesEdgeEventFetcher; @@ -62,7 +63,8 @@ public class EdgeSyncCursor { fetchers.add(new TenantEdgeEventFetcher(ctx.getTenantService())); fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); - fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService())); + fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), TenantId.SYS_TENANT_ID)); + fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), edge.getTenantId())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); } Customer publicCustomer = ctx.getCustomerService().findPublicCustomer(edge.getTenantId()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index 7e4b463573..b18c05cd53 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -17,50 +17,32 @@ package org.thingsboard.server.service.edge.rpc.fetch; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.settings.AdminSettingsService; -import java.util.ArrayList; -import java.util.List; - @AllArgsConstructor @Slf4j -public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { +public class AdminSettingsEdgeEventFetcher extends BasePageableEdgeEventFetcher { private final AdminSettingsService adminSettingsService; + private final TenantId tenantId; @Override - public PageLink getPageLink(int pageSize) { - return null; - } - - public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { - List result = fetchAdminSettingsForKeys(tenantId, edge.getId(), List.of("general", "mail", "connectivity", "jwt")); - - // return PageData object to be in sync with other fetchers - return new PageData<>(result, 1, result.size(), false); + PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) { + return adminSettingsService.findAllByTenantId(this.tenantId, pageLink); } - private List fetchAdminSettingsForKeys(TenantId tenantId, EdgeId edgeId, List keys) { - List result = new ArrayList<>(); - for (String key : keys) { - AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key); - if (adminSettings != null) { - result.add(EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(adminSettings))); - } - } - return result; + @Override + EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, AdminSettings adminSettings) { + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, + EdgeEventActionType.UPDATED, adminSettings.getId(), null); } - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java index b7337173cd..6c65d141d8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java @@ -41,7 +41,7 @@ public class OAuth2EdgeEventFetcher extends BasePageableEdgeEventFetcher findAllByTenantId(TenantId tenantId, PageLink pageLink); + AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings); boolean deleteAdminSettingsByTenantIdAndKey(TenantId tenantId, String key); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java index eff86ee5d8..5f8d57f6f8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java @@ -38,7 +38,7 @@ public enum EdgeEventType { TENANT_PROFILE(true, EntityType.TENANT_PROFILE), WIDGETS_BUNDLE(true, EntityType.WIDGETS_BUNDLE), WIDGET_TYPE(true, EntityType.WIDGET_TYPE), - ADMIN_SETTINGS(true, null), + ADMIN_SETTINGS(true, EntityType.ADMIN_SETTINGS), OTA_PACKAGE(true, EntityType.OTA_PACKAGE), QUEUE(true, EntityType.QUEUE), NOTIFICATION_RULE(true, EntityType.NOTIFICATION_RULE), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 11c33c9cdd..65432f6b7f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -113,6 +113,7 @@ public class EntityIdFactory { case OAUTH2_CLIENT -> new OAuth2ClientId(uuid); case DOMAIN -> new DomainId(uuid); case CALCULATED_FIELD -> new CalculatedFieldId(uuid); + case ADMIN_SETTINGS -> new AdminSettingsId(uuid); default -> throw new IllegalArgumentException("EdgeEventType " + edgeEventType + " is not supported!"); }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java index 2c814ef50a..ee8510a851 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.settings; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.UUID; @@ -27,6 +29,8 @@ public interface AdminSettingsDao extends Dao { AdminSettings findByTenantIdAndKey(UUID tenantId, String key); + PageData findAllByTenantId(TenantId tenantId, PageLink pageLink); + boolean removeByTenantIdAndKey(UUID tenantId, String key); void removeByTenantId(UUID tenantId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index d6708aabfe..71f0eb268e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FluentFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EntityType; @@ -27,6 +28,9 @@ import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; @@ -44,6 +48,9 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { @Autowired private DataValidator adminSettingsValidator; + @Autowired + protected ApplicationEventPublisher eventPublisher; + @Override public AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId) { log.trace("Executing findAdminSettingsById [{}]", adminSettingsId); @@ -63,10 +70,15 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { return adminSettingsDao.findByTenantIdAndKey(tenantId.getId(), key); } + @Override + public PageData findAllByTenantId(TenantId tenantId, PageLink pageLink) { + return adminSettingsDao.findAllByTenantId(tenantId, pageLink); + } + @Override public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) { log.trace("Executing saveAdminSettings [{}]", adminSettings); - adminSettingsValidator.validate(adminSettings, data -> tenantId); + AdminSettings oldAdminSettings = adminSettingsValidator.validate(adminSettings, data -> tenantId); if (adminSettings.getKey().equals("mail")) { AdminSettings mailSettings = findAdminSettingsByKey(tenantId, "mail"); if (mailSettings != null) { @@ -84,7 +96,10 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { if (adminSettings.getTenantId() == null) { adminSettings.setTenantId(TenantId.SYS_TENANT_ID); } - return adminSettingsDao.save(tenantId, adminSettings); + AdminSettings savedAdminSettings = adminSettingsDao.save(tenantId, adminSettings); + eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedAdminSettings.getTenantId()).entityId(savedAdminSettings.getId()) + .entity(savedAdminSettings).oldEntity(oldAdminSettings).created(adminSettings.getId() == null).build()); + return savedAdminSettings; } @Override diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java index 2c5851eb82..043e01f482 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.jupiter.api.Assertions; @@ -25,12 +26,16 @@ import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +@Slf4j @DaoSqlTest public class AdminSettingsServiceTest extends AbstractServiceTest { @@ -113,4 +118,24 @@ public class AdminSettingsServiceTest extends AbstractServiceTest { }).hasMessageContaining("already exists"); } + @Test + public void testFindAllByTenantId() { + int pageSize = 10; + int totalElements = 100; + + for (int i = 0; i < totalElements; i++) { + AdminSettings settings = new AdminSettings(); + settings.setTenantId(tenantId); + String key = RandomStringUtils.randomAlphanumeric(15); + settings.setKey(key); + settings.setJsonValue(JacksonUtil.newObjectNode().put("value", i)); + adminSettingsService.saveAdminSettings(tenantId, settings); + } + + PageData pageData = adminSettingsService.findAllByTenantId(tenantId, new PageLink(pageSize)); + assertThat(pageData.getData().size()).isEqualTo(pageSize); + assertThat(pageData.getTotalElements()).isEqualTo(totalElements); + + } + } From 17d5db7aa126e64541d237677321e50c2d55c523 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 17 Feb 2026 18:29:36 +0200 Subject: [PATCH 2/2] Copilot review changes --- .../edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java | 4 ++-- .../processor/settings/AdminSettingsEdgeProcessor.java | 10 ++++++++-- .../server/dao/service/AdminSettingsServiceTest.java | 2 -- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index b18c05cd53..47e004ac82 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -33,11 +33,11 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; public class AdminSettingsEdgeEventFetcher extends BasePageableEdgeEventFetcher { private final AdminSettingsService adminSettingsService; - private final TenantId tenantId; + private final TenantId fetcherTenantId; @Override PageData fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) { - return adminSettingsService.findAllByTenantId(this.tenantId, pageLink); + return adminSettingsService.findAllByTenantId(fetcherTenantId, pageLink); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java index 96210e6325..2e0a65f324 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java @@ -36,8 +36,14 @@ public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor { @Override public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { - AdminSettingsId adminSettingsId = new AdminSettingsId(edgeEvent.getEntityId()); - AdminSettings adminSettings = edgeCtx.getAdminSettingsService().findAdminSettingsById(edgeEvent.getTenantId(), adminSettingsId); + AdminSettings adminSettings = null; + if (edgeEvent.getEntityId() != null) { + AdminSettingsId adminSettingsId = new AdminSettingsId(edgeEvent.getEntityId()); + adminSettings = edgeCtx.getAdminSettingsService().findAdminSettingsById(edgeEvent.getTenantId(), adminSettingsId); + } else if (edgeEvent.getBody() != null && !edgeEvent.getBody().isEmpty()) { + // legacy + adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class); + } if (adminSettings == null) { return null; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java index 043e01f482..1b7924aad6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java @@ -17,7 +17,6 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; -import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.junit.jupiter.api.Assertions; @@ -35,7 +34,6 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -@Slf4j @DaoSqlTest public class AdminSettingsServiceTest extends AbstractServiceTest {