first-fix
This commit is contained in:
1 parent
6d87c1edc8
commit
565d9d673a
6284 files changed
+820267
-76
No files matched your search
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class CacheSpecs {
|
||||
private Integer timeToLiveInMinutes;
|
||||
private Integer maxSize;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.Map;
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "cache")
|
||||
@Data
|
||||
public class CacheSpecsMap {
|
||||
|
||||
@Value("${security.jwt.refreshTokenExpTime:604800}")
|
||||
private int refreshTokenExpTime;
|
||||
|
||||
@Getter
|
||||
private Map<String, CacheSpecs> specs;
|
||||
|
||||
@PostConstruct
|
||||
public void replaceTheJWTTokenRefreshExpTime() {
|
||||
if (specs != null) {
|
||||
var cacheSpecs = specs.get(CacheConstants.USERS_SESSION_INVALIDATION_CACHE);
|
||||
if (cacheSpecs != null) {
|
||||
cacheSpecs.setTimeToLiveInMinutes((refreshTokenExpTime / 60) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class CaffeineTbCacheTransaction<K extends Serializable, V extends Serializable> implements TbCacheTransaction<K, V> {
|
||||
@Getter
|
||||
private final UUID id = UUID.randomUUID();
|
||||
private final CaffeineTbTransactionalCache<K, V> cache;
|
||||
@Getter
|
||||
private final List<K> keys;
|
||||
@Getter
|
||||
@Setter
|
||||
private boolean failed;
|
||||
|
||||
private final Map<Object, Object> pendingPuts = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public void putIfAbsent(K key, V value) {
|
||||
pendingPuts.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit() {
|
||||
return cache.commit(id, pendingPuts);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
cache.rollback(id);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+193
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.cache.CacheManager;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public abstract class CaffeineTbTransactionalCache<K extends Serializable, V extends Serializable> implements TbTransactionalCache<K, V> {
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
@Getter
|
||||
private final String cacheName;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
private final Map<K, Set<UUID>> objectTransactions = new HashMap<>();
|
||||
private final Map<UUID, CaffeineTbCacheTransaction<K, V>> transactions = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public TbCacheValueWrapper<V> get(K key) {
|
||||
return SimpleTbCacheValueWrapper.wrap(cacheManager.getCache(cacheName).get(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
lock.lock();
|
||||
try {
|
||||
failAllTransactionsByKey(key);
|
||||
cacheManager.getCache(cacheName).put(key, value);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIfAbsent(K key, V value) {
|
||||
lock.lock();
|
||||
try {
|
||||
failAllTransactionsByKey(key);
|
||||
doPutIfAbsent(key, value);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(K key) {
|
||||
lock.lock();
|
||||
try {
|
||||
failAllTransactionsByKey(key);
|
||||
doEvict(key);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(Collection<K> keys) {
|
||||
lock.lock();
|
||||
try {
|
||||
keys.forEach(key -> {
|
||||
failAllTransactionsByKey(key);
|
||||
doEvict(key);
|
||||
});
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictOrPut(K key, V value) {
|
||||
//No need to put the value in case of Caffeine, because evict will cancel concurrent transaction used to "get" the missing value from cache.
|
||||
evict(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbCacheTransaction<K, V> newTransactionForKey(K key) {
|
||||
return newTransaction(Collections.singletonList(key));
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys) {
|
||||
return newTransaction(keys);
|
||||
}
|
||||
|
||||
void doPutIfAbsent(Object key, Object value) {
|
||||
cacheManager.getCache(cacheName).putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
void doEvict(K key) {
|
||||
cacheManager.getCache(cacheName).evict(key);
|
||||
}
|
||||
|
||||
TbCacheTransaction<K, V> newTransaction(List<K> keys) {
|
||||
lock.lock();
|
||||
try {
|
||||
var transaction = new CaffeineTbCacheTransaction<>(this, keys);
|
||||
var transactionId = transaction.getId();
|
||||
for (K key : keys) {
|
||||
objectTransactions.computeIfAbsent(key, k -> new HashSet<>()).add(transactionId);
|
||||
}
|
||||
transactions.put(transactionId, transaction);
|
||||
return transaction;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean commit(UUID trId, Map<Object, Object> pendingPuts) {
|
||||
lock.lock();
|
||||
try {
|
||||
var tr = transactions.get(trId);
|
||||
var success = !tr.isFailed();
|
||||
if (success) {
|
||||
for (K key : tr.getKeys()) {
|
||||
Set<UUID> otherTransactions = objectTransactions.get(key);
|
||||
if (otherTransactions != null) {
|
||||
for (UUID otherTrId : otherTransactions) {
|
||||
if (trId == null || !trId.equals(otherTrId)) {
|
||||
transactions.get(otherTrId).setFailed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pendingPuts.forEach(this::doPutIfAbsent);
|
||||
}
|
||||
removeTransaction(trId);
|
||||
return success;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void rollback(UUID id) {
|
||||
lock.lock();
|
||||
try {
|
||||
removeTransaction(id);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private void removeTransaction(UUID id) {
|
||||
CaffeineTbCacheTransaction<K, V> transaction = transactions.remove(id);
|
||||
if (transaction != null) {
|
||||
for (var key : transaction.getKeys()) {
|
||||
Set<UUID> transactions = objectTransactions.get(key);
|
||||
if (transactions != null) {
|
||||
transactions.remove(id);
|
||||
if (transactions.isEmpty()) {
|
||||
objectTransactions.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void failAllTransactionsByKey(K key) {
|
||||
Set<UUID> transactionsIds = objectTransactions.get(key);
|
||||
if (transactionsIds != null) {
|
||||
for (UUID otherTrId : transactionsIds) {
|
||||
transactions.get(otherTrId).setFailed(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class RedisTbCacheTransaction<K extends Serializable, V extends Serializable> implements TbCacheTransaction<K, V> {
|
||||
|
||||
private final RedisTbTransactionalCache<K, V> cache;
|
||||
private final RedisConnection connection;
|
||||
|
||||
@Override
|
||||
public void putIfAbsent(K key, V value) {
|
||||
cache.put(connection, key, value, RedisStringCommands.SetOption.UPSERT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean commit() {
|
||||
try {
|
||||
var execResult = connection.exec();
|
||||
var result = execResult != null && execResult.stream().anyMatch(Objects::nonNull);
|
||||
return result;
|
||||
} finally {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
try {
|
||||
connection.discard();
|
||||
} finally {
|
||||
connection.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.support.NullValue;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.RedisStringCommands;
|
||||
import org.springframework.data.redis.connection.jedis.JedisClusterConnection;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnection;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.types.Expiration;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.util.JedisClusterCRC16;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
public abstract class RedisTbTransactionalCache<K extends Serializable, V extends Serializable> implements TbTransactionalCache<K, V> {
|
||||
|
||||
private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE);
|
||||
static final JedisPool MOCK_POOL = new JedisPool(); //non-null pool required for JedisConnection to trigger closing jedis connection
|
||||
|
||||
@Getter
|
||||
private final String cacheName;
|
||||
private final JedisConnectionFactory connectionFactory;
|
||||
private final RedisSerializer<String> keySerializer = StringRedisSerializer.UTF_8;
|
||||
private final TbRedisSerializer<K, V> valueSerializer;
|
||||
private final Expiration evictExpiration;
|
||||
private final Expiration cacheTtl;
|
||||
|
||||
public RedisTbTransactionalCache(String cacheName,
|
||||
CacheSpecsMap cacheSpecsMap,
|
||||
RedisConnectionFactory connectionFactory,
|
||||
TBRedisCacheConfiguration configuration,
|
||||
TbRedisSerializer<K, V> valueSerializer) {
|
||||
this.cacheName = cacheName;
|
||||
this.connectionFactory = (JedisConnectionFactory) connectionFactory;
|
||||
this.valueSerializer = valueSerializer;
|
||||
this.evictExpiration = Expiration.from(configuration.getEvictTtlInMs(), TimeUnit.MILLISECONDS);
|
||||
this.cacheTtl = Optional.ofNullable(cacheSpecsMap)
|
||||
.map(CacheSpecsMap::getSpecs)
|
||||
.map(x -> x.get(cacheName))
|
||||
.map(CacheSpecs::getTimeToLiveInMinutes)
|
||||
.map(t -> Expiration.from(t, TimeUnit.MINUTES))
|
||||
.orElseGet(Expiration::persistent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbCacheValueWrapper<V> get(K key) {
|
||||
try (var connection = connectionFactory.getConnection()) {
|
||||
byte[] rawKey = getRawKey(key);
|
||||
byte[] rawValue = connection.get(rawKey);
|
||||
if (rawValue == null) {
|
||||
return null;
|
||||
} else if (Arrays.equals(rawValue, BINARY_NULL_VALUE)) {
|
||||
return SimpleTbCacheValueWrapper.empty();
|
||||
} else {
|
||||
V value = valueSerializer.deserialize(key, rawValue);
|
||||
return SimpleTbCacheValueWrapper.wrap(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(K key, V value) {
|
||||
try (var connection = connectionFactory.getConnection()) {
|
||||
put(connection, key, value, RedisStringCommands.SetOption.UPSERT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putIfAbsent(K key, V value) {
|
||||
try (var connection = connectionFactory.getConnection()) {
|
||||
put(connection, key, value, RedisStringCommands.SetOption.SET_IF_ABSENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(K key) {
|
||||
try (var connection = connectionFactory.getConnection()) {
|
||||
connection.del(getRawKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(Collection<K> keys) {
|
||||
//Redis expects at least 1 key to delete. Otherwise - ERR wrong number of arguments for 'del' command
|
||||
if (keys.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
try (var connection = connectionFactory.getConnection()) {
|
||||
connection.del(keys.stream().map(this::getRawKey).toArray(byte[][]::new));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictOrPut(K key, V value) {
|
||||
try (var connection = connectionFactory.getConnection()) {
|
||||
var rawKey = getRawKey(key);
|
||||
var records = connection.del(rawKey);
|
||||
if (records == null || records == 0) {
|
||||
//We need to put the value in case of Redis, because evict will NOT cancel concurrent transaction used to "get" the missing value from cache.
|
||||
connection.set(rawKey, getRawValue(value), evictExpiration, RedisStringCommands.SetOption.UPSERT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbCacheTransaction<K, V> newTransactionForKey(K key) {
|
||||
byte[][] rawKey = new byte[][]{getRawKey(key)};
|
||||
RedisConnection connection = watch(rawKey);
|
||||
return new RedisTbCacheTransaction<>(this, connection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TbCacheTransaction<K, V> newTransactionForKeys(List<K> keys) {
|
||||
RedisConnection connection = watch(keys.stream().map(this::getRawKey).toArray(byte[][]::new));
|
||||
return new RedisTbCacheTransaction<>(this, connection);
|
||||
}
|
||||
|
||||
private RedisConnection getConnection(byte[] rawKey) {
|
||||
if (!connectionFactory.isRedisClusterAware()) {
|
||||
return connectionFactory.getConnection();
|
||||
}
|
||||
RedisConnection connection = connectionFactory.getClusterConnection();
|
||||
|
||||
int slotNum = JedisClusterCRC16.getSlot(rawKey);
|
||||
Jedis jedis = ((JedisClusterConnection) connection).getNativeConnection().getConnectionFromSlot(slotNum);
|
||||
|
||||
JedisConnection jedisConnection = new JedisConnection(jedis, MOCK_POOL, jedis.getDB());
|
||||
jedisConnection.setConvertPipelineAndTxResults(connectionFactory.getConvertPipelineAndTxResults());
|
||||
|
||||
return jedisConnection;
|
||||
}
|
||||
|
||||
private RedisConnection watch(byte[][] rawKeysList) {
|
||||
RedisConnection connection = getConnection(rawKeysList[0]);
|
||||
try {
|
||||
connection.watch(rawKeysList);
|
||||
connection.multi();
|
||||
} catch (Exception e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
private byte[] getRawKey(K key) {
|
||||
String keyString = cacheName + key.toString();
|
||||
byte[] rawKey;
|
||||
try {
|
||||
rawKey = keySerializer.serialize(keyString);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to serialize the cache key: {}", key, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (rawKey == null) {
|
||||
log.warn("Failed to serialize the cache key: {}", key);
|
||||
throw new IllegalArgumentException("Failed to serialize the cache key!");
|
||||
}
|
||||
return rawKey;
|
||||
}
|
||||
|
||||
private byte[] getRawValue(V value) {
|
||||
if (value == null) {
|
||||
return BINARY_NULL_VALUE;
|
||||
} else {
|
||||
try {
|
||||
return valueSerializer.serialize(value);
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to serialize the cache value: {}", value, e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void put(RedisConnection connection, K key, V value, RedisStringCommands.SetOption setOption) {
|
||||
byte[] rawKey = getRawKey(key);
|
||||
byte[] rawValue = getRawValue(value);
|
||||
connection.set(rawKey, rawValue, cacheTtl, setOption);
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import org.springframework.cache.Cache;
|
||||
|
||||
@ToString
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class SimpleTbCacheValueWrapper<T> implements TbCacheValueWrapper<T> {
|
||||
|
||||
private final T value;
|
||||
|
||||
@Override
|
||||
public T get() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public static <T> SimpleTbCacheValueWrapper<T> empty() {
|
||||
return new SimpleTbCacheValueWrapper<>(null);
|
||||
}
|
||||
|
||||
public static <T> SimpleTbCacheValueWrapper<T> wrap(T value) {
|
||||
return new SimpleTbCacheValueWrapper<>(value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> SimpleTbCacheValueWrapper<T> wrap(Cache.ValueWrapper source) {
|
||||
return source == null ? null : new SimpleTbCacheValueWrapper<>((T) source.get());
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.ConverterRegistry;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.util.Assert;
|
||||
import org.thingsboard.server.common.data.id.EntityId;
|
||||
import redis.clients.jedis.JedisPoolConfig;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@EnableCaching
|
||||
@Data
|
||||
public abstract class TBRedisCacheConfiguration {
|
||||
|
||||
@Value("${redis.evictTtlInMs:60000}")
|
||||
private int evictTtlInMs;
|
||||
|
||||
@Value("${redis.pool_config.maxTotal:128}")
|
||||
private int maxTotal;
|
||||
|
||||
@Value("${redis.pool_config.maxIdle:128}")
|
||||
private int maxIdle;
|
||||
|
||||
@Value("${redis.pool_config.minIdle:16}")
|
||||
private int minIdle;
|
||||
|
||||
@Value("${redis.pool_config.testOnBorrow:true}")
|
||||
private boolean testOnBorrow;
|
||||
|
||||
@Value("${redis.pool_config.testOnReturn:true}")
|
||||
private boolean testOnReturn;
|
||||
|
||||
@Value("${redis.pool_config.testWhileIdle:true}")
|
||||
private boolean testWhileIdle;
|
||||
|
||||
@Value("${redis.pool_config.minEvictableMs:60000}")
|
||||
private long minEvictableMs;
|
||||
|
||||
@Value("${redis.pool_config.evictionRunsMs:30000}")
|
||||
private long evictionRunsMs;
|
||||
|
||||
@Value("${redis.pool_config.maxWaitMills:60000}")
|
||||
private long maxWaitMills;
|
||||
|
||||
@Value("${redis.pool_config.numberTestsPerEvictionRun:3}")
|
||||
private int numberTestsPerEvictionRun;
|
||||
|
||||
@Value("${redis.pool_config.blockWhenExhausted:true}")
|
||||
private boolean blockWhenExhausted;
|
||||
|
||||
@Bean
|
||||
public RedisConnectionFactory redisConnectionFactory() {
|
||||
return loadFactory();
|
||||
}
|
||||
|
||||
protected abstract JedisConnectionFactory loadFactory();
|
||||
|
||||
/**
|
||||
* Transaction aware RedisCacheManager.
|
||||
* Enable RedisCaches to synchronize cache put/evict operations with ongoing Spring-managed transactions.
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager(RedisConnectionFactory cf) {
|
||||
DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService();
|
||||
RedisCacheConfiguration.registerDefaultConverters(redisConversionService);
|
||||
registerDefaultConverters(redisConversionService);
|
||||
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().withConversionService(redisConversionService);
|
||||
return RedisCacheManager.builder(cf).cacheDefaults(configuration)
|
||||
.transactionAware()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate() {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(redisConnectionFactory());
|
||||
return template;
|
||||
}
|
||||
|
||||
private static void registerDefaultConverters(ConverterRegistry registry) {
|
||||
Assert.notNull(registry, "ConverterRegistry must not be null!");
|
||||
registry.addConverter(EntityId.class, String.class, EntityId::toString);
|
||||
}
|
||||
|
||||
protected JedisPoolConfig buildPoolConfig() {
|
||||
final JedisPoolConfig poolConfig = new JedisPoolConfig();
|
||||
poolConfig.setMaxTotal(maxTotal);
|
||||
poolConfig.setMaxIdle(maxIdle);
|
||||
poolConfig.setMinIdle(minIdle);
|
||||
poolConfig.setTestOnBorrow(testOnBorrow);
|
||||
poolConfig.setTestOnReturn(testOnReturn);
|
||||
poolConfig.setTestWhileIdle(testWhileIdle);
|
||||
poolConfig.setSoftMinEvictableIdleTime(Duration.ofMillis(minEvictableMs));
|
||||
poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(evictionRunsMs));
|
||||
poolConfig.setMaxWaitMillis(maxWaitMills);
|
||||
poolConfig.setNumTestsPerEvictionRun(numberTestsPerEvictionRun);
|
||||
poolConfig.setBlockWhenExhausted(blockWhenExhausted);
|
||||
return poolConfig;
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisClusterConfiguration;
|
||||
import org.springframework.data.redis.connection.RedisNode;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
import org.thingsboard.server.common.data.StringUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(TbCaffeineCacheConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "cluster")
|
||||
public class TBRedisClusterConfiguration extends TBRedisCacheConfiguration {
|
||||
|
||||
private static final String COMMA = ",";
|
||||
private static final String COLON = ":";
|
||||
|
||||
@Value("${redis.cluster.nodes:}")
|
||||
private String clusterNodes;
|
||||
|
||||
@Value("${redis.cluster.max-redirects:12}")
|
||||
private Integer maxRedirects;
|
||||
|
||||
@Value("${redis.cluster.useDefaultPoolConfig:true}")
|
||||
private boolean useDefaultPoolConfig;
|
||||
|
||||
@Value("${redis.password:}")
|
||||
private String password;
|
||||
|
||||
public JedisConnectionFactory loadFactory() {
|
||||
RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration();
|
||||
clusterConfiguration.setClusterNodes(getNodes(clusterNodes));
|
||||
clusterConfiguration.setMaxRedirects(maxRedirects);
|
||||
clusterConfiguration.setPassword(password);
|
||||
if (useDefaultPoolConfig) {
|
||||
return new JedisConnectionFactory(clusterConfiguration);
|
||||
} else {
|
||||
return new JedisConnectionFactory(clusterConfiguration, buildPoolConfig());
|
||||
}
|
||||
}
|
||||
|
||||
private List<RedisNode> getNodes(String nodes) {
|
||||
List<RedisNode> result;
|
||||
if (StringUtils.isBlank(nodes)) {
|
||||
result = Collections.emptyList();
|
||||
} else {
|
||||
result = new ArrayList<>();
|
||||
for (String hostPort : nodes.split(COMMA)) {
|
||||
String host = hostPort.split(COLON)[0];
|
||||
Integer port = Integer.valueOf(hostPort.split(COLON)[1]);
|
||||
result.add(new RedisNode(host, port));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
|
||||
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnMissingBean(TbCaffeineCacheConfiguration.class)
|
||||
@ConditionalOnProperty(prefix = "redis.connection", value = "type", havingValue = "standalone")
|
||||
public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration {
|
||||
|
||||
@Value("${redis.standalone.host:localhost}")
|
||||
private String host;
|
||||
|
||||
@Value("${redis.standalone.port:6379}")
|
||||
private Integer port;
|
||||
|
||||
@Value("${redis.standalone.clientName:standalone}")
|
||||
private String clientName;
|
||||
|
||||
@Value("${redis.standalone.connectTimeout:30000}")
|
||||
private Long connectTimeout;
|
||||
|
||||
@Value("${redis.standalone.readTimeout:60000}")
|
||||
private Long readTimeout;
|
||||
|
||||
@Value("${redis.standalone.useDefaultClientConfig:true}")
|
||||
private boolean useDefaultClientConfig;
|
||||
|
||||
@Value("${redis.standalone.usePoolConfig:false}")
|
||||
private boolean usePoolConfig;
|
||||
|
||||
@Value("${redis.db:0}")
|
||||
private Integer db;
|
||||
|
||||
@Value("${redis.password:}")
|
||||
private String password;
|
||||
|
||||
public JedisConnectionFactory loadFactory() {
|
||||
RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration();
|
||||
standaloneConfiguration.setHostName(host);
|
||||
standaloneConfiguration.setPort(port);
|
||||
standaloneConfiguration.setDatabase(db);
|
||||
standaloneConfiguration.setPassword(password);
|
||||
if (useDefaultClientConfig) {
|
||||
return new JedisConnectionFactory(standaloneConfiguration);
|
||||
} else {
|
||||
return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig());
|
||||
}
|
||||
}
|
||||
|
||||
private JedisClientConfiguration buildClientConfig() {
|
||||
if (usePoolConfig) {
|
||||
return JedisClientConfiguration.builder()
|
||||
.clientName(clientName)
|
||||
.connectTimeout(Duration.ofMillis(connectTimeout))
|
||||
.readTimeout(Duration.ofMillis(readTimeout))
|
||||
.usePooling().poolConfig(buildPoolConfig())
|
||||
.build();
|
||||
} else {
|
||||
return JedisClientConfiguration.builder()
|
||||
.clientName(clientName)
|
||||
.connectTimeout(Duration.ofMillis(connectTimeout))
|
||||
.readTimeout(Duration.ofMillis(readTimeout)).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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 interface TbCacheTransaction<K, V> {
|
||||
|
||||
void putIfAbsent(K key, V value);
|
||||
|
||||
boolean commit();
|
||||
|
||||
void rollback();
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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 interface TbCacheValueWrapper<T> {
|
||||
|
||||
T get();
|
||||
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.github.benmanes.caffeine.cache.RemovalCause;
|
||||
import com.github.benmanes.caffeine.cache.Ticker;
|
||||
import com.github.benmanes.caffeine.cache.Weigher;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
import org.springframework.cache.support.SimpleCacheManager;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Configuration
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
@EnableCaching
|
||||
@Slf4j
|
||||
public class TbCaffeineCacheConfiguration {
|
||||
|
||||
private final CacheSpecsMap configuration;
|
||||
|
||||
public TbCaffeineCacheConfiguration(CacheSpecsMap configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transaction aware CaffeineCache implementation with TransactionAwareCacheManagerProxy
|
||||
* to synchronize cache put/evict operations with ongoing Spring-managed transactions.
|
||||
*/
|
||||
@Bean
|
||||
public CacheManager cacheManager() {
|
||||
log.trace("Initializing cache: {} specs {}", Arrays.toString(RemovalCause.values()), configuration.getSpecs());
|
||||
SimpleCacheManager manager = new SimpleCacheManager();
|
||||
if (configuration.getSpecs() != null) {
|
||||
List<CaffeineCache> caches =
|
||||
configuration.getSpecs().entrySet().stream()
|
||||
.map(entry -> buildCache(entry.getKey(),
|
||||
entry.getValue()))
|
||||
.collect(Collectors.toList());
|
||||
manager.setCaches(caches);
|
||||
}
|
||||
|
||||
//SimpleCacheManager is not a bean (will be wrapped), so call initializeCaches manually
|
||||
manager.initializeCaches();
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) {
|
||||
|
||||
final Caffeine<Object, Object> caffeineBuilder
|
||||
= Caffeine.newBuilder()
|
||||
.weigher(collectionSafeWeigher())
|
||||
.maximumWeight(cacheSpec.getMaxSize())
|
||||
.expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES)
|
||||
.ticker(ticker());
|
||||
return new CaffeineCache(name, caffeineBuilder.build());
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Ticker ticker() {
|
||||
return Ticker.systemTicker();
|
||||
}
|
||||
|
||||
private Weigher<? super Object, ? super Object> collectionSafeWeigher() {
|
||||
return (Weigher<Object, Object>) (key, value) -> {
|
||||
if (value instanceof Collection) {
|
||||
return ((Collection) value).size();
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.thingsboard.server.common.data.FSTUtils;
|
||||
|
||||
public class TbFSTRedisSerializer<K, V> implements TbRedisSerializer<K, V> {
|
||||
|
||||
@Override
|
||||
public byte[] serialize(V value) throws SerializationException {
|
||||
return FSTUtils.encode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V deserialize(K key, byte[] bytes) throws SerializationException {
|
||||
return FSTUtils.decode(bytes);
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import org.springframework.data.redis.serializer.SerializationException;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public interface TbRedisSerializer<K, T> {
|
||||
|
||||
@Nullable
|
||||
byte[] serialize(@Nullable T t) throws SerializationException;
|
||||
|
||||
@Nullable
|
||||
T deserialize(K key, @Nullable byte[] bytes) throws SerializationException;
|
||||
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public interface TbTransactionalCache<K extends Serializable, V extends Serializable> {
|
||||
|
||||
String getCacheName();
|
||||
|
||||
TbCacheValueWrapper<V> get(K key);
|
||||
|
||||
void put(K key, V value);
|
||||
|
||||
void putIfAbsent(K key, V value);
|
||||
|
||||
void evict(K key);
|
||||
|
||||
void evict(Collection<K> keys);
|
||||
|
||||
void evictOrPut(K key, V value);
|
||||
|
||||
TbCacheTransaction<K, V> newTransactionForKey(K key);
|
||||
|
||||
/**
|
||||
* Note that all keys should be in the same cache slot for redis. You may control the cache slot using '{}' bracers.
|
||||
* See CLUSTER KEYSLOT command for more details.
|
||||
* @param keys - list of keys to use
|
||||
* @return transaction object
|
||||
*/
|
||||
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) {
|
||||
var cacheValue = cacheValueWrapper.get();
|
||||
return cacheValue == null ? null : cacheValueToResult.apply(cacheValue);
|
||||
}
|
||||
var cacheTransaction = newTransactionForKey(key);
|
||||
try {
|
||||
R dbValue = dbCall.get();
|
||||
if (dbValue != null || cacheNullValue) {
|
||||
cacheTransaction.putIfAbsent(key, dbValueToCacheValue.apply(dbValue));
|
||||
cacheTransaction.commit();
|
||||
return dbValue;
|
||||
} else {
|
||||
cacheTransaction.rollback();
|
||||
return null;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
cacheTransaction.rollback();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.device;
|
||||
|
||||
import lombok.Data;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.id.TenantId;
|
||||
|
||||
@Data
|
||||
public class DeviceCacheEvictEvent {
|
||||
|
||||
private final TenantId tenantId;
|
||||
private final DeviceId deviceId;
|
||||
private final String newName;
|
||||
private final String oldName;
|
||||
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.device;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
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() {
|
||||
if (deviceId != null) {
|
||||
return tenantId + "_" + deviceId;
|
||||
} else {
|
||||
return tenantId + "_n_" + deviceName;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.device;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.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.RedisTbTransactionalCache;
|
||||
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
|
||||
import org.thingsboard.server.cache.TbFSTRedisSerializer;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
import org.thingsboard.server.common.data.Device;
|
||||
|
||||
@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 TbFSTRedisSerializer<>());
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.ota;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_DATA_CACHE;
|
||||
|
||||
@Service
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
@RequiredArgsConstructor
|
||||
public class CaffeineOtaPackageCache implements OtaPackageDataCache {
|
||||
|
||||
private final CacheManager cacheManager;
|
||||
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
return get(key, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] get(String key, int chunkSize, int chunk) {
|
||||
byte[] data = cacheManager.getCache(OTA_PACKAGE_DATA_CACHE).get(key, byte[].class);
|
||||
|
||||
if (chunkSize < 1) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (data != null && data.length > 0) {
|
||||
int startIndex = chunkSize * chunk;
|
||||
|
||||
int size = Math.min(data.length - startIndex, chunkSize);
|
||||
|
||||
if (startIndex < data.length && size > 0) {
|
||||
byte[] result = new byte[size];
|
||||
System.arraycopy(data, startIndex, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, byte[] value) {
|
||||
cacheManager.getCache(OTA_PACKAGE_DATA_CACHE).putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(String key) {
|
||||
cacheManager.getCache(OTA_PACKAGE_DATA_CACHE).evict(key);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.ota;
|
||||
|
||||
public interface OtaPackageDataCache {
|
||||
|
||||
byte[] get(String key);
|
||||
|
||||
byte[] get(String key, int chunkSize, int chunk);
|
||||
|
||||
void put(String key, byte[] value);
|
||||
|
||||
void evict(String key);
|
||||
|
||||
default boolean has(String otaPackageId) {
|
||||
byte[] data = get(otaPackageId, 1, 0);
|
||||
return data != null && data.length > 0;
|
||||
}
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.ota;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.data.redis.connection.RedisConnection;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_DATA_CACHE;
|
||||
|
||||
@Service
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@RequiredArgsConstructor
|
||||
public class RedisOtaPackageDataCache implements OtaPackageDataCache {
|
||||
|
||||
private final RedisConnectionFactory redisConnectionFactory;
|
||||
|
||||
@Override
|
||||
public byte[] get(String key) {
|
||||
return get(key, 0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] get(String key, int chunkSize, int chunk) {
|
||||
try (RedisConnection connection = redisConnectionFactory.getConnection()) {
|
||||
if (chunkSize == 0) {
|
||||
return connection.get(toOtaPackageCacheKey(key));
|
||||
}
|
||||
|
||||
int startIndex = chunkSize * chunk;
|
||||
int endIndex = startIndex + chunkSize - 1;
|
||||
return connection.getRange(toOtaPackageCacheKey(key), startIndex, endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void put(String key, byte[] value) {
|
||||
try (RedisConnection connection = redisConnectionFactory.getConnection()) {
|
||||
connection.set(toOtaPackageCacheKey(key), value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evict(String key) {
|
||||
try (RedisConnection connection = redisConnectionFactory.getConnection()) {
|
||||
connection.del(toOtaPackageCacheKey(key));
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] toOtaPackageCacheKey(String key) {
|
||||
return String.format("%s::%s", OTA_PACKAGE_DATA_CACHE, key).getBytes();
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.usersUpdateTime;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.thingsboard.server.cache.CaffeineTbTransactionalCache;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true)
|
||||
@Service("UsersSessionInvalidation")
|
||||
public class UsersSessionInvalidationCaffeineCache extends CaffeineTbTransactionalCache<String, Long> {
|
||||
|
||||
@Autowired
|
||||
public UsersSessionInvalidationCaffeineCache(CacheManager cacheManager) {
|
||||
super(cacheManager, CacheConstants.USERS_SESSION_INVALIDATION_CACHE);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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.usersUpdateTime;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.RedisTbTransactionalCache;
|
||||
import org.thingsboard.server.cache.TBRedisCacheConfiguration;
|
||||
import org.thingsboard.server.cache.TbFSTRedisSerializer;
|
||||
import org.thingsboard.server.common.data.CacheConstants;
|
||||
|
||||
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis")
|
||||
@Service("UsersSessionInvalidation")
|
||||
public class UsersSessionInvalidationRedisCache extends RedisTbTransactionalCache<String, Long> {
|
||||
|
||||
@Autowired
|
||||
public UsersSessionInvalidationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) {
|
||||
super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>());
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright © 2016-2023 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;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cache.CacheManager;
|
||||
import org.springframework.cache.caffeine.CaffeineCache;
|
||||
import org.springframework.cache.support.SimpleCacheManager;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit.jupiter.SpringExtension;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(SpringExtension.class)
|
||||
@ContextConfiguration(classes = {CacheSpecsMap.class, TbCaffeineCacheConfiguration.class})
|
||||
@EnableConfigurationProperties
|
||||
@TestPropertySource(properties = {
|
||||
"cache.type=caffeine",
|
||||
"cache.specs.relations.timeToLiveInMinutes=1440",
|
||||
"cache.specs.relations.maxSize=0",
|
||||
"cache.specs.devices.timeToLiveInMinutes=60",
|
||||
"cache.specs.devices.maxSize=100"})
|
||||
@Slf4j
|
||||
public class CacheSpecsMapTest {
|
||||
|
||||
@Autowired
|
||||
CacheManager cacheManager;
|
||||
|
||||
@Test
|
||||
public void verifyNotTransactionAwareCacheManagerProxy() {
|
||||
// We no longer use built-in transaction support for the caches, because we have our own cache cleanup and transaction logic that implements CAS.
|
||||
assertThat(cacheManager).isInstanceOf(SimpleCacheManager.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithNoTransactionAwareCacheDecorator() {
|
||||
// We no longer use built-in transaction support for the caches, because we have our own cache cleanup and transaction logic that implements CAS.
|
||||
assertThat(cacheManager.getCache("relations")).isInstanceOf(CaffeineCache.class);
|
||||
assertThat(cacheManager.getCache("devices")).isInstanceOf(CaffeineCache.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenCacheConfig_whenCacheManagerReady_thenVerifyNonExistedCaches() {
|
||||
assertThat(cacheManager.getCache("rainbows_and_unicorns")).isNull();
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<configuration>
|
||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.thingsboard.server.cache" level="TRACE"/>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="console"/>
|
||||
</root>
|
||||
</configuration>
|
||||
Reference in new issue
Block a user