Search code examples
spring-bootredisspring-dataspring-data-redisredisjson

Spring data redis support for modules?


I'm using Spring data Redis (version 2.1.1RELEASE) with the driver of lettuce (version 5.1.3RELEASE) I want to use this module: https://oss.redislabs.com/redisjson/ but the driver doesn't seem to support it.

I tried use the execute method:

Object response = redisTemplate.execute((RedisCallback<String>) connection -> {
  return  (String) connection.execute("JSON.GET foo");
});

And got an error:

java.lang.IllegalArgumentException: No enum constant io.lettuce.core.protocol.CommandType.JSON.GET FOO

Is there a way to do that? How can I utilize Redis modules?


Solution

  • OK so I managed to do it like that:

    First extend ProtocolKeyword and replace all '_' chars with '.':

    public interface ModuleCommand extends ProtocolKeyword {
    
      @Override
      default byte[] getBytes() {
        return name().replaceAll("_", ".").getBytes(LettuceCharsets.ASCII);
      }
    }
    

    Second, create an enum with the types of commands you wish to implement:

    public enum JsonCommand implements ModuleCommand {
    
      JSON_GET,
      JSON_MGET,
      JSON_SET,
      JSON_ARRAPPEND,
      JSON_DEL,
      // other commands you wish to implement
      ;
    }
    

    Now here's the interesting part, I already integrated more modules so I has to generalize the execution part:

    public abstract class ModuleAbstractManager {
    
    
      protected ByteArrayCodec codec = ByteArrayCodec.INSTANCE;
    
      @Autowired
      private LettuceConnectionFactory connectionFactory;
    
      protected <T> Optional<T> execute(String key, ModuleCommand jsonCommand, CommandOutput<byte[], byte[], T> output, String... args) {
    
        List<byte[]> extraArgs = Arrays.stream(args)
            .filter(arg -> !StringUtils.isEmpty(arg))
            .map(String::getBytes)
            .collect(Collectors.toList());
    
        CommandArgs<byte[], byte[]> commandArgs = new CommandArgs<>(codec)
            .addKey(key.getBytes())
            .addValues(extraArgs);
    
        LettuceConnection connection = (LettuceConnection) connectionFactory.getConnection();
    
        try {
          RedisFuture<T> future = connection.getNativeConnection().dispatch(jsonCommand, output, commandArgs);
          return Optional.ofNullable(future.get());
        }
        catch (InterruptedException | ExecutionException e) {
          return Optional.empty();
        }
        finally {
          connection.close();
        }
      }
    }
    

    And finally the execution itself:

    @Service
    public class RedisJsonManager extends ModuleAbstractManager {
    
      public static final String ROOT_PATH = ".";
      public static final String OK_RESPONSE = "OK";
      public static final String SET_IF_NOT_EXIST_FLAG = "NX";
    
    
      public Optional<String> getValue(String key) {
        return getValue(key, ROOT_PATH);
      }
    
      public Optional<String> getValue(String key, String path) {
    
        if (StringUtils.isEmpty(path)) {
          return Optional.empty();
        }
    
        return execute(key, JsonCommand.JSON_GET, new ValueOutput<>(codec), path)
            .map(String::new);
      }
    
      public Optional<String> getValue(String key, List<String> multiPath) {
    
        if (CollectionUtils.isEmpty(multiPath)) {
          return Optional.empty();
        }
    
        String[] args = multiPath.toArray(new String[0]);
        return execute(key, JsonCommand.JSON_GET, new ValueOutput<>(codec), args)
            .map(String::new);
      }
    
      public boolean setValueIfNotExist(String key, String json) {
        return setValue(key, json, ROOT_PATH, true);
      }
    
      public boolean setValueIfNotExist(String key, String json, String path) {
        return setValue(key, json, path, true);
      }
    
      public boolean setValue(String key, String json) {
        return setValue(key, json, ROOT_PATH, false);
      }
    
      public boolean setValue(String key, String json, String path) {
        return setValue(key, json, path, false);
      }
    
      private boolean setValue(String key, String json, String path, boolean setIfNotExist) {
        return execute(key, JsonCommand.JSON_SET, new StatusOutput<>(codec), path, json, setIfNotExist ? SET_IF_NOT_EXIST_FLAG : "")
            .map(OK_RESPONSE::equals)
            .orElse(false);
      }
    
      public Long addToArray(String key, String json) {
        return addToArray(key, json, ROOT_PATH);
      }
    
      public Long addToArray(String key, String json, String path) {
        return execute(key, JsonCommand.JSON_ARRAPPEND, new IntegerOutput<>(codec), path, json).orElse(0L);
      }
    
      public Long delete(String key) {
        return delete(key, ROOT_PATH);
      }
    
      public Long delete(String key, String path) {
        return execute(key, JsonCommand.JSON_DEL, new IntegerOutput<>(codec), path).orElse(0L);
      }
    }