diff --git a/examples/java/README.md b/examples/java/README.md index 0e46d8ba62..395ca7d1a7 100644 --- a/examples/java/README.md +++ b/examples/java/README.md @@ -1,18 +1,14 @@ ## Run -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. +Ensure that you have an instance of Valkey running on "localhost" on "6379". Otherwise, update glide.examples.StandaloneExample or glide.examples.ClusterExample with a configuration that matches your server settings. -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 ``` diff --git a/examples/java/build.gradle b/examples/java/build.gradle index fa55ac434e..6ff2785725 100644 --- a/examples/java/build.gradle +++ b/examples/java/build.gradle @@ -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" } @@ -11,10 +10,19 @@ repositories { } dependencies { - implementation "io.valkey:valkey-glide:1.0.1:${osdetector.classifier}" + implementation "io.valkey:valkey-glide:1.+:${osdetector.classifier}" } -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' } diff --git a/examples/java/src/main/java/glide/examples/ClusterExample.java b/examples/java/src/main/java/glide/examples/ClusterExample.java new file mode 100644 index 0000000000..cc598b632a --- /dev/null +++ b/examples/java/src/main/java/glide/examples/ClusterExample.java @@ -0,0 +1,151 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +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; + +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.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class ClusterExample { + + /** + * Creates and returns a GlideClusterClient instance. + * + *

This function initializes a GlideClusterClient 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 GlideClusterClient 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(List nodeList) + throws CancellationException, ExecutionException, InterruptedException { + // Check `GlideClusterClientConfiguration` for additional options. + GlideClusterClientConfiguration config = + GlideClusterClientConfiguration.builder() + .addresses(nodeList) + // 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 GlideClusterClient. + * + * @param client An instance of GlideClusterClient. + * @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 setResponse = client.set("foo", "bar"); + log(INFO, "app", "Set response is " + setResponse.get()); + + CompletableFuture 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 pong = client.ping(); + log(INFO, "app", "Ping response is " + pong.get()); + + // Send INFO REPLICATION with routing option to all nodes + ClusterValue 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 { + + // Define list of nodes + List nodeList = + Collections.singletonList(NodeAddress.builder().host("localhost").port(6379).build()); + + while (true) { + try (GlideClusterClient client = createClient(nodeList)) { + appLogic(client); + 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(); + } +} diff --git a/examples/java/src/main/java/glide/examples/ExamplesApp.java b/examples/java/src/main/java/glide/examples/ExamplesApp.java deleted file mode 100644 index 4a686786eb..0000000000 --- a/examples/java/src/main/java/glide/examples/ExamplesApp.java +++ /dev/null @@ -1,40 +0,0 @@ -/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ -package glide.examples; - -import glide.api.GlideClient; -import glide.api.models.configuration.GlideClientConfiguration; -import glide.api.models.configuration.NodeAddress; -import java.util.concurrent.ExecutionException; - -public class ExamplesApp { - - // main application entrypoint - public static void main(String[] args) { - runGlideExamples(); - } - - private static void runGlideExamples() { - String host = "localhost"; - Integer port = 6379; - boolean useSsl = false; - - GlideClientConfiguration config = - GlideClientConfiguration.builder() - .address(NodeAddress.builder().host(host).port(port).build()) - .useTLS(useSsl) - .build(); - - try (GlideClient client = GlideClient.createClient(config).get()) { - - System.out.println("PING: " + client.ping().get()); - System.out.println("PING(found you): " + client.ping("found you").get()); - - System.out.println("SET(apples, oranges): " + client.set("apples", "oranges").get()); - System.out.println("GET(apples): " + client.get("apples").get()); - - } catch (ExecutionException | InterruptedException e) { - System.out.println("Glide example failed with an exception: "); - e.printStackTrace(); - } - } -} diff --git a/examples/java/src/main/java/glide/examples/StandaloneExample.java b/examples/java/src/main/java/glide/examples/StandaloneExample.java new file mode 100644 index 0000000000..996e408e83 --- /dev/null +++ b/examples/java/src/main/java/glide/examples/StandaloneExample.java @@ -0,0 +1,138 @@ +/** Copyright Valkey GLIDE Project Contributors - SPDX Identifier: Apache-2.0 */ +package glide.examples; + +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 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.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public class StandaloneExample { + + /** + * Creates and returns a GlideClient instance. + * + *

This function initializes a GlideClient with the provided list of nodes. The + * list may contain either only primary node or a mix of primary and replica nodes. The + * GlideClient + * use these nodes to connect to the Standalone setup servers. + * + * @return A GlideClient 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(List nodeList) + throws CancellationException, ExecutionException, InterruptedException { + // Check `GlideClientConfiguration` for additional options. + GlideClientConfiguration config = + GlideClientConfiguration.builder() + .addresses(nodeList) + // 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 GlideClient. + * + * @param client An instance of GlideClient. + * @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 setResponse = client.set("foo", "bar"); + log(INFO, "app", "Set response is " + setResponse.get()); + + CompletableFuture getResponse = client.get("foo"); + log(INFO, "app", "Get response is " + getResponse.get()); + + // Send PING + CompletableFuture 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 { + + // Define list of nodes + List nodeList = + Collections.singletonList(NodeAddress.builder().host("localhost").port(6379).build()); + + while (true) { + try (GlideClient client = createClient(nodeList)) { + appLogic(client); + 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 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(); + } +}