Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 14 additions & 22 deletions server/src/main/java/org/eclipse/openvsx/cache/CacheConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@
import com.github.benmanes.caffeine.jcache.CacheManagerImpl;
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
import com.github.benmanes.caffeine.jcache.spi.CaffeineCachingProvider;
import io.micrometer.common.util.StringUtils;
import org.eclipse.openvsx.adapter.ExtensionQueryResult;
import org.eclipse.openvsx.cache.jedis.JedisUtil;
import org.eclipse.openvsx.entities.ExtensionVersion;
import org.eclipse.openvsx.json.ExtensionJson;
import org.eclipse.openvsx.json.NamespaceDetailsJson;
Expand All @@ -42,17 +42,13 @@
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.*;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.*;

import java.net.URI;
import java.time.Duration;
import java.util.List;
import java.util.OptionalLong;
import java.util.Properties;
import java.util.stream.Collectors;

import static org.eclipse.openvsx.cache.CacheService.*;

Expand Down Expand Up @@ -144,30 +140,26 @@ public Cache<Object, Object> settingCache(

@Bean
@ConditionalOnExpression("${bucket4j.enabled:false} && '${bucket4j.cache-to-use:}' == 'redis-jedis'")
public JedisPool jedisPool(RedisProperties properties) {
public RedisClient redisClient(RedisProperties properties) {
logger.info("Configure 'redis-jedis' bucket4j rate-limiting cache");
return new JedisPool(properties.getHost(), properties.getPort(), properties.getUsername(), properties.getPassword());
var builder = RedisClient.builder();

builder.hostAndPort(properties.getHost(), properties.getPort());
builder.clientConfig(JedisUtil.getClientConfig(properties));

return builder.build();
}

@Bean
@ConditionalOnExpression("${bucket4j.enabled:false} && '${bucket4j.cache-to-use:}' == 'redis-cluster-jedis'")
public JedisCluster jedisCluster(RedisProperties properties) {
public RedisClusterClient redisClusterClient(RedisProperties properties) {
logger.info("Configure 'redis-cluster-jedis' bucket4j rate-limiting cache");
var configBuilder = DefaultJedisClientConfig.builder();
var username = properties.getUsername();
if(StringUtils.isNotEmpty(username)) {
configBuilder.user(username);
}
var password = properties.getPassword();
if(StringUtils.isNotEmpty(password)) {
configBuilder.password(password);
}

var nodes = properties.getCluster().getNodes().stream()
.map(HostAndPort::from)
.collect(Collectors.toSet());
var builder = RedisClusterClient.builder();
builder.nodes(JedisUtil.getNodes(properties));
builder.clientConfig(JedisUtil.getClientConfig(properties));

return new JedisCluster(nodes, configBuilder.build());
return builder.build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,40 +27,46 @@
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.RedisClusterClient;

@Configuration
@ConditionalOnBucket4jEnabled
@ConditionalOnSynchronousPropertyCondition
@ConditionalOnClass(JedisBasedProxyManager.JedisBasedProxyManagerBuilder.class)
@ConditionalOnBean(JedisCluster.class)
@ConditionalOnBean(RedisClusterClient.class)
@ConditionalOnCache("redis-cluster-jedis")
public class JedisClusterBucket4jConfiguration {

public final JedisCluster jedisCluster;
public final RedisClusterClient redisClusterClient;
private final String configCacheName;

public JedisClusterBucket4jConfiguration(JedisCluster jedisCluster, Bucket4JBootProperties properties) {
this.jedisCluster = jedisCluster;
public JedisClusterBucket4jConfiguration(RedisClusterClient redisClusterClient, Bucket4JBootProperties properties) {
this.redisClusterClient = redisClusterClient;
this.configCacheName = properties.getFilterConfigCacheName();
}

@Bean
@ConditionalOnMissingBean(SyncCacheResolver.class)
public SyncCacheResolver bucket4RedisResolver() {
return new JedisClusterCacheResolver(jedisCluster);
return new JedisClusterCacheResolver(redisClusterClient);
}

@Bean
@ConditionalOnMissingBean(CacheManager.class)
@ConditionalOnFilterConfigCacheEnabled
public CacheManager<String, Bucket4JConfiguration> configCacheManager() {
return new JedisClusterCacheManager<>(jedisCluster, configCacheName, Bucket4JConfiguration.class);
return new JedisClusterCacheManager<>(redisClusterClient, configCacheName, Bucket4JConfiguration.class);
}

@Bean
@ConditionalOnFilterConfigCacheEnabled
public JedisClusterCacheListener<String, Bucket4JConfiguration> configCacheListener(ApplicationEventPublisher eventPublisher) {
return new JedisClusterCacheListener<>(jedisCluster, configCacheName, String.class, Bucket4JConfiguration.class, eventPublisher);
return new JedisClusterCacheListener<>(
redisClusterClient,
configCacheName,
String.class,
Bucket4JConfiguration.class,
eventPublisher
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.RedisClusterClient;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
Expand All @@ -33,20 +33,20 @@ public class JedisClusterCacheListener<K, V> extends JedisPubSub {

private static final Logger LOGGER = LoggerFactory.getLogger(JedisClusterCacheListener.class);

private final JedisCluster jedisCluster;
private final RedisClusterClient redisClusterClient;
private final ObjectMapper objectMapper = new ObjectMapper();
private final String updateChannel;
private final JavaType deserializeType;
private final ApplicationEventPublisher eventPublisher;

/**
* @param jedisCluster The cluster to use for listening/publishing events
* @param redisClusterClient The cluster to use for listening/publishing events
* @param cacheName The name of the cache. This is used as prefix for the event channels
* @param keyType The type of the key. This is required for parsing events and should match the K of this class.
* @param valueType The type of the value. This is required for parsing events and should match the V of this class.
*/
public JedisClusterCacheListener(JedisCluster jedisCluster, String cacheName, Class<K> keyType, Class<V> valueType, ApplicationEventPublisher eventPublisher) {
this.jedisCluster = jedisCluster;
public JedisClusterCacheListener(RedisClusterClient redisClusterClient, String cacheName, Class<K> keyType, Class<V> valueType, ApplicationEventPublisher eventPublisher) {
this.redisClusterClient = redisClusterClient;
this.updateChannel = cacheName.concat(":update");
this.deserializeType = objectMapper.getTypeFactory().constructParametricType(CacheUpdateEvent.class, keyType, valueType);
this.eventPublisher = eventPublisher;
Expand All @@ -66,7 +66,7 @@ public void subscribe() {
// This is done in a different thread since subscribe is a blocking call.
resetTask = executorService.schedule(()-> reconnectBackoffTimeMillis.set(1000), 10000, TimeUnit.MILLISECONDS);

jedisCluster.subscribe(this, updateChannel);
redisClusterClient.subscribe(this, updateChannel);
} catch (Exception e) {
LOGGER.error("Failed to connect the Jedis subscriber, attempting to reconnect in {} seconds. " +
"Exception was: {}", (reconnectBackoffTimeMillis.get() /1000), e.getMessage());
Expand All @@ -93,7 +93,7 @@ public void subscribe() {
}

private boolean isUp() {
return jedisCluster.ping().equals("PONG");
return redisClusterClient.ping().equals("PONG");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,25 +18,25 @@
import com.giffing.bucket4j.spring.boot.starter.config.cache.CacheUpdateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.RedisClusterClient;

public class JedisClusterCacheManager<K, V> implements CacheManager<K, V> {

private static final Logger LOGGER = LoggerFactory.getLogger(JedisClusterCacheManager.class);

private final JedisCluster cluster;
private final RedisClusterClient redisClusterClient;
private final String cacheName;
private final Class<V> valueType;
private final ObjectMapper objectMapper;
private final String updateChannel;

/**
* @param cluster The JedisCluster to use for reading/writing data to the cache
* @param redisClusterClient The RedisClusterClient to use for reading/writing data to the cache
* @param cacheName The name of the cache.
* @param valueType The type of the data. This is required for parsing and should always match the V of this class.
*/
public JedisClusterCacheManager(JedisCluster cluster, String cacheName, Class<V> valueType) {
this.cluster = cluster;
public JedisClusterCacheManager(RedisClusterClient redisClusterClient, String cacheName, Class<V> valueType) {
this.redisClusterClient = redisClusterClient;
this.cacheName = cacheName;
this.valueType = valueType;

Expand All @@ -48,7 +48,7 @@ public JedisClusterCacheManager(JedisCluster cluster, String cacheName, Class<V>
@Override
public V getValue(K key) {
try {
String serializedValue = cluster.hget(cacheName, objectMapper.writeValueAsString(key));
String serializedValue = redisClusterClient.hget(cacheName, objectMapper.writeValueAsString(key));
return serializedValue != null ? objectMapper.readValue(serializedValue, this.valueType) : null;
} catch (JsonProcessingException e) {
LOGGER.warn("Exception occurred while retrieving key '{}' from cache '{}'. Message: {}", key, cacheName, e.getMessage());
Expand All @@ -63,12 +63,12 @@ public void setValue(K key, V value) {

String serializedKey = objectMapper.writeValueAsString(key);
String serializedValue = objectMapper.writeValueAsString(value);
cluster.hset(this.cacheName, serializedKey, serializedValue);
redisClusterClient.hset(this.cacheName, serializedKey, serializedValue);

//publish an update event if the key already existed
if(oldValue != null){
CacheUpdateEvent<K,V> updateEvent = new CacheUpdateEvent<>(key, oldValue, value);
cluster.publish(this.updateChannel, objectMapper.writeValueAsString(updateEvent));
redisClusterClient.publish(this.updateChannel, objectMapper.writeValueAsString(updateEvent));
}
} catch (JsonProcessingException e) {
LOGGER.warn("Exception occurred while setting key '{}' in cache '{}'. Message: {}", key, cacheName, e.getMessage());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,20 @@
import com.giffing.bucket4j.spring.boot.starter.config.cache.SyncCacheResolver;
import io.github.bucket4j.distributed.ExpirationAfterWriteStrategy;
import io.github.bucket4j.distributed.proxy.AbstractProxyManager;
import io.github.bucket4j.distributed.proxy.ClientSideConfig;
import io.github.bucket4j.redis.jedis.cas.JedisBasedProxyManager;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.RedisClusterClient;

import java.time.Duration;

import static java.nio.charset.StandardCharsets.UTF_8;

public class JedisClusterCacheResolver extends AbstractCacheResolverTemplate<byte[]> implements SyncCacheResolver {

private final JedisCluster jedisCluster;
private final RedisClusterClient redisClusterClient;

public JedisClusterCacheResolver(JedisCluster jedisCluster) {
this.jedisCluster = jedisCluster;
public JedisClusterCacheResolver(RedisClusterClient redisClusterClient) {
this.redisClusterClient = redisClusterClient;
}

@Override
Expand All @@ -43,8 +44,13 @@ public byte[] castStringToCacheKey(String key) {

@Override
public AbstractProxyManager<byte[]> getProxyManager(String cacheName) {
return JedisBasedProxyManager.builderFor(jedisCluster)
.withExpirationStrategy(ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10)))
var clientSideConfig =
ClientSideConfig.getDefault().withExpirationAfterWriteStrategy(
ExpirationAfterWriteStrategy.basedOnTimeForRefillingBucketUpToMax(Duration.ofSeconds(10))
);

return JedisBasedProxyManager.builderFor(redisClusterClient)
.withClientSideConfig(clientSideConfig)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
import io.micrometer.core.instrument.util.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.RedisClusterClient;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
Expand All @@ -26,16 +26,16 @@
public abstract class JedisClusterChannelListener extends JedisPubSub {
private final Logger logger = LoggerFactory.getLogger(JedisClusterChannelListener.class);

private final JedisCluster jedisCluster;
private final RedisClusterClient redisClusterClient;
private final String channelName;
private final String listenerName;

// Redis subscriber state
private volatile Thread subscriberThread;
private volatile boolean running = true;

public JedisClusterChannelListener(JedisCluster jedisCluster, String channelName, String listenerName) {
this.jedisCluster = jedisCluster;
public JedisClusterChannelListener(RedisClusterClient redisClusterClient, String channelName, String listenerName) {
this.redisClusterClient = redisClusterClient;
this.channelName = channelName;
this.listenerName = listenerName;
}
Expand Down Expand Up @@ -68,7 +68,7 @@ private void subscribeLoop() {
try {
resetTask = executor.schedule(() -> backoffMs.set(1000), 10, TimeUnit.SECONDS);
logger.debug("Subscribing to redis channel {}", channelName);
jedisCluster.subscribe(this, channelName);
redisClusterClient.subscribe(this, channelName);
} catch (Exception e) {
if (!running) break;
logger.warn(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/******************************************************************************
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*****************************************************************************/
package org.eclipse.openvsx.cache.jedis;

import io.micrometer.common.util.StringUtils;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import redis.clients.jedis.DefaultJedisClientConfig;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.JedisClientConfig;

import java.util.Set;
import java.util.stream.Collectors;

public class JedisUtil {

private JedisUtil() {}

public static JedisClientConfig getClientConfig(RedisProperties properties) {
var configBuilder = DefaultJedisClientConfig.builder();
var username = properties.getUsername();
if (StringUtils.isNotEmpty(username)) {
configBuilder.user(username);
}
var password = properties.getPassword();
if (StringUtils.isNotEmpty(password)) {
configBuilder.password(password);
}

configBuilder.ssl(properties.getSsl().isEnabled());
return configBuilder.build();
}

public static Set<HostAndPort> getNodes(RedisProperties properties) {
return properties.getCluster().getNodes().stream()
.map(HostAndPort::from)
.collect(Collectors.toSet());
}
}
Loading