Browse Source

Merge pull request #15050 from volodymyr-babak/admin-setting-fix

Event-sourced propagation for admin settings
pull/15140/head
Viacheslav Klimov 3 months ago
committed by GitHub
parent
commit
bc0e63bea3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  2. 34
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java
  3. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OAuth2EdgeEventFetcher.java
  4. 10
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java
  5. 79
      application/src/test/java/org/thingsboard/server/edge/AdminSettingsEdgeTest.java
  6. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java
  7. 2
      common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java
  8. 1
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  9. 4
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java
  10. 19
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
  11. 23
      dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java

4
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());

34
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<AdminSettings> {
private final AdminSettingsService adminSettingsService;
private final TenantId fetcherTenantId;
@Override
public PageLink getPageLink(int pageSize) {
return null;
}
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
List<EdgeEvent> 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<AdminSettings> fetchEntities(TenantId tenantId, Edge edge, PageLink pageLink) {
return adminSettingsService.findAllByTenantId(fetcherTenantId, pageLink);
}
private List<EdgeEvent> fetchAdminSettingsForKeys(TenantId tenantId, EdgeId edgeId, List<String> keys) {
List<EdgeEvent> 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);
}
}

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

@ -41,7 +41,7 @@ public class OAuth2EdgeEventFetcher extends BasePageableEdgeEventFetcher<DomainI
@Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, DomainInfo domainInfo) {
return EdgeUtils.constructEdgeEvent(TenantId.SYS_TENANT_ID, edge.getId(), EdgeEventType.DOMAIN,
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DOMAIN,
EdgeEventActionType.ADDED, domainInfo.getId(), null);
}

10
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/settings/AdminSettingsEdgeProcessor.java

@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@ -35,7 +36,14 @@ public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor {
@Override
public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) {
AdminSettings adminSettings = JacksonUtil.convertValue(edgeEvent.getBody(), AdminSettings.class);
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;
}

79
application/src/test/java/org/thingsboard/server/edge/AdminSettingsEdgeTest.java

@ -0,0 +1,79 @@
/**
* 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.edge;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg;
@DaoSqlTest
public class AdminSettingsEdgeTest extends AbstractEdgeTest {
@Autowired
private AdminSettingsService adminSettingsService;
@Test
public void testAdminSettings() throws Exception {
loginSysAdmin();
// save
AdminSettings adminSettings = new AdminSettings();
adminSettings.setKey("edgeTest");
ObjectNode jsonValue = JacksonUtil.newObjectNode();
jsonValue.put("key1", "value1");
adminSettings.setJsonValue(jsonValue);
edgeImitator.expectMessageAmount(1);
AdminSettings savedAdminSettings = doPost("/api/admin/settings", adminSettings, AdminSettings.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AdminSettingsUpdateMsg);
AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) latestMessage;
AdminSettings adminSettingsMsg = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true);
Assert.assertNotNull(adminSettingsMsg);
Assert.assertEquals("edgeTest", adminSettingsMsg.getKey());
Assert.assertEquals("value1", adminSettingsMsg.getJsonValue().get("key1").asText());
// update
ObjectNode updatedJsonValue = (ObjectNode) savedAdminSettings.getJsonValue();
updatedJsonValue.put("key2", "value2");
savedAdminSettings.setJsonValue(updatedJsonValue);
edgeImitator.expectMessageAmount(1);
doPost("/api/admin/settings", savedAdminSettings, AdminSettings.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AdminSettingsUpdateMsg);
adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) latestMessage;
adminSettingsMsg = JacksonUtil.fromString(adminSettingsUpdateMsg.getEntity(), AdminSettings.class, true);
Assert.assertNotNull(adminSettingsMsg);
Assert.assertEquals("edgeTest", adminSettingsMsg.getKey());
Assert.assertEquals("value1", adminSettingsMsg.getJsonValue().get("key1").asText());
Assert.assertEquals("value2", adminSettingsMsg.getJsonValue().get("key2").asText());
adminSettingsService.deleteAdminSettingsByTenantIdAndKey(savedAdminSettings.getTenantId(), "edgeTest");
}
}

4
common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java

@ -18,6 +18,8 @@ package org.thingsboard.server.dao.settings;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.id.AdminSettingsId;
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.entity.EntityDaoService;
public interface AdminSettingsService extends EntityDaoService {
@ -28,6 +30,8 @@ public interface AdminSettingsService extends EntityDaoService {
AdminSettings findAdminSettingsByTenantIdAndKey(TenantId tenantId, String key);
PageData<AdminSettings> findAllByTenantId(TenantId tenantId, PageLink pageLink);
AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings);
boolean deleteAdminSettingsByTenantIdAndKey(TenantId tenantId, String key);

2
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),

1
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!");
};
}

4
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> {
AdminSettings findByTenantIdAndKey(UUID tenantId, String key);
PageData<AdminSettings> findAllByTenantId(TenantId tenantId, PageLink pageLink);
boolean removeByTenantIdAndKey(UUID tenantId, String key);
void removeByTenantId(UUID tenantId);

19
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<AdminSettings> 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<AdminSettings> 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

23
dao/src/test/java/org/thingsboard/server/dao/service/AdminSettingsServiceTest.java

@ -25,9 +25,12 @@ 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;
@ -113,4 +116,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<AdminSettings> pageData = adminSettingsService.findAllByTenantId(tenantId, new PageLink(pageSize));
assertThat(pageData.getData().size()).isEqualTo(pageSize);
assertThat(pageData.getTotalElements()).isEqualTo(totalElements);
}
}

Loading…
Cancel
Save