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 examples #1925

Merged
merged 19 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
14 changes: 5 additions & 9 deletions examples/java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@

Ensure that you have an instance of Valkey running on "localhost" on "6379". Otherwise, update glide.examples.ExamplesApp with a configuration that matches your server settings.
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved

To run the example:
To run the Standalone example:
```
cd valkey-glide/examples/java
./gradlew :run
./gradlew :runStandalone
```

You should expect to see the output:
To run the Cluster example:
```
> Task :run
PING: PONG
PING(found you): found you
SET(apples, oranges): OK
GET(apples): oranges
cd valkey-glide/examples/java
./gradlew :runCluster
```
18 changes: 13 additions & 5 deletions examples/java/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
plugins {
// Apply the application plugin to add support for building a CLI application in Java.
id 'application'
id "java"
id "com.google.osdetector" version "1.7.3"
}

Expand All @@ -14,7 +13,16 @@ dependencies {
implementation group: 'io.valkey', name: 'valkey-glide', version: '1.0.0', classifier: osdetector.classifier
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
}

application {
// Define the main class for the application.
mainClass = 'glide.examples.ExamplesApp'
task runStandalone(type: JavaExec) {
group = 'application'
description = 'Run the standalone example'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'glide.examples.StandaloneExample'
}

task runCluster(type: JavaExec) {
group = 'application'
description = 'Run the cluster example'
classpath = sourceSets.main.runtimeClasspath
mainClass = 'glide.examples.ClusterExample'
}
153 changes: 153 additions & 0 deletions examples/java/src/main/java/glide/examples/ClusterExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.examples;

import glide.api.GlideClusterClient;
import glide.api.logging.Logger;
import glide.api.models.ClusterValue;
import glide.api.models.commands.InfoOptions;
import glide.api.models.configuration.GlideClusterClientConfiguration;
import glide.api.models.configuration.NodeAddress;
import glide.api.models.exceptions.ClosingException;
import glide.api.models.exceptions.ConnectionException;
import glide.api.models.exceptions.TimeoutException;

import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static glide.api.logging.Logger.Level.ERROR;
import static glide.api.logging.Logger.Level.INFO;
import static glide.api.logging.Logger.Level.WARN;
import static glide.api.logging.Logger.log;
import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_NODES;

public class ClusterExample {

/**
* Creates and returns a <code>GlideClusterClient</code> instance.
*
* This function initializes a <code>GlideClusterClient</code> with the provided list of nodes.
* The list may contain the address of one or more cluster nodes, and the client will automatically
* discover all nodes in the cluster.
*
* @return A <code>GlideClusterClient</code> connected to the discovered nodes.
* @throws CancellationException if the operation is cancelled.
* @throws ExecutionException if the client fails due to execution errors.
* @throws InterruptedException if the operation is interrupted.
*/
public static GlideClusterClient createClient() throws CancellationException, ExecutionException, InterruptedException {
String host = "localhost";
Integer port = 6379;

// Check `GlideClusterClientConfiguration` for additional options.
GlideClusterClientConfiguration config =
GlideClusterClientConfiguration.builder()
.address(
NodeAddress.builder()
.host(host)
.port(port)
.build())
// Enable this field if the servers are configured with TLS.
//.useTLS(true);
.build();

GlideClusterClient client = GlideClusterClient.createClient(config).get();
return client;
}

/**
* Executes the main logic of the application, performing basic operations such as SET, GET, PING,
* and INFO REPLICATION using the provided <code>GlideClusterClient</code>.
*
* @param client An instance of <code>GlideClusterClient</code>.
* @throws ExecutionException if an execution error occurs during operations.
* @throws InterruptedException if the operation is interrupted.
*/
public static void appLogic(GlideClusterClient client) throws ExecutionException, InterruptedException {

// Send SET and GET
CompletableFuture<String> setResponse = client.set("foo", "bar");
log(INFO, "app", "Set response is " + setResponse.get());

CompletableFuture<String> getResponse = client.get("foo");
log(INFO, "app", "Get response is " + getResponse.get());

// Send PING to all primaries (according to Valkey's PING request_policy)
CompletableFuture<String> pong = client.ping();
log(INFO, "app", "Ping response is " + pong.get());

// Send INFO REPLICATION with routing option to all nodes
ClusterValue<String> infoResponse =
client.info(
InfoOptions.builder().section(InfoOptions.Section.REPLICATION).build(), ALL_NODES).get();
log(
INFO,
"app",
"INFO REPLICATION responses from all nodes are " + infoResponse.getMultiValue());
}

/**
* Executes the application logic with exception handling.
*
* @throws ExecutionException if an execution error occurs during operations.
*/
private static void execAppLogic() throws ExecutionException {

while (true) {
try (GlideClusterClient client = createClient()) {
appLogic(client);
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
return;
} catch (CancellationException e) {
log(ERROR, "glide", "Request cancelled: " + e.getMessage());
throw e;
} catch (InterruptedException e) {
log(ERROR, "glide", "Client interrupted: " + e.getMessage());
Thread.currentThread().interrupt(); // Restore interrupt status
throw new CancellationException("Client was interrupted.");
} catch (ExecutionException e) {
// All Glide errors will be handled as ExecutionException
if (e.getCause() instanceof ClosingException) {
// If the error message contains "NOAUTH", raise the exception
// because it indicates a critical authentication issue.
if (e.getMessage().contains("NOAUTH")) {
log(
ERROR, "glide", "Authentication error encountered: " + e.getMessage());
throw e;
} else {
log(
WARN,
"glide",
"Client has closed and needs to be re-created: " + e.getMessage());
}
} else if (e.getCause() instanceof ConnectionException) {
// The client wasn't able to reestablish the connection within the given retries
log(ERROR, "glide", "Connection error encountered: " + e.getMessage());
throw e;
} else if (e.getCause() instanceof TimeoutException) {
// A request timed out. You may choose to retry the execution based on your application's
// logic
log(ERROR, "glide", "Timeout encountered: " + e.getMessage());
throw e;
} else {
log(ERROR, "glide", "Execution error encountered: " + e.getCause());
throw e;
}
}
}
}

/**
* The entry point of the cluster example. This method sets up the logger configuration
* and executes the main application logic.
*
* @param args Command-line arguments passed to the application.
* @throws ExecutionException if an error occurs during execution of the application logic.
*/
public static void main(String[] args) throws ExecutionException {
// In this example, we will utilize the client's logger for all log messages
Logger.setLoggerConfig(INFO);
// Optional - set the logger to write to a file
// Logger.setLoggerConfig(Logger.Level.INFO, file)
execAppLogic();
}
}
40 changes: 0 additions & 40 deletions examples/java/src/main/java/glide/examples/ExamplesApp.java

This file was deleted.

137 changes: 137 additions & 0 deletions examples/java/src/main/java/glide/examples/StandaloneExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
package glide.examples;

import glide.api.GlideClient;
import glide.api.logging.Logger;
import glide.api.models.configuration.GlideClientConfiguration;
import glide.api.models.configuration.NodeAddress;
import glide.api.models.exceptions.ClosingException;
import glide.api.models.exceptions.ConnectionException;
import glide.api.models.exceptions.TimeoutException;

import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static glide.api.logging.Logger.Level.ERROR;
import static glide.api.logging.Logger.Level.INFO;
import static glide.api.logging.Logger.Level.WARN;
import static glide.api.logging.Logger.log;

public class StandaloneExample {

/**
* Creates and returns a <code>GlideClient</code> instance.
*
* This function initializes a <code>GlideClient</code> with the provided list of nodes.
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
* The list may contain either only primary node or a mix of primary and replica nodes. The <code>GlideClient
* </code> use these nodes to connect to the Standalone setup servers.
*
* @return A <code>GlideClient</code> connected to the provided node address.
* @throws CancellationException if the operation is cancelled.
* @throws ExecutionException if the client fails due to execution errors.
* @throws InterruptedException if the operation is interrupted.
*/
public static GlideClient createClient() throws CancellationException, ExecutionException, InterruptedException {
String host = "localhost";
Integer port = 6379;

// Check `GlideClientConfiguration` for additional options.
GlideClientConfiguration config =
GlideClientConfiguration.builder()
.address(NodeAddress.builder().host(host).port(port).build())
yipin-chen marked this conversation as resolved.
Show resolved Hide resolved
// Enable this field if the servers are configured with TLS.
//.useTLS(true);
.build();

GlideClient client = GlideClient.createClient(config).get();
return client;
}

/**
* Executes the main logic of the application, performing basic operations such as SET, GET, and
* PING using the provided <code>GlideClient</code>.
*
* @param client An instance of <code>GlideClient</code>.
* @throws ExecutionException if an execution error occurs during operations.
* @throws InterruptedException if the operation is interrupted.
*/
public static void appLogic(GlideClient client) throws ExecutionException, InterruptedException {

// Send SET and GET
CompletableFuture<String> setResponse = client.set("foo", "bar");
log(INFO, "app", "Set response is " + setResponse.get());

CompletableFuture<String> getResponse = client.get("foo");
log(INFO, "app", "Get response is " + getResponse.get());

// Send PING
CompletableFuture<String> pong = client.ping();
log(INFO, "app", "Ping response is " + pong.get());
}

/**
* Executes the application logic with exception handling.
*
* @throws ExecutionException if an execution error occurs during operations.
*/
private static void execAppLogic() throws ExecutionException {

while (true) {
try (GlideClient client = createClient()) {
appLogic(client);
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
return;
acarbonetto marked this conversation as resolved.
Show resolved Hide resolved
} catch (CancellationException e) {
log(ERROR, "glide", "Request cancelled: " + e.getMessage());
throw e;
} catch (InterruptedException e) {
log(ERROR, "glide", "Client interrupted: " + e.getMessage());
Thread.currentThread().interrupt(); // Restore interrupt status
throw new CancellationException("Client was interrupted.");
barshaul marked this conversation as resolved.
Show resolved Hide resolved
} catch (ExecutionException e) {
// All Glide errors will be handled as ExecutionException
if (e.getCause() instanceof ClosingException) {
// If the error message contains "NOAUTH", raise the exception
// because it indicates a critical authentication issue.
if (e.getMessage().contains("NOAUTH")) {
log(
ERROR, "glide", "Authentication error encountered: " + e.getMessage());
throw e;
} else {
log(
WARN,
"glide",
"Client has closed and needs to be re-created: " + e.getMessage());
}
} else if (e.getCause() instanceof ConnectionException) {
// The client wasn't able to reestablish the connection within the given retries
log(ERROR, "glide", "Connection error encountered: " + e.getMessage());
throw e;
} else if (e.getCause() instanceof TimeoutException) {
// A request timed out. You may choose to retry the execution based on your application's
// logic
log(ERROR, "glide", "Timeout encountered: " + e.getMessage());
throw e;
} else {
log(ERROR, "glide", "Execution error encountered: " + e.getCause());
throw e;
}
}
}
}

/**
* The entry point of the standalone example. This method sets up the logger configuration
* and executes the main application logic.
*
* @param args Command-line arguments passed to the application.
* @throws ExecutionException if an error occurs during execution of the application logic.
*/
public static void main(String[] args) throws ExecutionException {
// In this example, we will utilize the client's logger for all log messages
Logger.setLoggerConfig(INFO);
// Optional - set the logger to write to a file
// Logger.setLoggerConfig(Logger.Level.INFO, file)
execAppLogic();
}
}
Loading