Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: Add GET, SET, INFO and PING commands #894

Closed
Closed
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ef2d791
Java: Add commands for GET/SET/INFO/PING
acarbonetto Feb 2, 2024
6cb5039
Spotless
acarbonetto Feb 2, 2024
4efa9a3
Add OK response to base commands
acarbonetto Feb 2, 2024
02a3b27
Fix OK test
acarbonetto Feb 2, 2024
78c3b74
Fix: Info with cluster mode routes
acarbonetto Feb 2, 2024
a874fb1
Update docs and tests for review comments
acarbonetto Feb 2, 2024
516c258
Spotless and review comments
acarbonetto Feb 2, 2024
a3f0bb1
revert RequestRoutingConfiguration.java
acarbonetto Feb 2, 2024
2abfc00
Add IT tests
acarbonetto Feb 2, 2024
ad8895b
Make Route nonnull
acarbonetto Feb 2, 2024
f85625f
Spotless
acarbonetto Feb 2, 2024
5766c3d
Apply review comments to make class/feilds final
acarbonetto Feb 2, 2024
b113b8a
Add more IT tests; fix redis version infooptions
acarbonetto Feb 2, 2024
6c8e04b
Fix Redis versioned tests
acarbonetto Feb 2, 2024
4262a4b
Remove redis 6 issue with LatencyStats
acarbonetto Feb 3, 2024
8c22212
Remove redis 6 issue with LatencyStats
acarbonetto Feb 3, 2024
e96c284
Remove EVERYTHING info() test
acarbonetto Feb 3, 2024
59e7b8f
spotless
acarbonetto Feb 3, 2024
ee90545
Fix IT.
Yury-Fridlyand Feb 3, 2024
210094c
cleanup
Yury-Fridlyand Feb 3, 2024
677c98c
Minor fixes.
Yury-Fridlyand Feb 3, 2024
70cd993
Remove OK object
acarbonetto Feb 5, 2024
2dd1a03
Revert change to customCommand
acarbonetto Feb 5, 2024
4a56401
Rename command files
acarbonetto Feb 5, 2024
42b0498
Add ping(route) to Cluster client
acarbonetto Feb 5, 2024
b780bfc
Clean up javadocs; and remove extra test
acarbonetto Feb 6, 2024
40520a7
Fix CI run
acarbonetto Feb 6, 2024
55a0097
Revert documentation changes in ClusterValue
acarbonetto Feb 7, 2024
d082f32
Change SetOptions.expiry to use constructors
acarbonetto Feb 7, 2024
8388c49
Update cluster mode javadocs
acarbonetto Feb 7, 2024
a96658b
Update javadocs to be consistent with Node client
acarbonetto Feb 7, 2024
189badb
minor update
acarbonetto Feb 7, 2024
fcd13df
minor update
acarbonetto Feb 7, 2024
dc43db8
remove check in SetOptions
acarbonetto Feb 7, 2024
247ffdb
Update OK statis string
acarbonetto Feb 7, 2024
cb8cac4
Clean upRedisClusterClientTest.java
acarbonetto Feb 7, 2024
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
126 changes: 111 additions & 15 deletions java/client/src/main/java/glide/api/BaseClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@
package glide.api;

import static glide.ffi.resolvers.SocketListenerResolver.getSocket;
import static redis_request.RedisRequestOuterClass.RequestType.GetString;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.SetString;

import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.StringCommands;
import glide.api.models.commands.SetOptions;
import glide.api.models.configuration.BaseClientConfiguration;
import glide.api.models.exceptions.RedisException;
import glide.connectors.handlers.CallbackDispatcher;
import glide.connectors.handlers.ChannelHandler;
import glide.connectors.resources.Platform;
Expand All @@ -13,31 +20,24 @@
import glide.managers.BaseCommandResponseResolver;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.ArrayUtils;
import response.ResponseOuterClass.Response;

