= yaml = title: “Fortune in Java” fullTitle: “Fortune in Java” layout: java_tutorial wherein: you build a fortune teller service and a client to talk to it. sort: 1 toc: true prerequisites: {} = yaml =
In this tutorial, we will create a fortune teller server and a client to talk to it. The server will have two methods:
Add
, which adds a fortune to the list of fortunes, andGet
, which retrieves a random fortune.In this tutorial, we will use the Gradle build tool to build the project. There is no requirement that Vanadium Java projects use Gradle, but it's the easiest way to get started. See the installation instructions for details. The remainder of the tutorial will assume that you have the gradle
program in your PATH.
The first step to defining a Gradle project is to create a build.gradle
file in the project root directory.
First, create a new project directory.
mkdir fortuneJava cd fortuneJava
{{# helpers.hidden }}
JAVA_PROJECT_DIR=$(mktemp -d -t tmp.XXXXXXXXXX) cd $JAVA_PROJECT_DIR export PATH=$JIRI_ROOT/third_party/java/gradle:$PATH
{{/ helpers.hidden }}
Now, create a build.gradle
file:
cat <<EOF > build.gradle buildscript { repositories { mavenCentral() } dependencies { // Our project is going to use VDL, so we need to depend on the Vanadium // Gradle plugin. classpath 'io.v:gradle-plugin:0.5' } } // We're going to be building an application to run apply plugin: 'application' // It's going to use VDL apply plugin: 'io.v.vdl' // And it's going to be written in Java apply plugin: 'java' // This class will contain our server's entry point mainClassName = 'io.v.tutorial.FortuneTutorial' repositories { mavenCentral() } dependencies { // We need the Vanadium Java libraries. compile 'io.v:vanadium:0.1' } vdl { // This is where the VDL tool will look for VDL definitions. inputPaths += 'src/main/java' } EOF
In Java, we must use VDL (Vanadium Definition Language) to define our server interface. We will reuse the same interface definition from the Client/Server Basics tutorial. For this tutorial, the fortune teller server definition lives in src/main/java/io/v/tutorial/fortune.vdl
. Let's create this file now:
mkdir -p src/main/java/io/v/tutorial cat <<EOF > src/main/java/io/v/tutorial/fortune.vdl package fortune type Fortune interface { // Returns a random fortune. Get() (wisdom string | error) // Adds a fortune to the set used by Get(). Add(wisdom string) error } EOF
As you can see, we provide ‘Get’ and ‘Add’ methods to get and add fortunes. We can now test our VDL file by asking Gradle to generate the corresponding Java files. Do this by running
gradle vdl
You should see output like the following:
{{# helpers.code }} :prepareVdl :extractVdl :generateVdl signature time vdltool io/v/tutorial :removeVdlRoot :vdl
BUILD SUCCESSFUL {{/ helpers.code }}
The io/v/tutorial
line indicates that VDL tool has processed your input file. If you now look inside the generated-src
directory, you'll find the following entries:
{{# helpers.code }} generated-src/vdl/io/v/tutorial/FortuneServerWrapper.java generated-src/vdl/io/v/tutorial/FortuneClient.java generated-src/vdl/io/v/tutorial/FortuneServer.java generated-src/vdl/io/v/tutorial/FortuneClientImpl.java generated-src/vdl/io/v/tutorial/FortuneClientFactory.java {{/ helpers.code }}
Now we must provide an implementation for the FortuneServer.
Create src/main/java/io/v/tutorial/InMemoryFortuneServer.java
:
cat <<EOF > src/main/java/io/v/tutorial/InMemoryFortuneServer.java package io.v.tutorial; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; import io.v.v23.V; import io.v.v23.context.VContext; import io.v.v23.naming.Endpoint; import io.v.v23.rpc.Server; import io.v.v23.rpc.ServerCall; import io.v.v23.security.VSecurity; import io.v.v23.verror.VException; public class InMemoryFortuneServer implements FortuneServer { private final List<String> fortunes = new ArrayList<>(); private final Random random = new Random(System.currentTimeMillis()); @Override public String get(VContext ctx, ServerCall call) throws VException { if (fortunes.isEmpty()) { throw new VException("There are no fortunes available, try Add()ing one!"); } else { return fortunes.get(random.nextInt(fortunes.size())); } } @Override public void add(VContext ctx, ServerCall call, String wisdom) throws VException { fortunes.add(wisdom); } public static Endpoint[] startServer() throws VException { // Initialize the Vanadium runtime and load its native shared library // implementation. This is required before we can do anything involving // Vanadium. VContext context = V.init(); // Serve a new InMemoryFortuneServer with an allow-everyone authorizer. // This call will return immediately, serving is done in a separate // thread. Server fortuneServer = V.getServer(V.withNewServer(context, "", new InMemoryFortuneServer(), VSecurity.newAllowEveryoneAuthorizer())); return fortuneServer.getStatus().getEndpoints(); } } EOF
And now let's create our entry point at src/main/java/io/v/tutorial/FortuneTutorial.java
:
cat <<EOF > src/main/java/io/v/tutorial/FortuneTutorial.java package io.v.tutorial; import java.io.IOException; import java.util.Arrays; import io.v.v23.naming.Endpoint; import io.v.v23.verror.VException; public class FortuneTutorial { public static void main(String[] args) throws IOException, VException { Endpoint[] endpoints = InMemoryFortuneServer.startServer(); System.out.println("FortuneServer available at the following endpoints: " + Arrays.toString(endpoints)); System.out.println("Listening for connections, press enter to quit."); System.in.read(); System.out.println("Exiting..."); } } EOF
Now you are ready to build and run the server.
gradle installDist
This will leave an executable script in build/install/fortuneJava/bin/fortuneJava
. When we run it:
{{# helpers.code }} FortuneServer available at the following endpoints: [@5@wsh@127.0.0.1:54221@50704f409f1fc0bf20f01020b02f2030@s@sjr@example.com-15180@@, @5@wsh@192.168.2.4:54221@50704f409f1fc0bf20f01020b02f2030@s@sjr@example.com-15180@@] Listening for connections, press enter to quit.
{{/ helpers.code }}
Excellent, the server is now running. Let's write a client to talk to it.
The VDL step has generated a client stub for us to use to call methods on a server. We obtain stub instances from the generated FortuneClientFactory
class. The only other piece of information we need is the name of the endpoint to which to talk.
Let's create src/main/java/io/v/tutorial/FancyFortuneClient.java
:
cat <<EOF > src/main/java/io/v/tutorial/FancyFortuneClient.java package io.v.tutorial; import io.v.v23.OptionDefs; import io.v.v23.Options; import io.v.v23.context.VContext; import io.v.v23.verror.VException; public class FancyFortuneClient { private final VContext context; private final FortuneClient client; private final Options options; public FancyFortuneClient(VContext context, String endpointName) { this.context = context; this.client = FortuneClientFactory.getFortuneClient(endpointName); // The SKIP_SERVER_ENDPOINT_AUTHORIZATION is necessary because this // tutorial does not deal with trust. If we omit this option, our client // will not trust the server. Your production code should not set this // option because it makes the client vulnerable to man-in-the-middle // attacks. this.options = new Options().set(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION, true); } public String get() throws VException { return client.get(context, options); } public void add(String wisdom) throws VException { client.add(context, wisdom, options); } } EOF
Let's modify the main FortuneTutorial class a little. It will now have three modes depending on how many arguments we pass in:
no arguments: start a Fortune server just as we did in the first part of the tutorial
one argument: fetch a fortune from the endpoint named by that argument
two or more arguments: add the second and subsequent arguments as fortunes to the server named by the first argument
For example:
{{# helpers.code }} build/install/fortuneJava/bin/fortuneJava # run a server build/install/fortuneJava/bin/fortuneJava @5@...@@ # fetch a fortune build/install/fortuneJava/bin/fortuneJava @5@...@@ “Hello world!” # add a fortune {{/ helpers.code }}
Here is the new FortuneTutorial:
cat <<EOF > src/main/java/io/v/tutorial/FortuneTutorial.java package io.v.tutorial; import java.io.IOException; import java.util.Arrays; import io.v.v23.V; import io.v.v23.naming.Endpoint; import io.v.v23.verror.VException; public class FortuneTutorial { public static void main(String[] args) throws IOException, VException { if (args.length > 0) { FancyFortuneClient client = new FancyFortuneClient(V.init(), args[0]); if (args.length >= 2) { for (int i = 1; i < args.length; i++) { client.add(args[i]); } } else { System.out.println(client.get()); } } else { Endpoint[] endpoints = InMemoryFortuneServer.startServer(); System.out.println("FortuneServer available at the following endpoints: " + Arrays.toString(endpoints)); System.out.println("Listening for connections, press enter to quit."); System.in.read(); System.out.println("Exiting..."); } } } EOF
Build the tutorial again:
gradle installDist
Here's an example session:
{{# helpers.code }} $ FortuneServer available at the following endpoints: [@5@wsh@127.0.0.1:36191@c00090af3ff050d020ef3f4f4f40ffff@s@sjr@example.com-28934@@, @5@wsh@192.168.2.4:36191@c00090af3ff050d020ef3f4f4f40ffff@s@sjr@example.com-28934@@] Listening for connections, press enter to quit. {{/ helpers.code }}
In a separate terminal:
{{# helpers.code }}
$ export ENDPOINT=/@5@wsh@192.168.2.4:36191@c00090af3ff050d020ef3f4f4f40ffff@s@sjr@example.com-28934@@ $ build/install/fortuneJava/bin/fortuneJava $ENDPOINT Exception in thread “main” io.v.v23.verror.VException: sjr:<rpc.Client>“/@5@wsh@192.168.2.4:36191@c00090af3ff050d020ef3f4f4f40ffff@s@sjr@example.com-28934@@”.Get: Error: There are no fortunes available, try Add()ing one! at io.v.v23.verror.VExceptionVdlConverter.nativeFromVdlValue(VExceptionVdlConverter.java:70) at io.v.v23.verror.VExceptionVdlConverter.nativeFromVdlValue(VExceptionVdlConverter.java:22) at io.v.v23.vom.BinaryDecoder.readValue(BinaryDecoder.java:190) at io.v.v23.vom.BinaryDecoder.readValueMessage(BinaryDecoder.java:118) at io.v.v23.vom.BinaryDecoder.decodeValue(BinaryDecoder.java:85) at io.v.v23.vom.VomUtil.decode(VomUtil.java:104) at io.v.impl.google.rpc.ClientCallImpl.nativeFinish(Native Method) at io.v.impl.google.rpc.ClientCallImpl.finish(ClientCallImpl.java:35) at io.v.tutorial.FortuneClientImpl.get(FortuneClientImpl.java:62) at io.v.tutorial.FancyFortuneClient.get(FancyFortuneClient.java:21) at io.v.tutorial.FortuneTutorial.main(FortuneTutorial.java:19) $ build/install/fortuneJava/bin/fortuneJava $ENDPOINT “Hello, world!” $ build/install/fortuneJava/bin/fortuneJava $ENDPOINT Hello, world!
{{/ helpers.code }}
{{# helpers.hidden }}
rm -Rf $JAVA_PROJECT_DIR
{{/ helpers.hidden }}
Congratulations! You have successfully run the Java fortune example.
You have: