Browse Source

Device Cache Impl

pull/6536/head
Andrii Shvaika 4 years ago
parent
commit
eb6458738d
  1. 8
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  2. 1
      application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java
  3. 41
      common/cache/src/main/java/org/thingsboard/server/cache/CacheKeyUtil.java
  4. 22
      common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java
  5. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java
  6. 5
      dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java
  7. 17
      dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java
  8. 51
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCacheKey.java
  9. 33
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCaffeineCache.java
  10. 30
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceEvent.java
  11. 49
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceRedisCache.java
  12. 150
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  13. 43
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractCachedEntityService.java
  14. 3
      dao/src/main/java/org/thingsboard/server/dao/entity/AbstractEntityService.java
  15. 46
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  16. 22
      dao/src/main/java/org/thingsboard/server/dao/relation/RelationCacheKey.java
  17. 11
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  18. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java
  19. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  20. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java
  21. 8
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
  22. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java
  23. 10
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
  24. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
  25. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java
  26. 15
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java
  27. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java
  28. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java
  29. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java
  30. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java
  31. 5
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
  32. 3
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java
  33. 29
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java
  34. 3
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationCacheTest.java
  35. 46
      dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java

8
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<Event> 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);

1
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;
/**

41
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;
}
}
}

22
common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java

@ -39,6 +39,28 @@ public interface TbTransactionalCache<K extends Serializable, V extends Serializ
TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys);
default V getAndPutInTransaction(K key, Supplier<V> dbCall, boolean cacheNullValue) {
TbCacheValueWrapper<V> 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> R getAndPutInTransaction(K key, Supplier<R> dbCall, Function<V, R> cacheValueToResult, Function<R, V> dbValueToCacheValue, boolean cacheNullValue) {
TbCacheValueWrapper<V> cacheValueWrapper = get(key);
if (cacheValueWrapper != null) {

4
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java

@ -28,5 +28,9 @@ public interface EdgeEventService {
PageData<EdgeEvent> 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);
}

5
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);

17
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);

51
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);
}
}

33
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<DeviceCacheKey, Device> {
public DeviceCaffeineCache(CacheManager cacheManager) {
super(cacheManager, CacheConstants.DEVICE_CACHE);
}
}

30
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;
}

49
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<DeviceCacheKey, Device> {
public DeviceRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new RedisSerializer<>() {
private final RedisSerializer<Object> 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);
}
});
}
}

150
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<DeviceCacheKey, Device, DeviceEvent> 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<Device> 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<Device> 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<DeviceCacheKey> 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<EntityView> 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<Device> 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<DeviceInfo> 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<Device> 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<Device> 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<DeviceInfo> 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<DeviceInfo> 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<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> 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<Device> 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<DeviceInfo> 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<Device> 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<DeviceInfo> 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<DeviceInfo> 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<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> 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<List<Device>> findDevicesByQuery(TenantId tenantId, DeviceSearchQuery query) {
ListenableFuture<List<EntityRelation>> 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<Device>, List<Device>>() {
devices = Futures.transform(devices, new Function<>() {
@Nullable
@Override
public List<Device> apply(@Nullable List<Device> deviceList) {
@ -517,6 +534,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
return devices;
}
@Transactional(propagation = Propagation.SUPPORTS)
@Override
public ListenableFuture<List<EntitySubtype>> 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<EntityView> 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<UUID> 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<Device> 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<Device> 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<TenantId, Device> tenantDevicesRemover =
new PaginatedRemover<TenantId, Device>() {
new PaginatedRemover<>() {
@Override
protected PageData<Device> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {

43
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<K extends Serializable, V extends Serializable, E> extends AbstractEntityService {
@Autowired
protected TbTransactionalCache<K, V> 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);
}

3
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;

46
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<RelationCacheKey, RelationCacheValue> cache;
private final ApplicationEventPublisher publisher;
private final ApplicationEventPublisher eventPublisher;
private final JpaExecutorService executor;
public BaseRelationService(RelationDao relationDao, @Lazy EntityService entityService,
TbTransactionalCache<RelationCacheKey, RelationCacheValue> 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<RelationCacheKey> 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);
}
}
}

22
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);
}
}

11
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -36,7 +36,8 @@ public abstract class DataValidator<D extends BaseData<?>> {
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<D, TenantId> tenantIdFunction) {
// Returns old instance of the same object that is fetched during validation.
public D validate(D data, Function<D, TenantId> tenantIdFunction) {
try {
if (data == null) {
throw new DataValidationException("Data object can't be null!");
@ -46,11 +47,14 @@ public abstract class DataValidator<D extends BaseData<?>> {
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<D extends BaseData<?>> {
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) {

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java

@ -39,13 +39,14 @@ public class AdminSettingsDataValidator extends DataValidator<AdminSettings> {
}
@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;
}

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java

@ -67,7 +67,7 @@ public class AssetDataValidator extends DataValidator<Asset> {
}
@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<Asset> {
if (!old.getName().equals(asset.getName())) {
cacheManager.removeAssetFromCacheByName(tenantId, old.getName());
}
return old;
}
@Override

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java

@ -30,7 +30,8 @@ public class ClientRegistrationTemplateDataValidator extends DataValidator<OAuth
}
@Override
protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
protected OAuth2ClientRegistrationTemplate validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
return null;
}
@Override

8
dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java

@ -31,6 +31,8 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.Optional;
@Component
public class CustomerDataValidator extends DataValidator<Customer> {
@ -59,14 +61,16 @@ public class CustomerDataValidator extends DataValidator<Customer> {
}
@Override
protected void validateUpdate(TenantId tenantId, Customer customer) {
customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
protected Customer validateUpdate(TenantId tenantId, Customer customer) {
Optional<Customer> 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

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java

@ -46,7 +46,7 @@ public class DeviceCredentialsDataValidator extends DataValidator<DeviceCredenti
}
@Override
protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) {
protected DeviceCredentials validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) {
if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) {
throw new DeviceCredentialsValidationException("Unable to update non-existent device credentials!");
}
@ -54,6 +54,7 @@ public class DeviceCredentialsDataValidator extends DataValidator<DeviceCredenti
if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) {
throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!");
}
return existingCredentials;
}
@Override

10
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java

@ -61,9 +61,6 @@ public class DeviceDataValidator extends DataValidator<Device> {
@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<Device> {
}
@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

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java

@ -236,7 +236,7 @@ public class DeviceProfileDataValidator extends DataValidator<DeviceProfile> {
}
@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<DeviceProfile> {
throw new DataValidationException(message);
}
}
return old;
}
private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java

@ -46,11 +46,12 @@ public class EdgeDataValidator extends DataValidator<Edge> {
}
@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

15
dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java

@ -48,13 +48,14 @@ public class EntityViewDataValidator extends DataValidator<EntityView> {
}
@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

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java

@ -86,7 +86,7 @@ public class OtaPackageDataValidator extends BaseOtaPackageDataValidator<OtaPack
}
@Override
protected void validateUpdate(TenantId tenantId, OtaPackage otaPackage) {
protected OtaPackage validateUpdate(TenantId tenantId, OtaPackage otaPackage) {
OtaPackage otaPackageOld = otaPackageDao.findById(tenantId, otaPackage.getUuidId());
validateUpdate(otaPackage, otaPackageOld);
@ -101,5 +101,6 @@ public class OtaPackageDataValidator extends BaseOtaPackageDataValidator<OtaPack
long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
}
return otaPackageOld;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java

@ -33,8 +33,9 @@ public class OtaPackageInfoDataValidator extends BaseOtaPackageDataValidator<Ota
}
@Override
protected void validateUpdate(TenantId tenantId, OtaPackageInfo otaPackage) {
protected OtaPackageInfo validateUpdate(TenantId tenantId, OtaPackageInfo otaPackage) {
OtaPackageInfo otaPackageOld = otaPackageInfoDao.findById(tenantId, otaPackage.getUuidId());
validateUpdate(otaPackage, otaPackageOld);
return otaPackageOld;
}
}

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java

@ -51,12 +51,13 @@ public class TenantDataValidator extends DataValidator<Tenant> {
}
@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) {

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java

@ -56,7 +56,7 @@ public class TenantProfileDataValidator extends DataValidator<TenantProfile> {
}
@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<TenantProfile> {
} else if (old.isIsolatedTbCore() != tenantProfile.isIsolatedTbCore()) {
throw new DataValidationException("Can't update isolatedTbCore property!");
}
return old;
}
}

5
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java

@ -83,8 +83,8 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
}
@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<WidgetTypeDetails> {
if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) {
throw new DataValidationException("Update of widget type alias is prohibited!");
}
return new WidgetTypeDetails(storedWidgetType);
}
}

3
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java

@ -69,7 +69,7 @@ public class WidgetsBundleDataValidator extends DataValidator<WidgetsBundle> {
}
@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<WidgetsBundle> {
if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) {
throw new DataValidationException("Update of widgets bundle alias is prohibited!");
}
return storedWidgetsBundle;
}
}

29
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

3
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);
}
}

46
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 {

Loading…
Cancel
Save