/** Base Client class for Redis */
@AllArgsConstructor
public abstract class BaseClient implements AutoCloseable {
public abstract class BaseClient
implements AutoCloseable, StringCommands, ConnectionManagementCommands {

public static final String OK = "OK";

protected final ConnectionManager connectionManager;
protected final CommandManager commandManager;

/**
* Extracts the response from the Protobuf response and either throws an exception or returns the
* appropriate response as an Object
*
* @param response Redis protobuf message
* @return Response Object
*/
protected Object handleObjectResponse(Response response) {
// convert protobuf response into Object and then Object into T
return new BaseCommandResponseResolver(RedisValueResolver::valueFromPointer).apply(response);
}

/**
* Async request for an async (non-blocking) Redis client.
*
Expand Down Expand Up @@ -74,8 +74,8 @@ protected static <T> CompletableFuture<T> CreateClient(
* Closes this resource, relinquishing any underlying resources. This method is invoked
* automatically on objects managed by the try-with-resources statement.
*
* <p>see: <a
* href="https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html#close--">AutoCloseable::close()</a>
* @see <a
* href="https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html#close--">AutoCloseable::close()</a>
*/
@Override
public void close() throws ExecutionException {
Expand All @@ -100,4 +100,100 @@ protected static ConnectionManager buildConnectionManager(ChannelHandler channel
protected static CommandManager buildCommandManager(ChannelHandler channelHandler) {
return new CommandManager(channelHandler);
}

/**
* Extracts the response from the Protobuf response and either throws an exception or returns the
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
* appropriate response as an <code>Object</code>.
*
* @param response Redis protobuf message
* @return Response <code>Object</code>
*/
protected Object handleObjectResponse(Response response) {
// convert protobuf response into Object
return new BaseCommandResponseResolver(RedisValueResolver::valueFromPointer).apply(response);
}

/**
* Extracts the response value from the Redis response and either throws an exception or returns
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
* the value as a <code>String</code>.
*
* @param response Redis protobuf message
* @return Response as a <code>String</code>
* @throws RedisException if there's a type mismatch
*/
protected String handleStringResponse(Response response) {
Object value = handleObjectResponse(response);
if (value instanceof String || value == null) {
return (String) value;
}
throw new RedisException(
"Unexpected return type from Redis: got "
+ value.getClass().getSimpleName()
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
+ " expected String");
}

/**
* Extracts the response value from the Redis response and either throws an exception or returns
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
* the value as an <code>Object[]</code>.
*
* @param response Redis protobuf message
* @return Response as an <code>Object[]</code>
* @throws RedisException if there's a type mismatch
*/
protected Object[] handleArrayResponse(Response response) {
Object value = handleObjectResponse(response);
if (value instanceof Object[]) {
return (Object[]) value;
}
String className = (value == null) ? "null" : value.getClass().getSimpleName();
throw new RedisException(
"Unexpected return type from Redis: got " + className + " expected Object[]");
}

/**
* Extracts the response value from the Redis response and either throws an exception or returns
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
* the value as a <code>Map</code>.
*
* @param response Redis protobuf message
* @return Response as a <code>Map</code>
* @throws RedisException if there's a type mismatch
*/
@SuppressWarnings("unchecked")
protected Map<Object, Object> handleMapResponse(Response response) {
Object value = handleObjectResponse(response);
if (value instanceof Map) {
return (Map<Object, Object>) value;
}
String className = (value == null) ? "null" : value.getClass().getSimpleName();
throw new RedisException(
"Unexpected return type from Redis: got " + className + " expected Map");
}

@Override
public CompletableFuture<String> ping() {
return commandManager.submitNewCommand(Ping, new String[0], this::handleStringResponse);
}

@Override
public CompletableFuture<String> ping(String msg) {
return commandManager.submitNewCommand(Ping, new String[] {msg}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> get(String key) {
return commandManager.submitNewCommand(
GetString, new String[] {key}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> set(String key, String value) {
return commandManager.submitNewCommand(
SetString, new String[] {key, value}, this::handleStringResponse);
}

@Override
public CompletableFuture<String> set(String key, String value, SetOptions options) {
String[] arguments = ArrayUtils.addAll(new String[] {key, value}, options.toArgs());
return commandManager.submitNewCommand(SetString, arguments, this::handleStringResponse);
}
}
28 changes: 21 additions & 7 deletions java/client/src/main/java/glide/api/RedisClient.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

import glide.api.commands.BaseCommands;
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static redis_request.RedisRequestOuterClass.RequestType.Info;

import glide.api.commands.ConnectionManagementCommands;
import glide.api.commands.GenericCommands;
import glide.api.commands.ServerManagementCommands;
import glide.api.models.commands.InfoOptions;
import glide.api.models.configuration.RedisClientConfiguration;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import glide.managers.models.Command;
import java.util.concurrent.CompletableFuture;

/**
* Async (non-blocking) client for Redis in Standalone mode. Use {@link #CreateClient} to request a
* client to Redis.
*/
public class RedisClient extends BaseClient implements BaseCommands {
public class RedisClient extends BaseClient
implements GenericCommands, ConnectionManagementCommands, ServerManagementCommands {

protected RedisClient(ConnectionManager connectionManager, CommandManager commandManager) {
super(connectionManager, commandManager);
Expand All @@ -22,16 +28,24 @@ protected RedisClient(ConnectionManager connectionManager, CommandManager comman
* Async request for an async (non-blocking) Redis client in Standalone mode.
*
* @param config Redis client Configuration
* @return a Future to connect and return a RedisClient
* @return A Future to connect and return a RedisClient
*/
public static CompletableFuture<RedisClient> CreateClient(RedisClientConfiguration config) {
return CreateClient(config, RedisClient::new);
}

@Override
public CompletableFuture<Object> customCommand(String[] args) {
Command command =
Command.builder().requestType(Command.RequestType.CUSTOM_COMMAND).arguments(args).build();
return commandManager.submitNewCommand(command, this::handleObjectResponse);
return commandManager.submitNewCommand(CustomCommand, args, this::handleStringResponse);
}

@Override
public CompletableFuture<String> info() {
return commandManager.submitNewCommand(Info, new String[0], this::handleStringResponse);
}

@Override
public CompletableFuture<String> info(InfoOptions options) {
return commandManager.submitNewCommand(Info, options.toArgs(), this::handleStringResponse);
}
}
85 changes: 70 additions & 15 deletions java/client/src/main/java/glide/api/RedisClusterClient.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api;

import glide.api.commands.ClusterBaseCommands;
import static redis_request.RedisRequestOuterClass.RequestType.CustomCommand;
import static redis_request.RedisRequestOuterClass.RequestType.Info;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;

import glide.api.commands.ConnectionManagementClusterCommands;
import glide.api.commands.GenericClusterCommands;
import glide.api.commands.ServerManagementClusterCommands;
import glide.api.models.ClusterValue;
import glide.api.models.commands.InfoOptions;
import glide.api.models.configuration.RedisClusterClientConfiguration;
import glide.api.models.configuration.RequestRoutingConfiguration.Route;
import glide.managers.CommandManager;
import glide.managers.ConnectionManager;
import glide.managers.models.Command;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import lombok.NonNull;

/**
* Async (non-blocking) client for Redis in Cluster mode. Use {@link #CreateClient} to request a
* client to Redis.
*/
public class RedisClusterClient extends BaseClient implements ClusterBaseCommands {
public class RedisClusterClient extends BaseClient
implements ConnectionManagementClusterCommands,
GenericClusterCommands,
ServerManagementClusterCommands {

protected RedisClusterClient(ConnectionManager connectionManager, CommandManager commandManager) {
super(connectionManager, commandManager);
Expand All @@ -25,7 +36,7 @@ protected RedisClusterClient(ConnectionManager connectionManager, CommandManager
* Async request for an async (non-blocking) Redis client in Cluster mode.
*
* @param config Redis cluster client Configuration
* @return a Future to connect and return a ClusterClient
* @return A Future to connect and return a RedisClusterClient
*/
public static CompletableFuture<RedisClusterClient> CreateClient(
RedisClusterClientConfiguration config) {
Expand All @@ -34,28 +45,72 @@ public static CompletableFuture<RedisClusterClient> CreateClient(

@Override
public CompletableFuture<ClusterValue<Object>> customCommand(String[] args) {
Command command =
Command.builder().requestType(Command.RequestType.CUSTOM_COMMAND).arguments(args).build();
// TODO if a command returns a map as a single value, ClusterValue misleads user
return commandManager.submitNewCommand(
command, response -> ClusterValue.of(handleObjectResponse(response)));
CustomCommand,
args,
Optional.empty(),
response -> ClusterValue.of(handleObjectResponse(response)));
}

@Override
@SuppressWarnings("unchecked")
public CompletableFuture<ClusterValue<Object>> customCommand(String[] args, Route route) {
Command command =
Command.builder()
.requestType(Command.RequestType.CUSTOM_COMMAND)
.arguments(args)
.route(route)
.build();

return commandManager.submitNewCommand(
command,
CustomCommand,
args,
Optional.of(route),
response ->
route.isSingleNodeRoute()
? ClusterValue.ofSingleValue(handleObjectResponse(response))
: ClusterValue.ofMultiValue((Map<String, Object>) handleObjectResponse(response)));
}

@Override
public CompletableFuture<String> ping(@NonNull Route route) {
return commandManager.submitNewCommand(
Ping, new String[0], Optional.of(route), this::handleStringResponse);
}

@Override
public CompletableFuture<String> ping(@NonNull String msg, @NonNull Route route) {
return commandManager.submitNewCommand(
Ping, new String[] {msg}, Optional.of(route), this::handleStringResponse);
}

@Override
public CompletableFuture<ClusterValue<String>> info() {
return commandManager.submitNewCommand(
Info,
new String[0],
Optional.empty(),
response -> ClusterValue.of(handleObjectResponse(response)));
}

@Override
public CompletableFuture<ClusterValue<String>> info(@NonNull Route route) {
return commandManager.submitNewCommand(
Info,
new String[0],
Optional.of(route),
response -> ClusterValue.of(handleObjectResponse(response)));
}

@Override
public CompletableFuture<ClusterValue<String>> info(InfoOptions options) {
return commandManager.submitNewCommand(
Info,
options.toArgs(),
Optional.empty(),
response -> ClusterValue.of(handleObjectResponse(response)));
}

@Override
public CompletableFuture<ClusterValue<String>> info(InfoOptions options, @NonNull Route route) {
return commandManager.submitNewCommand(
Info,
options.toArgs(),
Optional.of(route),
response -> ClusterValue.of(handleObjectResponse(response)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.commands;

import glide.api.models.configuration.RequestRoutingConfiguration.Route;
import java.util.concurrent.CompletableFuture;

/**
* Connection Management Commands interface.
*
* @see: <a href="https://redis.io/commands/?group=connection">Connection Management Commands</a>
*/
public interface ConnectionManagementClusterCommands {

/**
* Ping the Redis server.
*
* @param route Routing configuration for the command
* @see <a href="https://redis.io/commands/ping/">redis.io</a> for details.
* @return Response from Redis containing a <code>String</code>.
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
*/
CompletableFuture<String> ping(Route route);

/**
* Ping the Redis server.
*
* @see <a href="https://redis.io/commands/ping/">redis.io</a> for details.
* @param msg The ping argument that will be returned.
* @param route Routing configuration for the command
* @return Response from Redis containing a <code>String</code>.
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
*/
CompletableFuture<String> ping(String msg, Route route);
}
Loading
Loading