Skip to content

Commit b45545d

Browse files
committed
Modified Redis Heuristic Calculator to decouple it from RedisDB. Redis Handler now creates a generic structure with Redis data. Also Redis client is using redis-data no more. It now uses Lettuce with refflection
1 parent 059b63f commit b45545d

7 files changed

Lines changed: 324 additions & 159 deletions

File tree

client-java/controller/pom.xml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,6 @@
134134
<groupId>io.rest-assured</groupId>
135135
<artifactId>rest-assured</artifactId>
136136
</dependency>
137-
<dependency>
138-
<groupId>org.springframework.data</groupId>
139-
<artifactId>spring-data-redis</artifactId>
140-
</dependency>
141137
<dependency>
142138
<groupId>org.hamcrest</groupId>
143139
<artifactId>hamcrest-all</artifactId>

client-java/controller/src/main/java/org/evomaster/client/java/controller/internal/db/RedisHandler.java

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import org.evomaster.client.java.controller.internal.TaintHandlerExecutionTracer;
44
import org.evomaster.client.java.controller.redis.RedisClient;
55
import org.evomaster.client.java.controller.redis.RedisHeuristicsCalculator;
6+
import org.evomaster.client.java.controller.redis.RedisInfo;
67
import org.evomaster.client.java.instrumentation.RedisCommand;
7-
import org.springframework.data.redis.connection.RedisConnectionFactory;
8+
import org.evomaster.client.java.utils.SimpleLogger;
89

910
import java.util.*;
1011

