Browse Source

refactoring, added tests

pull/15274/head
dashevchenko 6 months ago
parent
commit
c033f4b5b3
  1. 56
      application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java
  2. 23
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java
  3. 23
      application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java
  4. 108
      application/src/test/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCacheTest.java

56
application/src/main/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCache.java

@ -25,13 +25,13 @@ import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.queue.util.AfterStartUp;
@ -46,6 +46,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Service
@Slf4j
@ -214,41 +215,38 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
}
private void evictTenantCfs(TenantId tenantId) {
var removedCfIds = new HashSet<CalculatedFieldId>();
var removedCfEntityIds = new HashSet<EntityId>();
var removedLinkEntityIds = new HashSet<EntityId>();
for (Map.Entry<CalculatedFieldId, CalculatedField> entry : calculatedFields.entrySet()) {
CalculatedFieldId cfId = entry.getKey();
CalculatedField cf = entry.getValue();
if (cf.getTenantId().equals(tenantId)) {
calculatedFields.remove(cfId);
List<CalculatedFieldLink> links = calculatedFieldLinks.remove(cfId);
if (links != null) {
links.forEach(link -> removedLinkEntityIds.add(link.getEntityId()));
}
calculatedFieldsCtx.remove(cfId);
removedCfIds.add(cfId);
removedCfEntityIds.add(cf.getEntityId());
log.debug("[{}] evict calculated field from cache on tenant deletion: {}", cfId, cf);
var toRemove = calculatedFields.entrySet().stream()
.filter(e -> e.getValue().getTenantId().equals(tenantId))
.map(Map.Entry::getKey)
.collect(Collectors.toSet());
toRemove.forEach(cfId -> {
CalculatedField cf = calculatedFields.remove(cfId);
List<CalculatedFieldLink> links = calculatedFieldLinks.remove(cfId);
if (links != null) {
links.forEach(link -> removedLinkEntityIds.add(link.getEntityId()));
}
}
calculatedFieldsCtx.remove(cfId);
removedCfEntityIds.add(cf.getEntityId());
});
removedCfEntityIds.forEach(entityId -> {
List<CalculatedField> cfs = entityIdCalculatedFields.get(entityId);
if (cfs != null) {
cfs.removeIf(cf -> removedCfIds.contains(cf.getId()));
if (cfs.isEmpty()) {
entityIdCalculatedFields.remove(entityId);
entityIdCalculatedFields.compute(entityId, (k, cfs) -> {
if (cfs != null) {
cfs.removeIf(cf -> toRemove.contains(cf.getId()));
return cfs.isEmpty() ? null : cfs;
}
}
return null;
});
});
removedLinkEntityIds.forEach(entityId -> {
List<CalculatedFieldLink> entityLinks = entityIdCalculatedFieldLinks.get(entityId);
if (entityLinks != null) {
entityLinks.removeIf(link -> removedCfIds.contains(link.getCalculatedFieldId()));
if (entityLinks.isEmpty()) {
entityIdCalculatedFieldLinks.remove(entityId);
entityIdCalculatedFieldLinks.compute(entityId, ((entityId1, links) -> {
if (links != null) {
links.removeIf(link -> toRemove.contains(link.getCalculatedFieldId()));
return links.isEmpty() ? null : links;
}
}
return null;
}));
});
}

23
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbAssetProfileCache.java

@ -29,14 +29,14 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.asset.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Service
@Slf4j
@ -154,19 +154,12 @@ public class DefaultTbAssetProfileCache implements TbAssetProfileCache {
case TENANT:
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
TenantId tenantId = event.getTenantId();
var removedProfileIds = new HashSet<AssetProfileId>();
for (Map.Entry<AssetProfileId, AssetProfile> entry : assetProfilesMap.entrySet()) {
if (entry.getValue().getTenantId().equals(tenantId)) {
assetProfilesMap.remove(entry.getKey());
removedProfileIds.add(entry.getKey());
log.debug("[{}] evict asset profile from cache: {}", entry.getKey(), entry.getValue());
}
}
for (Map.Entry<AssetId, AssetProfileId> entry : assetsMap.entrySet()) {
if (removedProfileIds.contains(entry.getValue())) {
assetsMap.remove(entry.getKey());
}
}
Set<AssetProfileId> toRemove = assetProfilesMap.values().stream()
.filter(assetProfile -> assetProfile.getTenantId().equals(tenantId))
.map(AssetProfile::getId)
.collect(Collectors.toSet());
assetProfilesMap.keySet().removeAll(toRemove);
assetsMap.entrySet().removeIf(entry -> toRemove.contains(entry.getValue()));
profileListeners.remove(tenantId);
assetProfileListeners.remove(tenantId);
}

23
application/src/main/java/org/thingsboard/server/service/profile/DefaultTbDeviceProfileCache.java

@ -29,14 +29,14 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@Service
@Slf4j
@ -154,19 +154,12 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
case TENANT:
if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
TenantId tenantId = event.getTenantId();
var removedProfileIds = new HashSet<DeviceProfileId>();
for (Map.Entry<DeviceProfileId, DeviceProfile> entry : deviceProfilesMap.entrySet()) {
if (entry.getValue().getTenantId().equals(tenantId)) {
deviceProfilesMap.remove(entry.getKey());
removedProfileIds.add(entry.getKey());
log.debug("[{}] evict device profile from cache: {}", entry.getKey(), entry.getValue());
}
}
for (Map.Entry<DeviceId, DeviceProfileId> entry : devicesMap.entrySet()) {
if (removedProfileIds.contains(entry.getValue())) {
devicesMap.remove(entry.getKey());
}
}
Set<DeviceProfileId> toRemove = deviceProfilesMap.values().stream()
.filter(deviceProfile -> deviceProfile.getTenantId().equals(tenantId))
.map(DeviceProfile::getId)
.collect(Collectors.toSet());
deviceProfilesMap.keySet().removeAll(toRemove);
devicesMap.entrySet().removeIf(entry -> toRemove.contains(entry.getValue()));
profileListeners.remove(tenantId);
deviceProfileListeners.remove(tenantId);
}

108
application/src/test/java/org/thingsboard/server/service/cf/DefaultCalculatedFieldCacheTest.java

@ -26,9 +26,11 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldLink;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -151,6 +153,112 @@ public class DefaultCalculatedFieldCacheTest {
assertThat(cache.getCalculatedFieldsByEntityId(asset)).isEmpty();
}
// --- DeviceProfile/AssetProfile deletion tests ---
@Test
public void onComponentLifecycleEvent_deviceProfileDeleted_evictsCfsForThatProfile() {
TenantId tenant = new TenantId(UUID.randomUUID());
DeviceProfileId profileId = new DeviceProfileId(UUID.randomUUID());
CalculatedField cf = addCfToCache(tenant, profileId);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profileId, ComponentLifecycleEvent.DELETED));
assertThat(cache.getCalculatedField(cf.getId())).isNull();
assertThat(cache.getCalculatedFieldsByEntityId(profileId)).isEmpty();
}
@Test
public void onComponentLifecycleEvent_deviceProfileDeleted_removesLinksForLinkedEntities() {
TenantId tenant = new TenantId(UUID.randomUUID());
DeviceProfileId profileId = new DeviceProfileId(UUID.randomUUID());
DeviceId linkedDevice = new DeviceId(UUID.randomUUID());
addCfToCache(tenant, profileId, linkedDevice);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profileId, ComponentLifecycleEvent.DELETED));
assertThat(cache.getCalculatedFieldLinksByEntityId(linkedDevice)).isEmpty();
}
@Test
public void onComponentLifecycleEvent_deviceProfileDeleted_doesNotEvictOtherProfilesCfs() {
TenantId tenant = new TenantId(UUID.randomUUID());
DeviceProfileId profile1 = new DeviceProfileId(UUID.randomUUID());
DeviceProfileId profile2 = new DeviceProfileId(UUID.randomUUID());
CalculatedField cf1 = addCfToCache(tenant, profile1);
CalculatedField cf2 = addCfToCache(tenant, profile2);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profile1, ComponentLifecycleEvent.DELETED));
assertThat(cache.getCalculatedField(cf1.getId())).isNull();
assertThat(cache.getCalculatedFieldsByEntityId(profile1)).isEmpty();
assertThat(cache.getCalculatedField(cf2.getId())).isEqualTo(cf2);
assertThat(cache.getCalculatedFieldsByEntityId(profile2)).containsExactly(cf2);
}
@Test
public void onComponentLifecycleEvent_deviceProfileUpdated_doesNotEvictCfs() {
TenantId tenant = new TenantId(UUID.randomUUID());
DeviceProfileId profileId = new DeviceProfileId(UUID.randomUUID());
CalculatedField cf = addCfToCache(tenant, profileId);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profileId, ComponentLifecycleEvent.UPDATED));
assertThat(cache.getCalculatedField(cf.getId())).isEqualTo(cf);
assertThat(cache.getCalculatedFieldsByEntityId(profileId)).containsExactly(cf);
}
@Test
public void onComponentLifecycleEvent_assetProfileDeleted_evictsCfsForThatProfile() {
TenantId tenant = new TenantId(UUID.randomUUID());
AssetProfileId profileId = new AssetProfileId(UUID.randomUUID());
CalculatedField cf = addCfToCache(tenant, profileId);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profileId, ComponentLifecycleEvent.DELETED));
assertThat(cache.getCalculatedField(cf.getId())).isNull();
assertThat(cache.getCalculatedFieldsByEntityId(profileId)).isEmpty();
}
@Test
public void onComponentLifecycleEvent_assetProfileDeleted_removesLinksForLinkedEntities() {
TenantId tenant = new TenantId(UUID.randomUUID());
AssetProfileId profileId = new AssetProfileId(UUID.randomUUID());
AssetId linkedAsset = new AssetId(UUID.randomUUID());
addCfToCache(tenant, profileId, linkedAsset);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profileId, ComponentLifecycleEvent.DELETED));
assertThat(cache.getCalculatedFieldLinksByEntityId(linkedAsset)).isEmpty();
}
@Test
public void onComponentLifecycleEvent_assetProfileDeleted_doesNotEvictOtherProfilesCfs() {
TenantId tenant = new TenantId(UUID.randomUUID());
AssetProfileId profile1 = new AssetProfileId(UUID.randomUUID());
AssetProfileId profile2 = new AssetProfileId(UUID.randomUUID());
CalculatedField cf1 = addCfToCache(tenant, profile1);
CalculatedField cf2 = addCfToCache(tenant, profile2);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profile1, ComponentLifecycleEvent.DELETED));
assertThat(cache.getCalculatedField(cf1.getId())).isNull();
assertThat(cache.getCalculatedFieldsByEntityId(profile1)).isEmpty();
assertThat(cache.getCalculatedField(cf2.getId())).isEqualTo(cf2);
assertThat(cache.getCalculatedFieldsByEntityId(profile2)).containsExactly(cf2);
}
@Test
public void onComponentLifecycleEvent_assetProfileUpdated_doesNotEvictCfs() {
TenantId tenant = new TenantId(UUID.randomUUID());
AssetProfileId profileId = new AssetProfileId(UUID.randomUUID());
CalculatedField cf = addCfToCache(tenant, profileId);
cache.onComponentLifecycleEvent(new ComponentLifecycleMsg(tenant, profileId, ComponentLifecycleEvent.UPDATED));
assertThat(cache.getCalculatedField(cf.getId())).isEqualTo(cf);
assertThat(cache.getCalculatedFieldsByEntityId(profileId)).containsExactly(cf);
}
// --- CalculatedField lifecycle tests ---
@Test

Loading…
Cancel
Save