diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 0f08e618c3..5d86e6a2a5 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -20,9 +20,13 @@ import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.ClassRule; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.support.TestPropertySourceUtils; +import org.testcontainers.containers.GenericContainer; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.DataConstants; @@ -110,7 +114,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac .atMost(TIMEOUT, TimeUnit.SECONDS) .until(() -> { List debugEvents = getEvents(tenantId, ruleChainFinal.getFirstRuleNodeId(), DataConstants.LC_EVENT, 1000) - .getData().stream().filter(e-> { + .getData().stream().filter(e -> { var body = e.getBody(); return body.has("event") && body.get("event").asText().equals("STARTED") && body.has("success") && body.get("success").asBoolean(); @@ -128,7 +132,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac log.warn("before update attr"); attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, - Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))) + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))) .get(TIMEOUT, TimeUnit.SECONDS); log.warn("attr updated"); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java index de7bab4e4f..02205248fe 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.rules.lifecycle.sql; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; import org.thingsboard.server.rules.lifecycle.AbstractRuleEngineLifecycleIntegrationTest; /** diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/CacheKeyUtil.java b/common/cache/src/main/java/org/thingsboard/server/cache/CacheKeyUtil.java new file mode 100644 index 0000000000..1c1a096f59 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/CacheKeyUtil.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2022 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.cache; + +public class CacheKeyUtil { + + public static String toString(Object... keyElements) { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (Object keyElement : keyElements) { + first = addElement(sb, first, keyElement); + } + return sb.toString(); + } + + private static boolean addElement(StringBuilder sb, boolean first, Object element) { + if (element != null) { + if (!first) { + sb.append("_"); + } + sb.append(element); + return false; + } else { + return first; + } + } + +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java index fdcc7c4d7a..cc43f0c537 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java @@ -39,6 +39,28 @@ public interface TbTransactionalCache newTransactionForKeys(List keys); + default V getAndPutInTransaction(K key, Supplier dbCall, boolean cacheNullValue) { + TbCacheValueWrapper cacheValueWrapper = get(key); + if (cacheValueWrapper != null) { + return cacheValueWrapper.get(); + } + var cacheTransaction = newTransactionForKey(key); + try { + V dbValue = dbCall.get(); + if (dbValue != null || cacheNullValue) { + cacheTransaction.putIfAbsent(key, dbValue); + cacheTransaction.commit(); + return dbValue; + } else { + cacheTransaction.rollback(); + return null; + } + } catch (Throwable e) { + cacheTransaction.rollback(); + throw e; + } + } + default R getAndPutInTransaction(K key, Supplier dbCall, Function cacheValueToResult, Function dbValueToCacheValue, boolean cacheNullValue) { TbCacheValueWrapper cacheValueWrapper = get(key); if (cacheValueWrapper != null) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java index e9950447db..f6dae741b1 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java @@ -28,5 +28,9 @@ public interface EdgeEventService { PageData findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate); + /** + * Executes stored procedure to cleanup old edge events. + * @param ttl the ttl for edge events in seconds + */ void cleanupEvents(long ttl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java index 19842eacc4..06dcd51e61 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java @@ -15,15 +15,10 @@ */ package org.thingsboard.server.dao.cache; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; public interface EntitiesCacheManager { - void removeDeviceFromCacheByName(TenantId tenantId, String name); - - void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId); - void removeAssetFromCacheByName(TenantId tenantId, String name); void removeEdgeFromCacheByName(TenantId tenantId, String name); diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java index 2a28f07424..d24bd98822 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java @@ -19,13 +19,11 @@ import lombok.AllArgsConstructor; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import java.util.Arrays; import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE; -import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE; import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE; @Component @@ -34,21 +32,6 @@ public class EntitiesCacheManagerImpl implements EntitiesCacheManager { private final CacheManager cacheManager; - @Override - public void removeDeviceFromCacheByName(TenantId tenantId, String name) { - Cache cache = cacheManager.getCache(DEVICE_CACHE); - cache.evict(Arrays.asList(tenantId, name)); - } - - @Override - public void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) { - if (deviceId == null) { - return; - } - Cache cache = cacheManager.getCache(DEVICE_CACHE); - cache.evict(Arrays.asList(tenantId, deviceId)); - } - @Override public void removeAssetFromCacheByName(TenantId tenantId, String name) { Cache cache = cacheManager.getCache(ASSET_CACHE); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCacheKey.java new file mode 100644 index 0000000000..574a5c2617 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCacheKey.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2022 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.dao.device; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.cache.CacheKeyUtil; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.io.Serializable; + +@Getter +@EqualsAndHashCode +@RequiredArgsConstructor +@Builder +public class DeviceCacheKey implements Serializable { + + private final TenantId tenantId; + private final DeviceId deviceId; + private final String deviceName; + + public DeviceCacheKey(TenantId tenantId, DeviceId deviceId) { + this(tenantId, deviceId, null); + } + + public DeviceCacheKey(TenantId tenantId, String deviceName) { + this(tenantId, null, deviceName); + } + + @Override + public String toString() { + return CacheKeyUtil.toString(tenantId, deviceId, deviceName); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCaffeineCache.java new file mode 100644 index 0000000000..2349acbc53 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCaffeineCache.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 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.dao.device; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.dao.cache.CaffeineTbTransactionalCache; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("DeviceCache") +public class DeviceCaffeineCache extends CaffeineTbTransactionalCache { + + public DeviceCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.DEVICE_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceEvent.java new file mode 100644 index 0000000000..51ad354ea3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceEvent.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 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.dao.device; + +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +public class DeviceEvent { + + private final TenantId tenantId; + private final DeviceId deviceId; + private final String newDeviceName; + private final String oldDeviceName; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceRedisCache.java new file mode 100644 index 0000000000..a3c63b49f9 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceRedisCache.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2022 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.dao.device; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.dao.cache.RedisTbTransactionalCache; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("DeviceCache") +public class DeviceRedisCache extends RedisTbTransactionalCache { + + public DeviceRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new RedisSerializer<>() { + + private final RedisSerializer java = RedisSerializer.java(); + + @Override + public byte[] serialize(Device attributeKvEntry) throws SerializationException { + return java.serialize(attributeKvEntry); + } + + @Override + public Device deserialize(byte[] bytes) throws SerializationException { + return (Device) java.deserialize(bytes); + } + }); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index a3260ffa17..7d12d505d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -24,20 +24,23 @@ import org.apache.commons.lang3.RandomStringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; @@ -62,10 +65,10 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.dao.cache.EntitiesCacheManager; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -77,7 +80,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -91,7 +93,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service @Slf4j -public class DeviceServiceImpl extends AbstractEntityService implements DeviceService { +public class DeviceServiceImpl extends AbstractCachedEntityService implements DeviceService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; @@ -109,15 +111,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Autowired private DeviceProfileService deviceProfileService; - @Autowired - private EntitiesCacheManager cacheManager; - @Autowired private EventService eventService; @Autowired private DataValidator deviceValidator; + @Transactional(propagation = Propagation.SUPPORTS) @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceInfoById [{}]", deviceId); @@ -125,16 +125,20 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfoById(tenantId, deviceId.getId()); } - @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #deviceId}") + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - if (TenantId.SYS_TENANT_ID.equals(tenantId)) { - return deviceDao.findById(tenantId, deviceId.getId()); - } else { - return deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId()); - } + return cache.getAndPutInTransaction(new DeviceCacheKey(tenantId, deviceId), + () -> { + //TODO: possible bug source since sometimes we need to clear cache by tenant id and sometimes by sys tenant id? + if (TenantId.SYS_TENANT_ID.equals(tenantId)) { + return deviceDao.findById(tenantId, deviceId.getId()); + } else { + return deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId()); + } + }, true); } @Override @@ -148,47 +152,33 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } } - @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}") + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device findDeviceByTenantIdAndName(TenantId tenantId, String name) { log.trace("Executing findDeviceByTenantIdAndName [{}][{}]", tenantId, name); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - Optional deviceOpt = deviceDao.findDeviceByTenantIdAndName(tenantId.getId(), name); - return deviceOpt.orElse(null); + return cache.getAndPutInTransaction(new DeviceCacheKey(tenantId, name), + () -> deviceDao.findDeviceByTenantIdAndName(tenantId.getId(), name).orElse(null), true); } - @Caching(evict= { - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") - }) - @Transactional + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device saveDeviceWithAccessToken(Device device, String accessToken) { return doSaveDevice(device, accessToken, true); } - @Caching(evict= { - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") - }) + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device saveDevice(Device device, boolean doValidate) { return doSaveDevice(device, null, doValidate); } - @Caching(evict= { - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") - }) + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device saveDevice(Device device) { return doSaveDevice(device, null, true); } - @Caching(evict= { - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") - }) @Transactional @Override public Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials) { @@ -226,9 +216,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe private Device saveDeviceWithoutCredentials(Device device, boolean doValidate) { log.trace("Executing saveDevice [{}]", device); + Device oldDevice = null; if (doValidate) { - deviceValidator.validate(device, Device::getTenantId); + oldDevice = deviceValidator.validate(device, Device::getTenantId); + } else if (device.getId() != null) { + oldDevice = findDeviceById(device.getTenantId(), device.getId()); } + DeviceEvent deviceEvent = new DeviceEvent(device.getTenantId(), device.getId(), device.getName(), oldDevice != null ? oldDevice.getName() : null); try { DeviceProfile deviceProfile; if (device.getDeviceProfileId() == null) { @@ -246,13 +240,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } device.setType(deviceProfile.getName()); device.setDeviceData(syncDeviceData(deviceProfile, device.getDeviceData())); - return deviceDao.saveAndFlush(device.getTenantId(), device); + Device result = deviceDao.saveAndFlush(device.getTenantId(), device); + publishEvictEvent(deviceEvent); + return result; } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { // remove device from cache in case null value cached in the distributed redis. - cacheManager.removeDeviceFromCacheByName(device.getTenantId(), device.getName()); - cacheManager.removeDeviceFromCacheById(device.getTenantId(), device.getId()); + handleEvictEvent(deviceEvent); throw new DataValidationException("Device with such name already exists!"); } else { throw t; @@ -260,15 +255,27 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } } + @TransactionalEventListener(classes = DeviceEvent.class) + @Override + public void handleEvictEvent(DeviceEvent event) { + List keys = new ArrayList<>(3); + keys.add(new DeviceCacheKey(event.getTenantId(), event.getNewDeviceName())); + if (event.getDeviceId() != null) { + keys.add(new DeviceCacheKey(event.getTenantId(), event.getDeviceId())); + } + if (StringUtils.isNotEmpty(event.getOldDeviceName()) && !event.getOldDeviceName().equals(event.getNewDeviceName())) { + keys.add(new DeviceCacheKey(event.getTenantId(), event.getOldDeviceName())); + } + cache.evict(keys); + } + private DeviceData syncDeviceData(DeviceProfile deviceProfile, DeviceData deviceData) { if (deviceData == null) { deviceData = new DeviceData(); } if (deviceData.getConfiguration() == null || !deviceProfile.getType().equals(deviceData.getConfiguration().getType())) { - switch (deviceProfile.getType()) { - case DEFAULT: - deviceData.setConfiguration(new DefaultDeviceConfiguration()); - break; + if (deviceProfile.getType() == DeviceProfileType.DEFAULT) { + deviceData.setConfiguration(new DefaultDeviceConfiguration()); } } if (deviceData.getTransportConfiguration() == null || !deviceProfile.getTransportType().equals(deviceData.getTransportConfiguration().getType())) { @@ -293,24 +300,20 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceData; } + @Transactional @Override public Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId) { Device device = findDeviceById(tenantId, deviceId); device.setCustomerId(customerId); - Device savedDevice = saveDevice(device); - cacheManager.removeDeviceFromCacheByName(tenantId, device.getName()); - cacheManager.removeDeviceFromCacheById(tenantId, device.getId()); - return savedDevice; + return saveDevice(device); } + @Transactional @Override public Device unassignDeviceFromCustomer(TenantId tenantId, DeviceId deviceId) { Device device = findDeviceById(tenantId, deviceId); device.setCustomerId(null); - Device savedDevice = saveDevice(device); - cacheManager.removeDeviceFromCacheByName(tenantId, device.getName()); - cacheManager.removeDeviceFromCacheById(tenantId, device.getId()); - return savedDevice; + return saveDevice(device); } @Transactional @@ -320,7 +323,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); Device device = deviceDao.findById(tenantId, deviceId.getId()); - final String deviceName = device.getName(); + DeviceEvent deviceEvent = new DeviceEvent(device.getTenantId(), device.getId(), device.getName(), null); try { List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get(); if (entityViews != null && !entityViews.isEmpty()) { @@ -339,12 +342,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deviceDao.removeById(tenantId, deviceId.getId()); - cacheManager.removeDeviceFromCacheByName(tenantId, deviceName); - cacheManager.removeDeviceFromCacheById(tenantId, deviceId); + publishEvictEvent(deviceEvent); } - + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); @@ -353,6 +355,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantId(tenantId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); @@ -361,6 +364,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfosByTenantId(tenantId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); @@ -370,6 +374,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, @@ -383,6 +388,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndTypeAndEmptyOtaPackage(tenantId.getId(), deviceProfileId.getId(), type, pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type) { log.trace("Executing countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage, tenantId [{}], deviceProfileId [{}], type [{}]", tenantId, deviceProfileId, type); @@ -391,6 +397,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(tenantId.getId(), deviceProfileId.getId(), type); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); @@ -400,6 +407,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfosByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDeviceInfosByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndDeviceProfileId, tenantId [{}], deviceProfileId [{}], pageLink [{}]", tenantId, deviceProfileId, pageLink); @@ -409,6 +417,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfosByTenantIdAndDeviceProfileId(tenantId.getId(), deviceProfileId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public ListenableFuture> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List deviceIds) { log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds); @@ -417,7 +426,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndIdsAsync(tenantId.getId(), toUUIDs(deviceIds)); } - + @Transactional @Override public void deleteDevicesByTenantId(TenantId tenantId) { log.trace("Executing deleteDevicesByTenantId, tenantId [{}]", tenantId); @@ -425,6 +434,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe tenantDevicesRemover.removeEntities(tenantId, tenantId); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); @@ -434,6 +444,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); @@ -443,6 +454,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfosByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); @@ -453,6 +465,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); @@ -463,6 +476,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId, tenantId [{}], customerId [{}], deviceProfileId [{}], pageLink [{}]", tenantId, customerId, deviceProfileId, pageLink); @@ -473,6 +487,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfosByTenantIdAndCustomerIdAndDeviceProfileId(tenantId.getId(), customerId.getId(), deviceProfileId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public ListenableFuture> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List deviceIds) { log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds); @@ -483,6 +498,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe customerId.getId(), toUUIDs(deviceIds)); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public void unassignCustomerDevices(TenantId tenantId, CustomerId customerId) { log.trace("Executing unassignCustomerDevices, tenantId [{}], customerId [{}]", tenantId, customerId); @@ -491,6 +507,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe customerDeviceUnasigner.removeEntities(tenantId, customerId); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public ListenableFuture> findDevicesByQuery(TenantId tenantId, DeviceSearchQuery query) { ListenableFuture> relations = relationService.findByQuery(tenantId, query.toEntitySearchQuery()); @@ -506,7 +523,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return Futures.successfulAsList(futures); }, MoreExecutors.directExecutor()); - devices = Futures.transform(devices, new Function, List>() { + devices = Futures.transform(devices, new Function<>() { @Nullable @Override public List apply(@Nullable List deviceList) { @@ -517,6 +534,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return devices; } + @Transactional(propagation = Propagation.SUPPORTS) @Override public ListenableFuture> findDeviceTypesByTenantId(TenantId tenantId) { log.trace("Executing findDeviceTypesByTenantId, tenantId [{}]", tenantId); @@ -533,7 +551,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Override public Device assignDeviceToTenant(TenantId tenantId, Device device) { log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device); - try { List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get(); if (!CollectionUtils.isEmpty(entityViews)) { @@ -554,10 +571,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe device.setCustomerId(null); Device savedDevice = doSaveDevice(device, null, true); + DeviceEvent oldTenantEvent = new DeviceEvent(oldTenantId, device.getId(), device.getName(), null); + DeviceEvent newTenantEvent = new DeviceEvent(savedDevice.getTenantId(), device.getId(), device.getName(), null); + // explicitly remove device with previous tenant id from cache // result device object will have different tenant id and will not remove entity from cache - cacheManager.removeDeviceFromCacheByName(oldTenantId, device.getName()); - cacheManager.removeDeviceFromCacheById(oldTenantId, device.getId()); + publishEvictEvent(oldTenantEvent); + publishEvictEvent(newTenantEvent); return savedDevice; } @@ -605,15 +625,18 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } - cacheManager.removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above + + publishEvictEvent(new DeviceEvent(savedDevice.getTenantId(), savedDevice.getId(), provisionRequest.getDeviceName(), null)); return savedDevice; } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink) { return deviceDao.findDevicesIdsByDeviceProfileTransportType(transportType, pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId) { Device device = findDeviceById(tenantId, deviceId); @@ -633,6 +656,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return device; } + @Transactional(propagation = Propagation.SUPPORTS) @Override public Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId) { Device device = findDeviceById(tenantId, deviceId); @@ -652,6 +676,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return device; } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndEdgeId, tenantId [{}], edgeId [{}], pageLink [{}]", tenantId, edgeId, pageLink); @@ -661,6 +686,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public PageData findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink) { log.trace("Executing findDevicesByTenantIdAndEdgeIdAndType, tenantId [{}], edgeId [{}], type [{}] pageLink [{}]", tenantId, edgeId, type, pageLink); @@ -677,7 +703,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } private PaginatedRemover tenantDevicesRemover = - new PaginatedRemover() { + new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedEntityService.java new file mode 100644 index 0000000000..9d97aab675 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedEntityService.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2022 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.dao.entity; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.thingsboard.server.cache.TbTransactionalCache; + +import java.io.Serializable; + +public abstract class AbstractCachedEntityService extends AbstractEntityService { + + @Autowired + protected TbTransactionalCache cache; + + @Autowired + private ApplicationEventPublisher eventPublisher; + + protected void publishEvictEvent(E event) { + if (TransactionSynchronizationManager.isActualTransactionActive()) { + eventPublisher.publishEvent(event); + } else { + handleEvictEvent(event); + } + } + + public abstract void handleEvictEvent(E event); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java index 9321fd4e9e..94be99af12 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java @@ -18,7 +18,9 @@ package org.thingsboard.server.dao.entity; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; @@ -29,6 +31,7 @@ import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.relation.EntityRelationEvent; import org.thingsboard.server.dao.relation.RelationService; import java.util.List; diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 00eafeec1c..a12c2e3faa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -19,7 +19,6 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; @@ -28,6 +27,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -65,21 +65,21 @@ public class BaseRelationService implements RelationService { private final RelationDao relationDao; private final EntityService entityService; private final TbTransactionalCache cache; - private final ApplicationEventPublisher publisher; + private final ApplicationEventPublisher eventPublisher; private final JpaExecutorService executor; public BaseRelationService(RelationDao relationDao, @Lazy EntityService entityService, TbTransactionalCache cache, - ApplicationEventPublisher publisher, JpaExecutorService executor) { + ApplicationEventPublisher eventPublisher, JpaExecutorService executor) { this.relationDao = relationDao; this.entityService = entityService; this.cache = cache; - this.publisher = publisher; + this.eventPublisher = eventPublisher; this.executor = executor; } @TransactionalEventListener(classes = EntityRelationEvent.class) - public void handleRelationEvictEvent(EntityRelationEvent event) { + public void handleEvictEvent(EntityRelationEvent event) { List keys = new ArrayList<>(5); keys.add(new RelationCacheKey(event.getFrom(), event.getTo(), event.getType(), event.getTypeGroup())); keys.add(new RelationCacheKey(event.getFrom(), null, event.getType(), event.getTypeGroup(), EntitySearchDirection.FROM)); @@ -103,18 +103,21 @@ public class BaseRelationService implements RelationService { validate(from, to, relationType, typeGroup); RelationCacheKey cacheKey = new RelationCacheKey(from, to, relationType, typeGroup); return cache.getAndPutInTransaction(cacheKey, - () -> relationDao.getRelation(tenantId, from, to, relationType, typeGroup), + () -> { + log.trace("FETCH EntityRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup); + return relationDao.getRelation(tenantId, from, to, relationType, typeGroup); + }, RelationCacheValue::getRelation, relations -> RelationCacheValue.builder().relation(relations).build(), false); } - @Override @Transactional(propagation = Propagation.SUPPORTS) + @Override public boolean saveRelation(TenantId tenantId, EntityRelation relation) { log.trace("Executing saveRelation [{}]", relation); validate(relation); var result = relationDao.saveRelation(tenantId, relation); - publisher.publishEvent(EntityRelationEvent.from(relation)); + publishEvictEvent(EntityRelationEvent.from(relation)); return result; } @@ -123,18 +126,18 @@ public class BaseRelationService implements RelationService { log.trace("Executing saveRelationAsync [{}]", relation); validate(relation); var future = relationDao.saveRelationAsync(tenantId, relation); - future.addListener(() -> handleRelationEvictEvent(EntityRelationEvent.from(relation)), MoreExecutors.directExecutor()); + future.addListener(() -> handleEvictEvent(EntityRelationEvent.from(relation)), MoreExecutors.directExecutor()); return future; } @Transactional(propagation = Propagation.SUPPORTS) @Override public boolean deleteRelation(TenantId tenantId, EntityRelation relation) { - log.trace("Executing deleteRelation [{}]", relation); + log.trace("Executing DeleteRelation [{}]", relation); validate(relation); var result = relationDao.deleteRelation(tenantId, relation); //TODO: evict cache only if the relation was deleted. Note: relationDao.deleteRelation requires improvement. - publisher.publishEvent(EntityRelationEvent.from(relation)); + publishEvictEvent(EntityRelationEvent.from(relation)); return result; } @@ -143,7 +146,7 @@ public class BaseRelationService implements RelationService { log.trace("Executing deleteRelationAsync [{}]", relation); validate(relation); var future = relationDao.deleteRelationAsync(tenantId, relation); - future.addListener(() -> handleRelationEvictEvent(EntityRelationEvent.from(relation)), MoreExecutors.directExecutor()); + future.addListener(() -> handleEvictEvent(EntityRelationEvent.from(relation)), MoreExecutors.directExecutor()); return future; } @@ -154,7 +157,7 @@ public class BaseRelationService implements RelationService { validate(from, to, relationType, typeGroup); var result = relationDao.deleteRelation(tenantId, from, to, relationType, typeGroup); //TODO: evict cache only if the relation was deleted. Note: relationDao.deleteRelation requires improvement. - publisher.publishEvent(new EntityRelationEvent(from, to, relationType, typeGroup)); + publishEvictEvent(new EntityRelationEvent(from, to, relationType, typeGroup)); return result; } @@ -164,7 +167,7 @@ public class BaseRelationService implements RelationService { validate(from, to, relationType, typeGroup); var future = relationDao.deleteRelationAsync(tenantId, from, to, relationType, typeGroup); EntityRelationEvent event = new EntityRelationEvent(from, to, relationType, typeGroup); - future.addListener(() -> handleRelationEvictEvent(event), MoreExecutors.directExecutor()); + future.addListener(() -> handleEvictEvent(event), MoreExecutors.directExecutor()); return future; } @@ -245,17 +248,17 @@ public class BaseRelationService implements RelationService { if (deleteFromDb) { return Futures.transform(relationDao.deleteRelationAsync(tenantId, relation), bool -> { - handleRelationEvictEvent(EntityRelationEvent.from(relation)); + handleEvictEvent(EntityRelationEvent.from(relation)); return bool; }, MoreExecutors.directExecutor()); } else { - handleRelationEvictEvent(EntityRelationEvent.from(relation)); + handleEvictEvent(EntityRelationEvent.from(relation)); return Futures.immediateFuture(false); } } boolean delete(TenantId tenantId, EntityRelation relation, boolean deleteFromDb) { - publisher.publishEvent(EntityRelationEvent.from(relation)); + eventPublisher.publishEvent(EntityRelationEvent.from(relation)); if (deleteFromDb) { try { return relationDao.deleteRelation(tenantId, relation); @@ -592,4 +595,13 @@ public class BaseRelationService implements RelationService { } return relations; } + + private void publishEvictEvent(EntityRelationEvent event) { + if (TransactionSynchronizationManager.isActualTransactionActive()) { + eventPublisher.publishEvent(event); + } else { + handleEvictEvent(event); + } + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationCacheKey.java index 0df5e3e1c4..b60aa84b9b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationCacheKey.java @@ -19,6 +19,7 @@ import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import org.thingsboard.server.cache.CacheKeyUtil; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -45,26 +46,7 @@ public class RelationCacheKey implements Serializable { @Override public String toString() { - StringBuilder sb = new StringBuilder(); - boolean first = true; - first = addElement(sb, first, from); - first = addElement(sb, first, to); - first = addElement(sb, first, typeGroup); - first = addElement(sb, first, type); - first = addElement(sb, first, direction); - return sb.toString(); - } - - private boolean addElement(StringBuilder sb, boolean first, Object element) { - if (element != null) { - if (!first) { - sb.append("_"); - } - sb.append(element); - return false; - } else { - return first; - } + return CacheKeyUtil.toString(from, to, typeGroup, type, direction); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index e14e414ebb..1b27a2751f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -36,7 +36,8 @@ public abstract class DataValidator> { private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); - public void validate(D data, Function tenantIdFunction) { + // Returns old instance of the same object that is fetched during validation. + public D validate(D data, Function tenantIdFunction) { try { if (data == null) { throw new DataValidationException("Data object can't be null!"); @@ -46,11 +47,14 @@ public abstract class DataValidator> { TenantId tenantId = tenantIdFunction.apply(data); validateDataImpl(tenantId, data); + D old; if (data.getId() == null) { validateCreate(tenantId, data); + old = null; } else { - validateUpdate(tenantId, data); + old = validateUpdate(tenantId, data); } + return old; } catch (DataValidationException e) { log.error("Data object is invalid: [{}]", e.getMessage()); throw e; @@ -63,7 +67,8 @@ public abstract class DataValidator> { protected void validateCreate(TenantId tenantId, D data) { } - protected void validateUpdate(TenantId tenantId, D data) { + protected D validateUpdate(TenantId tenantId, D data) { + return null; } protected boolean isSameData(D existentData, D actualData) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java index c1485cfb1a..8033688787 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java @@ -39,13 +39,14 @@ public class AdminSettingsDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, AdminSettings adminSettings) { + protected AdminSettings validateUpdate(TenantId tenantId, AdminSettings adminSettings) { AdminSettings existentAdminSettings = adminSettingsService.findAdminSettingsById(tenantId, adminSettings.getId()); if (existentAdminSettings != null) { if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) { throw new DataValidationException("Changing key of admin settings entry is prohibited!"); } } + return existentAdminSettings; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java index 57d39ee781..bc03a2131b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java @@ -67,7 +67,7 @@ public class AssetDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, Asset asset) { + protected Asset validateUpdate(TenantId tenantId, Asset asset) { Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId()); if (old == null) { throw new DataValidationException("Can't update non existing asset!"); @@ -75,6 +75,7 @@ public class AssetDataValidator extends DataValidator { if (!old.getName().equals(asset.getName())) { cacheManager.removeAssetFromCacheByName(tenantId, old.getName()); } + return old; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java index d558c19e9e..b5d0247e28 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java @@ -30,7 +30,8 @@ public class ClientRegistrationTemplateDataValidator extends DataValidator { @@ -59,14 +61,16 @@ public class CustomerDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, Customer customer) { - customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( + protected Customer validateUpdate(TenantId tenantId, Customer customer) { + Optional customerOpt = customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()); + customerOpt.ifPresent( c -> { if (!c.getId().equals(customer.getId())) { throw new DataValidationException("Customer with such title already exists!"); } } ); + return customerOpt.orElse(null); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java index 5fca062d46..cc7e488df7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java @@ -46,7 +46,7 @@ public class DeviceCredentialsDataValidator extends DataValidator { @Autowired private OtaPackageService otaPackageService; - @Autowired - private EntitiesCacheManager cacheManager; - @Override protected void validateCreate(TenantId tenantId, Device device) { DefaultTenantProfileConfiguration profileConfiguration = @@ -73,15 +70,12 @@ public class DeviceDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, Device device) { + protected Device validateUpdate(TenantId tenantId, Device device) { Device old = deviceDao.findById(device.getTenantId(), device.getId().getId()); if (old == null) { throw new DataValidationException("Can't update non existing device!"); } - if (!old.getName().equals(device.getName())) { - cacheManager.removeDeviceFromCacheByName(tenantId, old.getName()); - cacheManager.removeDeviceFromCacheById(tenantId, device.getId()); - } + return old; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index adc8cf9fb1..e67d4f9b35 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -236,7 +236,7 @@ public class DeviceProfileDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, DeviceProfile deviceProfile) { + protected DeviceProfile validateUpdate(TenantId tenantId, DeviceProfile deviceProfile) { DeviceProfile old = deviceProfileDao.findById(deviceProfile.getTenantId(), deviceProfile.getId().getId()); if (old == null) { throw new DataValidationException("Can't update non existing device profile!"); @@ -255,6 +255,7 @@ public class DeviceProfileDataValidator extends DataValidator { throw new DataValidationException(message); } } + return old; } private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java index be01ec5c9b..490da123e6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java @@ -46,11 +46,12 @@ public class EdgeDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, Edge edge) { + protected Edge validateUpdate(TenantId tenantId, Edge edge) { Edge old = edgeDao.findById(edge.getTenantId(), edge.getId().getId()); if (!old.getName().equals(edge.getName())) { cacheManager.removeEdgeFromCacheByName(tenantId, old.getName()); } + return old; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java index a62e1afc9f..a38850c63c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java @@ -48,13 +48,14 @@ public class EntityViewDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, EntityView entityView) { - entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) - .ifPresent(e -> { - if (!e.getUuidId().equals(entityView.getUuidId())) { - throw new DataValidationException("Entity view with such name already exists!"); - } - }); + protected EntityView validateUpdate(TenantId tenantId, EntityView entityView) { + var opt = entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()); + opt.ifPresent(e -> { + if (!e.getUuidId().equals(entityView.getUuidId())) { + throw new DataValidationException("Entity view with such name already exists!"); + } + }); + return opt.orElse(null); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java index 8b261ad226..90db02d45e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java @@ -86,7 +86,7 @@ public class OtaPackageDataValidator extends BaseOtaPackageDataValidator { } @Override - protected void validateUpdate(TenantId tenantId, Tenant tenant) { + protected Tenant validateUpdate(TenantId tenantId, Tenant tenant) { Tenant old = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId()); if (old == null) { throw new DataValidationException("Can't update non existing tenant!"); } validateTenantProfile(tenantId, tenant); + return old; } private void validateTenantProfile(TenantId tenantId, Tenant tenant) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java index e8207534be..4c5e300966 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java @@ -56,7 +56,7 @@ public class TenantProfileDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, TenantProfile tenantProfile) { + protected TenantProfile validateUpdate(TenantId tenantId, TenantProfile tenantProfile) { TenantProfile old = tenantProfileDao.findById(TenantId.SYS_TENANT_ID, tenantProfile.getId().getId()); if (old == null) { throw new DataValidationException("Can't update non existing tenant profile!"); @@ -65,5 +65,6 @@ public class TenantProfileDataValidator extends DataValidator { } else if (old.isIsolatedTbCore() != tenantProfile.isIsolatedTbCore()) { throw new DataValidationException("Can't update isolatedTbCore property!"); } + return old; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java index ec3be14866..93a1766546 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java @@ -83,8 +83,8 @@ public class WidgetTypeDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { - WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId()); + protected WidgetTypeDetails validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + WidgetTypeDetails storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId()); if (!storedWidgetType.getTenantId().getId().equals(widgetTypeDetails.getTenantId().getId())) { throw new DataValidationException("Can't move existing widget type to different tenant!"); } @@ -94,5 +94,6 @@ public class WidgetTypeDataValidator extends DataValidator { if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) { throw new DataValidationException("Update of widget type alias is prohibited!"); } + return new WidgetTypeDetails(storedWidgetType); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java index d5b1a1149c..b140dfc79d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java @@ -69,7 +69,7 @@ public class WidgetsBundleDataValidator extends DataValidator { } @Override - protected void validateUpdate(TenantId tenantId, WidgetsBundle widgetsBundle) { + protected WidgetsBundle validateUpdate(TenantId tenantId, WidgetsBundle widgetsBundle) { WidgetsBundle storedWidgetsBundle = widgetsBundleDao.findById(tenantId, widgetsBundle.getId().getId()); if (!storedWidgetsBundle.getTenantId().getId().equals(widgetsBundle.getTenantId().getId())) { throw new DataValidationException("Can't move existing widgets bundle to different tenant!"); @@ -77,5 +77,6 @@ public class WidgetsBundleDataValidator extends DataValidator { if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) { throw new DataValidationException("Update of widgets bundle alias is prohibited!"); } + return storedWidgetsBundle; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java index 33cb7e1bc1..73b26df9e8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java @@ -19,6 +19,7 @@ import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -33,14 +34,33 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import java.io.IOException; +import java.text.ParseException; import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneOffset; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { + long timeBeforeStartTime; + long startTime; + long eventTime; + long endTime; + long timeAfterEndTime; + + @Before + public void before() throws ParseException { + timeBeforeStartTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T11:30:00Z").getTime(); + startTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T12:00:00Z").getTime(); + eventTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T12:30:00Z").getTime(); + endTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T13:00:00Z").getTime(); + timeAfterEndTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T13:30:30Z").getTime(); + } + @Test public void saveEdgeEvent() throws Exception { EdgeId edgeId = new EdgeId(Uuids.timeBased()); @@ -75,15 +95,8 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { return edgeEvent; } - @Test public void findEdgeEventsByTimeDescOrder() throws Exception { - long timeBeforeStartTime = LocalDateTime.of(2020, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC); - long startTime = LocalDateTime.of(2020, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC); - long eventTime = LocalDateTime.of(2020, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC); - long endTime = LocalDateTime.of(2020, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC); - long timeAfterEndTime = LocalDateTime.of(2020, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC); - EdgeId edgeId = new EdgeId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); @@ -113,6 +126,8 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { Assert.assertEquals(1, edgeEvents.getData().size()); Assert.assertEquals(Uuids.startOf(eventTime), edgeEvents.getData().get(0).getUuidId()); Assert.assertFalse(edgeEvents.hasNext()); + + edgeEventService.cleanupEvents(1); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationCacheTest.java index 9901438bdc..17dc127edb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationCacheTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationCacheTest.java @@ -97,7 +97,6 @@ public abstract class BaseRelationCacheTest extends AbstractServiceTest { relationService.getRelation(SYSTEM_TENANT_ID, ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON); relationService.getRelation(SYSTEM_TENANT_ID, ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON); - verify(relationDao, times(1)).getRelation(SYSTEM_TENANT_ID, ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON); - + verify(relationDao, times(2)).getRelation(SYSTEM_TENANT_ID, ENTITY_ID_FROM, ENTITY_ID_TO, RELATION_TYPE, RelationTypeGroup.COMMON); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java index 7d81e5afc6..f3f6dd8ff8 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java @@ -16,8 +16,9 @@ package org.thingsboard.server.dao.service.event; import com.datastax.oss.driver.api.core.uuid.Uuids; +import org.apache.commons.lang3.time.DateFormatUtils; import org.junit.Assert; -import org.junit.Ignore; +import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; @@ -31,14 +32,29 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.service.AbstractServiceTest; -import java.io.IOException; +import java.text.ParseException; import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneOffset; import java.util.Optional; -import java.util.concurrent.ExecutionException; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; public abstract class BaseEventServiceTest extends AbstractServiceTest { + long timeBeforeStartTime; + long startTime; + long eventTime; + long endTime; + long timeAfterEndTime; + + @Before + public void before() throws ParseException { + timeBeforeStartTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T11:30:00Z").getTime(); + startTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T12:00:00Z").getTime(); + eventTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T12:30:00Z").getTime(); + endTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T13:00:00Z").getTime(); + timeAfterEndTime = ISO_DATETIME_TIME_ZONE_FORMAT.parse("2016-11-01T13:30:30Z").getTime(); + } @Test public void saveEvent() throws Exception { @@ -55,18 +71,12 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { @Test public void findEventsByTypeAndTimeAscOrder() throws Exception { - long timeBeforeStartTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC); - long startTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC); - long eventTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC); - long endTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC); - long timeAfterEndTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC); - CustomerId customerId = new CustomerId(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); saveEventWithProvidedTime(timeBeforeStartTime, customerId, tenantId); Event savedEvent = saveEventWithProvidedTime(eventTime, customerId, tenantId); - Event savedEvent2 = saveEventWithProvidedTime(eventTime+1, customerId, tenantId); - Event savedEvent3 = saveEventWithProvidedTime(eventTime+2, customerId, tenantId); + Event savedEvent2 = saveEventWithProvidedTime(eventTime + 1, customerId, tenantId); + Event savedEvent3 = saveEventWithProvidedTime(eventTime + 2, customerId, tenantId); saveEventWithProvidedTime(timeAfterEndTime, customerId, tenantId); TimePageLink timePageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime"), startTime, endTime); @@ -86,22 +96,18 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { Assert.assertTrue(events.getData().size() == 1); Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent3.getUuidId())); Assert.assertFalse(events.hasNext()); + + eventService.cleanupEvents(timeBeforeStartTime - 1, timeAfterEndTime + 1, timeBeforeStartTime - 1, timeAfterEndTime + 1); } @Test public void findEventsByTypeAndTimeDescOrder() throws Exception { - long timeBeforeStartTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC); - long startTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC); - long eventTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC); - long endTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC); - long timeAfterEndTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC); - CustomerId customerId = new CustomerId(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); saveEventWithProvidedTime(timeBeforeStartTime, customerId, tenantId); Event savedEvent = saveEventWithProvidedTime(eventTime, customerId, tenantId); - Event savedEvent2 = saveEventWithProvidedTime(eventTime+1, customerId, tenantId); - Event savedEvent3 = saveEventWithProvidedTime(eventTime+2, customerId, tenantId); + Event savedEvent2 = saveEventWithProvidedTime(eventTime + 1, customerId, tenantId); + Event savedEvent3 = saveEventWithProvidedTime(eventTime + 2, customerId, tenantId); saveEventWithProvidedTime(timeAfterEndTime, customerId, tenantId); TimePageLink timePageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), startTime, endTime); @@ -121,6 +127,8 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { Assert.assertTrue(events.getData().size() == 1); Assert.assertTrue(events.getData().get(0).getUuidId().equals(savedEvent.getUuidId())); Assert.assertFalse(events.hasNext()); + + eventService.cleanupEvents(timeBeforeStartTime - 1, timeAfterEndTime + 1, timeBeforeStartTime - 1, timeAfterEndTime + 1); } private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws Exception {