@@ -77,17 +78,93 @@ public void handle(RedisCommand info) {
7778
public List<RedisCommandEvaluation> getEvaluatedRedisCommands() {
7879
operations.stream()
7980
.filter(command -> command.getType().shouldCalculateHeuristic())
80-
.forEach(redisInfo -> {
81-
RedisDistanceWithMetrics distanceWithMetrics = calculator.computeDistance(redisInfo, redisClient);
82-
evaluatedRedisCommands.add(new RedisCommandEvaluation(redisInfo, distanceWithMetrics));
81+
.forEach(redisCommand -> {
82+
RedisDistanceWithMetrics distanceWithMetrics = computeDistance(redisCommand, redisClient);
83+
evaluatedRedisCommands.add(new RedisCommandEvaluation(redisCommand, distanceWithMetrics));
8384
});
8485
operations.clear();
8586

8687
return evaluatedRedisCommands;
8788
}
8889

89-
public void setRedisClient(RedisConnectionFactory connectionFactory) {
90-
this.redisClient = new RedisClient(connectionFactory);
90+
private RedisDistanceWithMetrics computeDistance(RedisCommand redisCommand, RedisClient redisClient) {
91+
RedisCommand.RedisCommandType type = redisCommand.getType();
92+
try {
93+
switch (type) {
94+
case KEYS:
95+
case EXISTS: {
96+
List<RedisInfo> redisInfo = createRedisInfoForAllKeys(redisClient);
97+
return calculator.computeDistance(redisCommand, redisInfo);
98+
}
99+
100+
case GET: {
101+
List<RedisInfo> redisInfo = createRedisInfoForKeysByType("string", redisClient);
102+
return calculator.computeDistance(redisCommand, redisInfo);
103+
}
104+
105+
case HGET: {
106+
String field = redisCommand.extractArgs().get(1);
107+
List<RedisInfo> redisInfo = createRedisInfoForKeysByField(field, redisClient);
108+
return calculator.computeDistance(redisCommand, redisInfo);
109+
}
110+
111+
case HGETALL: {
112+
List<RedisInfo> redisInfo = createRedisInfoForKeysByType("hash", redisClient);
113+
return calculator.computeDistance(redisCommand, redisInfo);
114+
}
115+
116+
case SMEMBERS: {
117+
List<RedisInfo> redisInfo = createRedisInfoForKeysByType("set", redisClient);
118+
return calculator.computeDistance(redisCommand, redisInfo);
119+
}
120+
121+
case SINTER: {
122+
List<String> keys = redisCommand.extractArgs();
123+
List<RedisInfo> redisInfo = createRedisInfoForIntersection(keys, redisClient);
124+
return calculator.computeDistance(redisCommand, redisInfo);
125+
}
126+
127+
default:
128+
return new RedisDistanceWithMetrics(1d, 0);
129+
}
130+
} catch (Exception e) {
131+
SimpleLogger.warn("Could not compute distance for " + type + ": " + e.getMessage());
132+
return new RedisDistanceWithMetrics(1d, 0);
133+
}
134+
}
135+
136+
private List<RedisInfo> createRedisInfoForIntersection(List<String> keys, RedisClient redisClient) {
137+
List<RedisInfo> redisData = new ArrayList<>();
138+
keys.forEach(
139+
key -> redisData.add(new RedisInfo(key, redisClient.getType(key), redisClient.getSetMembers(key))
140+
));
141+
return redisData;
142+
}
143+
144+
private List<RedisInfo> createRedisInfoForAllKeys(RedisClient redisClient) {
145+
Set<String> keys = redisClient.getAllKeys();
146+
List<RedisInfo> redisData = new ArrayList<>();
147+
keys.forEach(
148+
key -> redisData.add(new RedisInfo(key))
149+
);
150+
return redisData;
91151
}
92152

153+
private List<RedisInfo> createRedisInfoForKeysByType(String type, RedisClient redisClient) {
154+
Set<String> keys = redisClient.getKeysByType(type);
155+
List<RedisInfo> redisData = new ArrayList<>();
156+
keys.forEach(key -> redisData.add(new RedisInfo(key)));
157+
return redisData;
158+
}
159+
160+
private List<RedisInfo> createRedisInfoForKeysByField(String field, RedisClient redisClient) {
161+
Set<String> keys = redisClient.getKeysByType("hash");
162+
List<RedisInfo> redisData = new ArrayList<>();
163+
keys.forEach(key -> redisData.add(new RedisInfo(key, redisClient.hashFieldExists(key, field))));
164+
return redisData;
165+
}
166+
167+
public void setRedisClient(RedisClient redisClient) {
168+
this.redisClient = redisClient;
169+
}
93170
}
Lines changed: 108 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,130 @@
11
package org.evomaster.client.java.controller.redis;
22

3-
import org.evomaster.client.java.instrumentation.RedisCommand;
4-
import org.springframework.data.redis.connection.RedisConnectionFactory;
5-
import org.springframework.data.redis.core.RedisTemplate;
6-
import org.springframework.data.redis.serializer.StringRedisSerializer;
7-
8-
import java.util.Set;
3+
import java.lang.reflect.Method;
4+
import java.util.*;
95
import java.util.stream.Collectors;
106

7+
/**
8+
* RedisClient that uses Lettuce dynamically via reflection, avoiding
9+
* compile-time dependency on Spring or Lettuce.
10+
*/
1111
public class RedisClient {
1212

13-
private final RedisTemplate<String, String> template;
13+
private final Object redisClient; // io.lettuce.core.RedisClient
14+
private final Object connection; // io.lettuce.core.api.StatefulRedisConnection
15+
private final Object syncCommands; // io.lettuce.core.api.sync.RedisCommands
16+
17+
public RedisClient(String host, int port) {
18+
try {
19+
Class<?> redisClientClass = Class.forName("io.lettuce.core.RedisClient");
20+
Class<?> redisURIClass = Class.forName("io.lettuce.core.RedisURI");
21+
22+
Method createUri = redisURIClass.getMethod("create", String.class);
23+
Object uri = createUri.invoke(null, "redis://" + host + ":" + port);
24+
25+
Method createClient = redisClientClass.getMethod("create", redisURIClass);
26+
this.redisClient = createClient.invoke(null, uri);
27+
28+
Method connectMethod = redisClientClass.getMethod("connect");
29+
this.connection = connectMethod.invoke(redisClient);
30+
31+
Class<?> statefulConnClass = Class.forName("io.lettuce.core.api.StatefulRedisConnection");
32+
Method syncMethod = statefulConnClass.getMethod("sync");
33+
this.syncCommands = syncMethod.invoke(connection);
1434

15-
public RedisClient(RedisConnectionFactory factory) {
16-
this.template = new RedisTemplate<>();
17-
this.template.setConnectionFactory(factory);
18-
StringRedisSerializer stringSerializer = new StringRedisSerializer();
19-
this.template.setKeySerializer(stringSerializer);
20-
this.template.setValueSerializer(stringSerializer);
21-
this.template.setHashKeySerializer(stringSerializer);
22-
this.template.setHashValueSerializer(stringSerializer);
23-
this.template.afterPropertiesSet();
35+
} catch (Exception e) {
36+
throw new RuntimeException("Failed to initialize Lettuce Redis client via reflection", e);
37+
}
2438
}
2539

26-
public Set<String> getKeysByType(String expectedType) {
27-
return template.keys("*").stream().filter(
28-
k -> template.type(k).name().equalsIgnoreCase(expectedType)
29-
).collect(Collectors.toSet());
40+
public void close() {
41+
try {
42+
if (connection != null) {
43+
Method close = connection.getClass().getMethod("close");
44+
close.invoke(connection);
45+
}
46+
if (redisClient != null) {
47+
Method shutdown = redisClient.getClass().getMethod("shutdown");
48+
shutdown.invoke(redisClient);
49+
}
50+
} catch (Exception ignored) {}
51+
}
52+
53+
/** Equivalent to SET key value */
54+
public void setValue(String key, String value) {
55+
invoke("set", key, value);
56+
}
57+
58+
/** Equivalent to GET key */
59+
public String getValue(String key) {
60+
return (String) invoke("get", key);
3061
}
3162

63+
/** Equivalent to KEYS * */
3264
public Set<String> getAllKeys() {
33-
return template.keys("*");
65+
Object result = invoke("keys", "*");
66+
if (result instanceof Collection)
67+
return new HashSet<>((Collection<String>) result);
68+
return Collections.emptySet();
3469
}
3570

36-
public String getType(String key){
37-
return String.valueOf(template.type(key));
71+
/** Equivalent to TYPE key */
72+
public String getType(String key) {
73+
Object result = invoke("type", key);
74+
return result != null ? result.toString() : null;
3875
}
3976

40-
public Boolean hashFieldExists(String k, String targetField){
41-
return template.opsForHash().hasKey(k, targetField);
77+
/** HSET key field value */
78+
public void hashSet(String key, String field, String value) {
79+
invoke("hset", key, field, value);
4280
}
4381

44-
public Set<String> getSetMembers(String key){
45-
return template.opsForSet().members(key);
82+
/** HEXISTS key field */
83+
public boolean hashFieldExists(String key, String field) {
84+
Object result = invoke("hexists", key, field);
85+
return result instanceof Boolean && (Boolean) result;
86+
}
87+
88+
/** SMEMBERS key */
89+
public Set<String> getSetMembers(String key) {
90+
Object result = invoke("smembers", key);
91+
if (result instanceof Collection)
92+
return new HashSet<>((Collection<String>) result);
93+
return Collections.emptySet();
94+
}
95+
96+
private Object invoke(String methodName, Object... args) {
97+
try {
98+
Class<?>[] argTypes = Arrays.stream(args)
99+
.map(Object::getClass)
100+
.toArray(Class<?>[]::new);
101+
102+
Method method = findMethod(syncCommands.getClass(), methodName, argTypes);
103+
if (method == null)
104+
throw new RuntimeException("Method not found: " + methodName);
105+
return method.invoke(syncCommands, args);
106+
107+
} catch (Exception e) {
108+
throw new RuntimeException("Error invoking Redis command: " + methodName, e);
109+
}
110+
}
111+
112+
private Method findMethod(Class<?> clazz, String name, Class<?>[] argTypes) {
113+
for (Method m : clazz.getMethods()) {
114+
if (!m.getName().equals(name)) continue;
115+
if (m.getParameterCount() != argTypes.length) continue;
116+
return m;
117+
}
118+
return null;
119+
}
120+
121+
public Set<String> getKeysByType(String expectedType) {
122+
return getAllKeys().stream()
123+
.filter(k -> expectedType.equalsIgnoreCase(getType(k)))
124+
.collect(Collectors.toSet());
46125
}
47126

48-
public void setValue(String key, String value){
49-
template.opsForValue().set(key, value);
127+
public void flushAll() {
128+
invoke("flushall");
50129
}
51130
}

0 commit comments

Comments
 (0)