diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsCaffeineCache.java new file mode 100644 index 0000000000..c24be4dc04 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsCaffeineCache.java @@ -0,0 +1,34 @@ +/** + * 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.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.cache.CaffeineTbTransactionalCache; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("DeviceCredentialsCache") +public class DeviceCredentialsCaffeineCache extends CaffeineTbTransactionalCache { + + public DeviceCredentialsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.DEVICE_CREDENTIALS_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsEvictEvent.java new file mode 100644 index 0000000000..4c59dcacbf --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsEvictEvent.java @@ -0,0 +1,28 @@ +/** + * 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 +class DeviceCredentialsEvictEvent { + + private final String newCedentialsId; + private final String oldCredentialsId; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java new file mode 100644 index 0000000000..580b130d5e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java @@ -0,0 +1,36 @@ +/** + * 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.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.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.cache.RedisTbTransactionalCache; +import org.thingsboard.server.dao.cache.TbRedisSerializer; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("DeviceCredentialsCache") +public class DeviceCredentialsRedisCache extends RedisTbTransactionalCache { + + public DeviceCredentialsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 72ec300ab0..0c068aad6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -21,8 +21,10 @@ import org.eclipse.leshan.core.util.SecurityUtil; 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.stereotype.Service; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; @@ -40,7 +42,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.EncryptionUtil; -import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.service.DataValidator; @@ -51,7 +53,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service @Slf4j -public class DeviceCredentialsServiceImpl extends AbstractEntityService implements DeviceCredentialsService { +public class DeviceCredentialsServiceImpl extends AbstractCachedEntityService implements DeviceCredentialsService { @Autowired private DeviceCredentialsDao deviceCredentialsDao; @@ -59,6 +61,15 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen @Autowired private DataValidator credentialsValidator; + @TransactionalEventListener(classes = DeviceCredentialsEvictEvent.class) + @Override + public void handleEvictEvent(DeviceCredentialsEvictEvent event) { + cache.evict(event.getNewCedentialsId()); + if (StringUtils.isNotEmpty(event.getOldCredentialsId()) && !event.getNewCedentialsId().equals(event.getOldCredentialsId())) { + cache.evict(event.getOldCredentialsId()); + } + } + @Override public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceCredentialsByDeviceId [{}]", deviceId); @@ -67,19 +78,21 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } @Override - @Cacheable(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #credentialsId", unless = "#result == null") public DeviceCredentials findDeviceCredentialsByCredentialsId(String credentialsId) { log.trace("Executing findDeviceCredentialsByCredentialsId [{}]", credentialsId); validateString(credentialsId, "Incorrect credentialsId " + credentialsId); - return deviceCredentialsDao.findByCredentialsId(TenantId.SYS_TENANT_ID, credentialsId); + return cache.getAndPutInTransaction(credentialsId, + () -> deviceCredentialsDao.findByCredentialsId(TenantId.SYS_TENANT_ID, credentialsId), + false); } + @Transactional(propagation = Propagation.SUPPORTS) @Override - @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, keyGenerator = "previousDeviceCredentialsId", beforeInvocation = true) public DeviceCredentials updateDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { return saveOrUpdate(tenantId, deviceCredentials); } + @Transactional(propagation = Propagation.SUPPORTS) @Override public DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { return saveOrUpdate(tenantId, deviceCredentials); @@ -93,7 +106,13 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen log.trace("Executing updateDeviceCredentials [{}]", deviceCredentials); credentialsValidator.validate(deviceCredentials, id -> tenantId); try { - return deviceCredentialsDao.saveAndFlush(tenantId, deviceCredentials); + DeviceCredentials oldDeviceCredentials = null; + if (deviceCredentials.getDeviceId() != null) { + oldDeviceCredentials = deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()); + } + var value = deviceCredentialsDao.saveAndFlush(tenantId, deviceCredentials); + publishEvictEvent(new DeviceCredentialsEvictEvent(value.getCredentialsId(), oldDeviceCredentials != null ? oldDeviceCredentials.getCredentialsId() : null)); + return value; } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null @@ -182,8 +201,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen deviceCredentials.setCredentialsValue(JacksonUtil.toString(lwM2MCredentials)); X509ClientCredential x509ClientConfig = (X509ClientCredential) clientCredentials; if ((StringUtils.isNotBlank(x509ClientConfig.getCert()))) { - String sha3Hash = EncryptionUtil.getSha3Hash(x509ClientConfig.getCert()); - credentialsId = sha3Hash; + credentialsId = EncryptionUtil.getSha3Hash(x509ClientConfig.getCert()); } else { credentialsId = x509ClientConfig.getEndpoint(); } @@ -251,7 +269,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen throw new DeviceCredentialsValidationException("LwM2M client PSK key must be random sequence in hex encoding!"); } - if (pskKey.length()% 32 != 0 || pskKey.length() > 128) { + if (pskKey.length() % 32 != 0 || pskKey.length() > 128) { throw new DeviceCredentialsValidationException("LwM2M client PSK key length = " + pskKey.length() + ". Key must be HexDec format: 32, 64, 128 characters!"); } @@ -366,11 +384,12 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen } } + @Transactional(propagation = Propagation.SUPPORTS) @Override - @CacheEvict(cacheNames = DEVICE_CREDENTIALS_CACHE, key = "'deviceCredentials_' + #deviceCredentials.credentialsId") public void deleteDeviceCredentials(TenantId tenantId, DeviceCredentials deviceCredentials) { log.trace("Executing deleteDeviceCredentials [{}]", deviceCredentials); deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId()); + publishEvictEvent(new DeviceCredentialsEvictEvent(deviceCredentials.getCredentialsId(), null)); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java index 689752f0fe..91becb8c0f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.device.DeviceCredentialsDao; import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.device.DeviceService; import java.util.UUID; @@ -120,7 +121,10 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest when(deviceCredentialsDao.findById(SYSTEM_TENANT_ID, deviceCredentialsId)).thenReturn(createDummyDeviceCredentialsEntity(CREDENTIALS_ID_1)); when(deviceService.findDeviceById(SYSTEM_TENANT_ID, new DeviceId(deviceId))).thenReturn(new Device()); - deviceCredentialsService.updateDeviceCredentials(SYSTEM_TENANT_ID, createDummyDeviceCredentials(deviceCredentialsId, CREDENTIALS_ID_2, deviceId)); + var dummy = createDummyDeviceCredentials(deviceCredentialsId, CREDENTIALS_ID_2, deviceId); + when(deviceCredentialsDao.saveAndFlush(SYSTEM_TENANT_ID, dummy)).thenReturn(dummy); + + deviceCredentialsService.updateDeviceCredentials(SYSTEM_TENANT_ID, dummy); when(deviceCredentialsDao.findByCredentialsId(SYSTEM_TENANT_ID, CREDENTIALS_ID_1)).thenReturn(null);