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.script.api.tbel.TbelInvokeService;
import org.thingsboard.server.common.data.cf.CalculatedField; import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldLink; 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.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId; import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageDataIterable; 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.cf.CalculatedFieldService;
import org.thingsboard.server.dao.usagerecord.ApiLimitService; import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import org.thingsboard.server.queue.util.AfterStartUp; 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.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@ -214,41 +215,38 @@ public class DefaultCalculatedFieldCache implements CalculatedFieldCache {
} }
private void evictTenantCfs(TenantId tenantId) { private void evictTenantCfs(TenantId tenantId) {
var removedCfIds = new HashSet<CalculatedFieldId>();
var removedCfEntityIds = new HashSet<EntityId>(); var removedCfEntityIds = new HashSet<EntityId>();
var removedLinkEntityIds = new HashSet<EntityId>(); var removedLinkEntityIds = new HashSet<EntityId>();
for (Map.Entry<CalculatedFieldId, CalculatedField> entry : calculatedFields.entrySet()) { var toRemove = calculatedFields.entrySet().stream()
CalculatedFieldId cfId = entry.getKey(); .filter(e -> e.getValue().getTenantId().equals(tenantId))
CalculatedField cf = entry.getValue(); .map(Map.Entry::getKey)
if (cf.getTenantId().equals(tenantId)) { .collect(Collectors.toSet());
calculatedFields.remove(cfId); toRemove.forEach(cfId -> {
List<CalculatedFieldLink> links = calculatedFieldLinks.remove(cfId); CalculatedField cf = calculatedFields.remove(cfId);
if (links != null) { List<CalculatedFieldLink> links = calculatedFieldLinks.remove(cfId);
links.forEach(link -> removedLinkEntityIds.add(link.getEntityId())); 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);
} }
} calculatedFieldsCtx.remove(cfId);
removedCfEntityIds.add(cf.getEntityId());
});
removedCfEntityIds.forEach(entityId -> { removedCfEntityIds.forEach(entityId -> {
List<CalculatedField> cfs = entityIdCalculatedFields.get(entityId); entityIdCalculatedFields.compute(entityId, (k, cfs) -> {
if (cfs != null) { if (cfs != null) {
cfs.removeIf(cf -> removedCfIds.contains(cf.getId())); cfs.removeIf(cf -> toRemove.contains(cf.getId()));
if (cfs.isEmpty()) { return cfs.isEmpty() ? null : cfs;
entityIdCalculatedFields.remove(entityId);
} }
} return null;
});
}); });
removedLinkEntityIds.forEach(entityId -> { removedLinkEntityIds.forEach(entityId -> {
List<CalculatedFieldLink> entityLinks = entityIdCalculatedFieldLinks.get(entityId); entityIdCalculatedFieldLinks.compute(entityId, ((entityId1, links) -> {
if (entityLinks != null) { if (links != null) {
entityLinks.removeIf(link -> removedCfIds.contains(link.getCalculatedFieldId())); links.removeIf(link -> toRemove.contains(link.getCalculatedFieldId()));
if (entityLinks.isEmpty()) { return links.isEmpty() ? null : links;
entityIdCalculatedFieldLinks.remove(entityId);
} }
} 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.AssetProfileService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import java.util.HashSet; import java.util.Set;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@ -154,19 +154,12 @@ public class DefaultTbAssetProfileCache implements TbAssetProfileCache {
case TENANT: case TENANT:
if (event.getEvent() == ComponentLifecycleEvent.DELETED) { if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
TenantId tenantId = event.getTenantId(); TenantId tenantId = event.getTenantId();
var removedProfileIds = new HashSet<AssetProfileId>(); Set<AssetProfileId> toRemove = assetProfilesMap.values().stream()
for (Map.Entry<AssetProfileId, AssetProfile> entry : assetProfilesMap.entrySet()) { .filter(assetProfile -> assetProfile.getTenantId().equals(tenantId))
if (entry.getValue().getTenantId().equals(tenantId)) { .map(AssetProfile::getId)
assetProfilesMap.remove(entry.getKey()); .collect(Collectors.toSet());
removedProfileIds.add(entry.getKey()); assetProfilesMap.keySet().removeAll(toRemove);
log.debug("[{}] evict asset profile from cache: {}", entry.getKey(), entry.getValue()); assetsMap.entrySet().removeIf(entry -> toRemove.contains(entry.getValue()));
}
}
for (Map.Entry<AssetId, AssetProfileId> entry : assetsMap.entrySet()) {
if (removedProfileIds.contains(entry.getValue())) {
assetsMap.remove(entry.getKey());
}
}
profileListeners.remove(tenantId); profileListeners.remove(tenantId);
assetProfileListeners.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.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import java.util.HashSet; import java.util.Set;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors;
@Service @Service
@Slf4j @Slf4j
@ -154,19 +154,12 @@ public class DefaultTbDeviceProfileCache implements TbDeviceProfileCache {
case TENANT: case TENANT:
if (event.getEvent() == ComponentLifecycleEvent.DELETED) { if (event.getEvent() == ComponentLifecycleEvent.DELETED) {
TenantId tenantId = event.getTenantId(); TenantId tenantId = event.getTenantId();
var removedProfileIds = new HashSet<DeviceProfileId>(); Set<DeviceProfileId> toRemove = deviceProfilesMap.values().stream()
for (Map.Entry<DeviceProfileId, DeviceProfile> entry : deviceProfilesMap.entrySet()) { .filter(deviceProfile -> deviceProfile.getTenantId().equals(tenantId))
if (entry.getValue().getTenantId().equals(tenantId)) { .map(DeviceProfile::getId)
deviceProfilesMap.remove(entry.getKey()); .collect(Collectors.toSet());
removedProfileIds.add(entry.getKey()); deviceProfilesMap.keySet().removeAll(toRemove);
log.debug("[{}] evict device profile from cache: {}", entry.getKey(), entry.getValue()); devicesMap.entrySet().removeIf(entry -> toRemove.contains(entry.getValue()));
}
}
for (Map.Entry<DeviceId, DeviceProfileId> entry : devicesMap.entrySet()) {
if (removedProfileIds.contains(entry.getValue())) {
devicesMap.remove(entry.getKey());
}
}
profileListeners.remove(tenantId); profileListeners.remove(tenantId);
deviceProfileListeners.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.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.AssetId; 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.CalculatedFieldId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; 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.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -151,6 +153,112 @@ public class DefaultCalculatedFieldCacheTest {
assertThat(cache.getCalculatedFieldsByEntityId(asset)).isEmpty(); 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 --- // --- CalculatedField lifecycle tests ---
@Test @Test

Loading…
Cancel
Save