Browse Source

Merge remote-tracking branch 'upstream/master' into api-key

pull/14074/head
Andrii Landiak 8 months ago
parent
commit
fdf313fc40
  1. 2
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  2. 4
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  3. 4
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  4. 4
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  5. 4
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  6. 4
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  7. 21
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  8. 4
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  9. 4
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  10. 6
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  11. 62
      dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java
  12. 45
      dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java
  13. 2
      ui-ngx/src/app/modules/login/pages/login/force-two-factor-auth-login.component.html
  14. 1
      ui-ngx/src/assets/locale/locale.constant-en_US.json

2
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -952,7 +952,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
argument.setRefEntityKey(new ReferencedEntityKey("createAlarm", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
configuration.setArguments(Map.of("createAlarm", argument));
AlarmRule alarmRule = new AlarmRule();
alarmRule.setAlarmDetails("attribute is ${bool}");
alarmRule.setAlarmDetails("attribute is ${createAlarm}");
SimpleAlarmCondition condition = new SimpleAlarmCondition();
TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression();
expression.setExpression("return createAlarm == true;");

4
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -151,12 +151,12 @@ public class BaseAssetService extends AbstractCachedEntityService<AssetCacheKey,
@Override
public Asset saveAsset(Asset asset, NameConflictStrategy nameConflictStrategy) {
return saveAsset(asset, true, nameConflictStrategy);
return saveEntity(asset, () -> saveAsset(asset, true, nameConflictStrategy));
}
@Override
public Asset saveAsset(Asset asset, boolean doValidate) {
return saveAsset(asset, doValidate, NameConflictStrategy.DEFAULT);
return saveEntity(asset, () -> saveAsset(asset, doValidate, NameConflictStrategy.DEFAULT));
}
private Asset saveAsset(Asset asset, boolean doValidate, NameConflictStrategy nameConflictStrategy) {

4
dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java

@ -143,13 +143,13 @@ public class CustomerServiceImpl extends AbstractCachedEntityService<CustomerCac
@Override
@Transactional
public Customer saveCustomer(Customer customer) {
return saveCustomer(customer, true, NameConflictStrategy.DEFAULT);
return saveCustomer(customer, NameConflictStrategy.DEFAULT);
}
@Override
@Transactional
public Customer saveCustomer(Customer customer, NameConflictStrategy nameConflictStrategy) {
return saveCustomer(customer, true, nameConflictStrategy);
return saveEntity(customer, () -> saveCustomer(customer, true, nameConflictStrategy));
}
private Customer saveCustomer(Customer customer, boolean doValidate) {

4
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -160,6 +160,10 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Override
public Dashboard saveDashboard(Dashboard dashboard, boolean doValidate) {
return saveEntity(dashboard, () -> doSaveDashboard(dashboard, doValidate));
}
private Dashboard doSaveDashboard(Dashboard dashboard, boolean doValidate) {
log.trace("Executing saveDashboard [{}]", dashboard);
if (doValidate) {
dashboardValidator.validate(dashboard, DashboardInfo::getTenantId);

4
dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java

@ -228,6 +228,10 @@ public class DeviceServiceImpl extends CachedVersionedEntityService<DeviceCacheK
}
private Device saveDeviceWithoutCredentials(Device device, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
return saveEntity(device, () -> doSaveDeviceWithoutCredentials(device, doValidate, nameConflictStrategy));
}
private Device doSaveDeviceWithoutCredentials(Device device, boolean doValidate, NameConflictStrategy nameConflictStrategy) {
log.trace("Executing saveDevice [{}]", device);
Device oldDevice = (device.getId() != null) ? deviceDao.findById(device.getTenantId(), device.getId().getId()) : null;
if (nameConflictStrategy.policy() == NameConflictPolicy.UNIQUIFY && (oldDevice == null || !oldDevice.getName().equals(device.getName()))) {

4
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -203,6 +203,10 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
@Override
public Edge saveEdge(Edge edge) {
return saveEntity(edge, () -> doSaveEdge(edge));
}
private Edge doSaveEdge(Edge edge) {
log.trace("Executing saveEdge [{}]", edge);
Edge oldEdge = edgeValidator.validate(edge, Edge::getTenantId);
EdgeCacheEvictEvent evictEvent = new EdgeCacheEvictEvent(edge.getTenantId(), edge.getName(), oldEdge != null ? oldEdge.getName() : null);

21
dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java

@ -21,11 +21,13 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.common.util.DebugModeUtil;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.HasDebugSettings;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.HasTenantId;
import org.thingsboard.server.common.data.NameConflictStrategy;
@ -51,8 +53,11 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Supplier;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -64,6 +69,8 @@ public abstract class AbstractEntityService {
public static final String INCORRECT_EDGE_ID = "Incorrect edgeId ";
public static final String INCORRECT_PAGE_LINK = "Incorrect page link ";
private final ConcurrentMap<TenantId, ReentrantLock> entityCreationLocks = new ConcurrentReferenceHashMap<>(16);
@Autowired
protected ApplicationEventPublisher eventPublisher;
@ -101,6 +108,20 @@ public abstract class AbstractEntityService {
@Value("${debug.settings.default_duration:15}")
private int defaultDebugDurationMinutes;
protected <E extends HasId & HasTenantId> E saveEntity(E entity, Supplier<E> saveFunction) {
if (entity.getId() == null) {
ReentrantLock lock = entityCreationLocks.computeIfAbsent(entity.getTenantId(), id -> new ReentrantLock());
lock.lock();
try {
return saveFunction.get();
} finally {
lock.unlock();
}
} else {
return saveFunction.get();
}
}
protected void createRelation(TenantId tenantId, EntityRelation relation) {
log.debug("Creating relation: {}", relation);
relationService.saveRelation(tenantId, relation);

4
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -127,6 +127,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
@Override
@Transactional
public RuleChain saveRuleChain(RuleChain ruleChain, boolean publishSaveEvent, boolean doValidate) {
return saveEntity(ruleChain, () -> doSaveRuleChain(ruleChain, publishSaveEvent, doValidate));
}
private RuleChain doSaveRuleChain(RuleChain ruleChain, boolean publishSaveEvent, boolean doValidate) {
log.trace("Executing doSaveRuleChain [{}]", ruleChain);
if (doValidate) {
ruleChainValidator.validate(ruleChain, RuleChain::getTenantId);

4
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -173,6 +173,10 @@ public class UserServiceImpl extends AbstractCachedEntityService<UserCacheKey, U
@Override
@Transactional
public User saveUser(TenantId tenantId, User user) {
return saveEntity(user, () -> doSaveUser(tenantId, user));
}
private User doSaveUser(TenantId tenantId, User user) {
log.trace("Executing saveUser [{}]", user);
User oldUser = userValidator.validate(user, User::getTenantId);
if (!userLoginCaseSensitive) {

6
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
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.id.TenantProfileId;
import org.thingsboard.server.common.data.oauth2.MapperType;
import org.thingsboard.server.common.data.oauth2.OAuth2Client;
import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig;
@ -185,8 +186,13 @@ public abstract class AbstractServiceTest {
}
public Tenant createTenant() {
return createTenant(null);
}
public Tenant createTenant(TenantProfileId tenantProfileId) {
Tenant tenant = new Tenant();
tenant.setTitle("My tenant " + UUID.randomUUID());
tenant.setTenantProfileId(tenantProfileId);
Tenant savedTenant = tenantService.saveTenant(tenant);
assertNotNull(savedTenant);
return savedTenant;

62
dao/src/test/java/org/thingsboard/server/dao/service/AssetServiceTest.java

@ -16,17 +16,25 @@
package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetProfile;
@ -44,6 +52,8 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.asset.AssetDao;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
@ -51,12 +61,16 @@ import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -72,13 +86,28 @@ public class AssetServiceTest extends AbstractServiceTest {
@Autowired
RelationService relationService;
@Autowired
TenantProfileService tenantProfileService;
@Autowired
private AssetProfileService assetProfileService;
@Autowired
private CalculatedFieldService calculatedFieldService;
@Autowired
private PlatformTransactionManager platformTransactionManager;
private static ListeningExecutorService executor;
private IdComparator<Asset> idComparator = new IdComparator<>();
private TenantId anotherTenantId;
@BeforeClass
public static void before() {
executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10, ThingsBoardThreadFactory.forName("AssetServiceTestScope")));
}
@AfterClass
public static void after() {
executor.shutdownNow();
}
@Test
public void testSaveAsset() {
@ -105,6 +134,39 @@ public class AssetServiceTest extends AbstractServiceTest {
assetService.deleteAsset(tenantId, savedAsset.getId());
}
@Test
public void testAssetLimitOnTenantProfileLevel() throws InterruptedException {
TenantProfile tenantProfile = new TenantProfile();
tenantProfile.setName("Test profile");
tenantProfile.setDescription("Test");
TenantProfileData profileData = new TenantProfileData();
profileData.setConfiguration(DefaultTenantProfileConfiguration.builder().maxAssets(5l).build());
tenantProfile.setProfileData(profileData);
tenantProfile.setDefault(false);
tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile = tenantProfileService.saveTenantProfile(anotherTenantId, tenantProfile);
anotherTenantId = createTenant(tenantProfile.getId()).getId();
for (int i = 0; i < 20; i++) {
executor.submit(() -> {
Asset asset = new Asset();
asset.setTenantId(anotherTenantId);
asset.setName(RandomStringUtils.randomAlphabetic(10));
asset.setType("default");
assetService.saveAsset(asset);
});
}
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> {
long countByTenantId = assetService.countByTenantId(anotherTenantId);
return countByTenantId == 5;
});
Thread.sleep(2000);
assertThat(assetService.countByTenantId(anotherTenantId)).isEqualTo(5);
}
@Test
public void testShouldNotPutInCacheRolledbackAssetProfile() {
AssetProfile assetProfile = new AssetProfile();

45
dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceTest.java

@ -16,9 +16,13 @@
package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.mockito.Mockito;
@ -27,6 +31,8 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.testcontainers.shaded.org.awaitility.Awaitility;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
@ -74,7 +80,10 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
@ -105,6 +114,17 @@ public class DeviceServiceTest extends AbstractServiceTest {
private IdComparator<Device> idComparator = new IdComparator<>();
private TenantId anotherTenantId;
private static ListeningExecutorService executor;
@BeforeClass
public static void beforeClass() {
executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10, ThingsBoardThreadFactory.forName("DeviceServiceTestScope")));
}
@AfterClass
public static void afterClass() {
executor.shutdownNow();
}
@Before
public void before() {
@ -136,6 +156,31 @@ public class DeviceServiceTest extends AbstractServiceTest {
deleteDevice(tenantId, device);
}
@Test
public void testDeviceLimitOnTenantProfileLevel() throws InterruptedException {
TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxDevices(5l).build());
tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
for (int i = 0; i < 20; i++) {
executor.submit(() -> {
Device device = new Device();
device.setTenantId(tenantId);
device.setName(StringUtils.randomAlphabetic(10));
device.setType("default");
deviceService.saveDevice(device, true);
});
}
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> {
long countByTenantId = deviceService.countByTenantId(tenantId);
return countByTenantId == 5;
});
Thread.sleep(2000);
assertThat(deviceService.countByTenantId(tenantId)).isEqualTo(5);
}
@Test
public void testSaveDevicesWithMaxDeviceOutOfLimit() {
TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);

2
ui-ngx/src/app/modules/login/pages/login/force-two-factor-auth-login.component.html

@ -157,7 +157,7 @@
</mat-card-header>
<mat-card-content>
<div class="flex flex-col items-center justify-start">
<form [formGroup]="emailConfigForm" class="mb-8">
<form [formGroup]="emailConfigForm" class="mb-8 w-full">
<p class="mat-body step-description input" translate>login.email-description</p>
<mat-form-field class="mat-block input-container flex-1">
<input matInput formControlName="email"

1
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -4178,6 +4178,7 @@
"copy-key": "Copy key",
"send-code": "Send code",
"email-label": "Email",
"email-description": "Enter an email to use as your authenticator.",
"sms-description": "Enter a phone number to use as your authenticator.",
"backup-code-description": "Print out the codes so you have them handy when you need to use them to log in to your account. You can use each backup code once.",
"backup-code-warn": "Once you leave this page, these codes cannot be shown again. Store them safely using the options below.",

Loading…
Cancel
Save