diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java b/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java
index f866b479..edc522e2 100644
--- a/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java
+++ b/redis-om-spring/src/main/java/com/redis/om/spring/RedisModulesConfiguration.java
@@ -8,6 +8,8 @@
import java.util.List;
import java.util.Set;
+import com.redis.om.spring.ops.CommandListener;
+import com.redis.om.spring.ops.NoOpCommandListener;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -202,6 +204,7 @@ RedisModulesClient redisModulesClient( //
* @param rmc the Redis modules client for low-level access
* @param template the string Redis template for basic operations
* @param gsonBuilder the Gson builder for JSON serialization
+ * @param commandListener a command listener for monitoring Redis commands
* @return the Redis modules operations instance
*/
@Bean(
@@ -215,10 +218,28 @@ RedisModulesOperations> redisModulesOperations( //
StringRedisTemplate template, //
@Qualifier(
"omGsonBuilder"
- ) GsonBuilder gsonBuilder) {
- return new RedisModulesOperations<>(rmc, template, gsonBuilder);
+ ) GsonBuilder gsonBuilder, final CommandListener commandListener) {
+ return new RedisModulesOperations<>(rmc, template, gsonBuilder, commandListener);
}
+ /**
+ * Provides a default implementation of the CommandListener bean.
+ *
+ * This method creates a no-operation (NoOp) implementation of the CommandListener interface.
+ * It is used as a fallback when no other CommandListener bean is defined in the application context.
+ *
+ * The {@code @Fallback} annotation ensures that this bean is only used when no other
+ * CommandListener bean is available, allowing developers to override it with a custom implementation if needed.
+ *
+ * @return a NoOpCommandListener instance, which performs no operations.
+ */
+ @Bean
+ @Fallback
+ public CommandListener commandListener() {
+ return new NoOpCommandListener();
+ }
+
+
/**
* Creates the JSON operations bean for RedisJSON commands.
*
diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/ops/CommandListener.java b/redis-om-spring/src/main/java/com/redis/om/spring/ops/CommandListener.java
new file mode 100644
index 00000000..ecc96fcc
--- /dev/null
+++ b/redis-om-spring/src/main/java/com/redis/om/spring/ops/CommandListener.java
@@ -0,0 +1,90 @@
+package com.redis.om.spring.ops;
+
+import com.redis.om.spring.autocomplete.Suggestion;
+import com.redis.om.spring.repository.query.autocomplete.AutoCompleteOptions;
+import redis.clients.jedis.search.FTCreateParams;
+import redis.clients.jedis.search.FTSearchParams;
+import redis.clients.jedis.search.IndexOptions;
+import redis.clients.jedis.search.Query;
+import redis.clients.jedis.search.Schema;
+import redis.clients.jedis.search.SearchProtocol;
+import redis.clients.jedis.search.SearchResult;
+import redis.clients.jedis.search.aggr.AggregationBuilder;
+import redis.clients.jedis.search.aggr.AggregationResult;
+import redis.clients.jedis.search.schemafields.SchemaField;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface CommandListener {
+ default void searchStarted(String indexName, Query q, FTSearchParams params) {}
+ default void searchFinished(String indexName, Query q, FTSearchParams params, SearchResult searchResult){}
+
+ default void createIndexStarted(String indexName, FTCreateParams params, List fields, Schema schema, IndexOptions options){}
+ default void createIndexFinished(String indexName, FTCreateParams params, List fields, Schema schema, IndexOptions options, String result){}
+
+ default void aggregateStarted(String indexName, AggregationBuilder q){}
+ default void aggregateFinished(String indexName, AggregationBuilder q){}
+
+ default void cursorDeleteStarted(String string, long cursorId){}
+ default void cursorDeleteFinished(String string, long cursorId, String result){}
+
+ default void cursorReadStarted(String string, long cursorId, int count){}
+ default void cursorReadFinished(String string, long cursorId, int count, AggregationResult aggregationResult){}
+
+ default void explainStarted(String string, Query q){}
+ default void explainFinished(String string, Query q, String s){}
+
+ default void infoStarted(String string){}
+ default void infoFinished(String string, Map stringObjectMap){}
+
+ default void dropIndexStarted(String string){}
+ default void dropIndexFinished(String string, String result){}
+
+ default void dropIndexAndDocumentsStarted(String string){}
+ default void dropIndexAndDocumentsFinished(String string, String result){}
+
+ default void addSuggestionStarted(String string, String key, String suggestion, double score){}
+ default void addSuggestionFinished(String string, String key, String suggestion, double score, long result){}
+
+ default void getSuggestionStarted(String string, String key, String prefix, AutoCompleteOptions options){}
+ default void getSuggestionFinished(String string, String key, String prefix, AutoCompleteOptions options, List list){}
+
+ default void deleteSuggestionStarted(String string, String key, String entry){}
+ default void deleteSuggestionFinished(String string, String key, String entry, boolean result){}
+
+ default void getSuggestionLengthStarted(String string, String key){}
+ default void getSuggestionLengthFinished(String string, String key, long result){}
+
+ default void alterIndexStarted(String string, SchemaField[] fields){}
+ default void alterIndexFinished(String string, SchemaField[] fields, String result){}
+
+ default void setConfigStarted(String string, String option, String value){}
+ default void setConfigFinished(String string, String option, String value, String result){}
+
+ default void getConfigStarted(String string, String option){}
+ default void getConfigFinished(String string, String option, Map result){}
+
+ default void getIndexConfigStarted(String string, String option){}
+ default void getIndexConfigFinished(String string, String option, Map result){}
+
+ default void addAliasStarted(String string, String name){}
+ default void addAliasFinished(String string, String name, String result){}
+
+ default void updateAliasStarted(String string, String name){}
+ default void updateAliasFinished(String string, String name, String result){}
+
+ default void deleteAliasStarted(String string, String name){}
+ default void deleteAliasFinished(String string, String name, String result){}
+
+ default void updateSynonymStarted(String string, String synonymGroupId, String[] terms){}
+ default void updateSynonymFinished(String string, String synonymGroupId, String[] terms, String result){}
+
+ default void dumpSynonymStarted(String string){}
+ default void dumpSynonymFinished(String string, Map> result){}
+
+ default void tagValsStarted(String string, String field){}
+ default void tagValsFinished(String string, String field, Set result){}
+
+ default void commandFailed(SearchProtocol.SearchCommand command, String indexName, Throwable t){}
+}
diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/ops/NoOpCommandListener.java b/redis-om-spring/src/main/java/com/redis/om/spring/ops/NoOpCommandListener.java
new file mode 100644
index 00000000..170e6125
--- /dev/null
+++ b/redis-om-spring/src/main/java/com/redis/om/spring/ops/NoOpCommandListener.java
@@ -0,0 +1,7 @@
+package com.redis.om.spring.ops;
+
+import org.springframework.stereotype.Component;
+
+@Component
+public class NoOpCommandListener implements CommandListener {
+}
diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/ops/RedisModulesOperations.java b/redis-om-spring/src/main/java/com/redis/om/spring/ops/RedisModulesOperations.java
index 3539058a..19c564ba 100644
--- a/redis-om-spring/src/main/java/com/redis/om/spring/ops/RedisModulesOperations.java
+++ b/redis-om-spring/src/main/java/com/redis/om/spring/ops/RedisModulesOperations.java
@@ -26,7 +26,8 @@
* @param client the Redis modules client for executing commands
* @param template the Spring Data Redis template for additional Redis operations
* @param gsonBuilder the Gson builder for JSON serialization/deserialization configuration
- *
+ * @param commandListener A command listener for monitoring Redis commands
+ *
* @author Redis OM Spring Team
* @see JSONOperations
* @see SearchOperations
@@ -37,7 +38,7 @@
* @see TDigestOperations
*/
public record RedisModulesOperations(RedisModulesClient client, StringRedisTemplate template,
- GsonBuilder gsonBuilder) {
+ GsonBuilder gsonBuilder, CommandListener commandListener) {
/**
* Creates and returns operations for interacting with RedisJSON module.
@@ -65,7 +66,7 @@ public JSONOperations opsForJSON() {
* @return a {@link SearchOperations} instance for search and indexing operations
*/
public SearchOperations opsForSearch(K index) {
- return new SearchOperationsImpl<>(index, client, template);
+ return new SearchOperationsImpl<>(index, client, template, commandListener);
}
/**
diff --git a/redis-om-spring/src/main/java/com/redis/om/spring/ops/search/SearchOperationsImpl.java b/redis-om-spring/src/main/java/com/redis/om/spring/ops/search/SearchOperationsImpl.java
index ba9d706a..1c77e90f 100644
--- a/redis-om-spring/src/main/java/com/redis/om/spring/ops/search/SearchOperationsImpl.java
+++ b/redis-om-spring/src/main/java/com/redis/om/spring/ops/search/SearchOperationsImpl.java
@@ -3,7 +3,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-
+import com.redis.om.spring.ops.CommandListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import com.google.gson.Gson;
@@ -39,6 +39,7 @@ public class SearchOperationsImpl implements SearchOperations {
private final RedisModulesClient modulesClient;
private final K index;
private final StringRedisTemplate template;
+ private final CommandListener commandListener;
/**
* Creates a new search operations implementation.
@@ -46,77 +47,117 @@ public class SearchOperationsImpl implements SearchOperations {
* @param index the search index identifier
* @param modulesClient the Redis modules client for search operations
* @param template the string Redis template for additional operations
+ * @param commandListener A command listener for monitoring Redis commands
*/
- public SearchOperationsImpl(K index, RedisModulesClient modulesClient, StringRedisTemplate template) {
+ public SearchOperationsImpl(K index, RedisModulesClient modulesClient, StringRedisTemplate template,
+ final CommandListener commandListener) {
this.index = index;
this.modulesClient = modulesClient;
this.search = modulesClient.clientForSearch();
this.template = template;
+ this.commandListener = commandListener;
}
- @Override
- public String createIndex(Schema schema, IndexOptions options) {
- return search.ftCreate(index.toString(), options, schema);
- }
+ @Override
+ public String createIndex(Schema schema, IndexOptions options) {
+ commandListener.createIndexStarted(index.toString(), null, null, schema, options);
+ final String s = search.ftCreate(index.toString(), options, schema);
+ commandListener.createIndexFinished(index.toString(), null, null, schema, options, s);
+ return s;
+ }
- @Override
- public String createIndex(FTCreateParams params, List fields) {
- return search.ftCreate(index.toString(), params, fields);
- }
+ @Override
+ public String createIndex(FTCreateParams params, List fields) {
+ commandListener.createIndexStarted(index.toString(), params, fields, null, null);
+ final String s = search.ftCreate(index.toString(), params, fields);
+ commandListener.createIndexFinished(index.toString(), params, fields, null, null, s);
+ return s;
+ }
- @Override
- public SearchResult search(Query q) {
- return search.ftSearch(SafeEncoder.encode(index.toString()), q);
- }
+ @Override
+ @Deprecated
+ public SearchResult search(Query q) {
+ commandListener.searchStarted(index.toString(), q, null);
+ final SearchResult searchResult = search.ftSearch(SafeEncoder.encode(index.toString()), q);
+ commandListener.searchFinished(index.toString(), q, null, searchResult);
+ return searchResult;
+ }
- @Override
- public SearchResult search(Query q, FTSearchParams params) {
- return search.ftSearch(index.toString(), q.toString(), params);
- }
+ @Override
+ public SearchResult search(Query q, FTSearchParams params) {
+ commandListener.searchStarted(index.toString(), q, null);
+ final SearchResult searchResult = search.ftSearch(index.toString(), q.toString(), params);
+ commandListener.searchFinished(index.toString(), q, null, searchResult);
+ return searchResult;
+ }
- @Override
- public AggregationResult aggregate(AggregationBuilder q) {
- return search.ftAggregate(index.toString(), q);
- }
+ @Override
+ public AggregationResult aggregate(AggregationBuilder q) {
+ commandListener.aggregateStarted(index.toString(), q);
+ final AggregationResult aggregationResult = search.ftAggregate(index.toString(), q);
+ commandListener.aggregateFinished(index.toString(), q);
+ return aggregationResult;
+ }
- @Override
- public String cursorDelete(long cursorId) {
- return search.ftCursorDel(index.toString(), cursorId);
- }
+ @Override
+ public String cursorDelete(long cursorId) {
+ commandListener.cursorDeleteStarted(index.toString(), cursorId);
+ final String result = search.ftCursorDel(index.toString(), cursorId);
+ commandListener.cursorDeleteFinished(index.toString(), cursorId, result);
+ return result;
+ }
@Override
public AggregationResult cursorRead(long cursorId, int count) {
- return search.ftCursorRead(index.toString(), cursorId, count);
+ commandListener.cursorReadStarted(index.toString(), cursorId, count);
+ final AggregationResult aggregationResult = search.ftCursorRead(index.toString(), cursorId, count);
+ commandListener.cursorReadFinished(index.toString(), cursorId, count, aggregationResult);
+ return aggregationResult;
}
@Override
public String explain(Query q) {
- return search.ftExplain(index.toString(), q);
+ commandListener.explainStarted(index.toString(), q);
+ final String s = search.ftExplain(index.toString(), q);
+ commandListener.explainFinished(index.toString(), q, s);
+ return s;
}
@Override
public Map getInfo() {
- return search.ftInfo(index.toString());
+ commandListener.infoStarted(index.toString());
+ final Map result = search.ftInfo(index.toString());
+ commandListener.infoFinished(index.toString(), result);
+ return result;
}
@Override
public String dropIndex() {
- return search.ftDropIndex(index.toString());
+ commandListener.dropIndexStarted(index.toString());
+ final String result = search.ftDropIndex(index.toString());
+ commandListener.dropIndexFinished(index.toString(), result);
+ return result;
}
@Override
public String dropIndexAndDocuments() {
- return search.ftDropIndexDD(index.toString());
+ commandListener.dropIndexAndDocumentsStarted(index.toString());
+ final String result = search.ftDropIndexDD(index.toString());
+ commandListener.dropIndexAndDocumentsFinished(index.toString(), result);
+ return result;
}
@Override
public Long addSuggestion(String key, String suggestion) {
- return search.ftSugAdd(key, suggestion, 1.0);
+ return addSuggestion(key, suggestion, 1.0);
}
@Override
public Long addSuggestion(String key, String suggestion, double score) {
- return search.ftSugAdd(key, suggestion, score);
+ commandListener.addSuggestionStarted(index.toString(), key, suggestion, score);
+ final long result = search.ftSugAdd(key, suggestion, score);
+ commandListener.addSuggestionFinished(index.toString(), key, suggestion, score, result);
+ return result;
}
@Override
@@ -126,101 +167,142 @@ public List getSuggestion(String key, String prefix) {
@Override
public List getSuggestion(String key, String prefix, AutoCompleteOptions options) {
+ commandListener.getSuggestionStarted(index.toString(), key, prefix, options);
Gson gson = modulesClient.gsonBuilder().create();
if (options.isWithScore()) {
List suggestions = search.ftSugGetWithScores(key, prefix, options.isFuzzy(), options.getLimit());
- return suggestions.stream().map(suggestion -> {
- if (options.isWithPayload()) {
- String[] keyParts = key.split(":");
- String payLoadKey = String.format("sugg:payload:%s:%s", keyParts[keyParts.length - 2],
- keyParts[keyParts.length - 1]);
- Object payload = template.opsForHash().get(payLoadKey, suggestion);
- String json = payload != null ? payload.toString() : "{}";
- Map payloadMap = gson.fromJson(json, new TypeToken