Compute Services

Building OpenWhisk actions with Java and Gradle

Share this post:

openwhisk-hero-image

In this post, using Gradle as a build tool, I will demonstrate how to build Apache OpenWhisk actions in Java.

(If you are in a hurry, all the code discussed in this post is available on GitHub.)

Why Java?

Since scripting languages in general, and JavaScript in particular are indeed a sensible choice for small, self-contained, short-lived actions, it’s no surprise that Node.js is the most popular of the multiple runtimes that OpenWhisk supports.

There are scenarios, however, in which Java is still the most appropriate choice–for example, if you are breaking down an existing application into small, serverless components and want to reuse code to the extent possible; or you simply want to leverage libraries from the very rich Java ecosystem.

Hello Java

For all OpenWhisk actions, regardless of the implementation language, OpenWhisk accepts and returns JSON objects.

The entry point of a Java-defined action has a signature that is almost familiar:

public static JsonObject main(JsonObject args);

The class JsonObject in the signature refers to the Google GSON library. In the absence of an officially sanctioned JSON library in the Java standard library, OpenWhisk relies on one of the most popular. Note that you can also declare the method to throw any exception: Java exceptions are caught by the runtime and translate into action invocation errors.

The full source code of a “hello world” Java action is given below:

package example;

import com.google.gson.JsonObject;

public class Hello {

public static JsonObject main(JsonObject args) {

String name = args.getAsJsonPrimitive("name").getAsString();

JsonObject response = new JsonObject();

response.addProperty("greeting", "Hello " + name + "!");

return response;

}

}

Building

One major difference between Java and scripting languages is that the actions need to be compiled into a .jar file before they are uploaded to OpenWhisk. In our examples, we use Gradle, as it is relatively lightweight and simple to configure.

We set up a project with following filesystem layout:

hello/build.gradle

hello/src/main/java/example/Hello.java

The Java file is given above, and the Gradle file has the following contents:

apply plugin: 'java'

version = '1.0'

repositories {

mavenCentral()

}

dependencies {

compile 'com.google.code.gson:gson:2.6.2'

}

The only important lines relate to the resolution of dependencies: because our Java action relies on the GSON library, it needs to be included in the classpath. Gradle automatically fetches the library from Maven Central (or other software repositories you may have configured locally).

We can build the project with a single command, which we run from within the hello directory (where build.gradleresides):

$ gradle jar

This compiles our action into a .class file, and packages it as a .jar file. We can look at the contents of this archive as follows:

$ jar -tf build/libs/hello-1.0.jar

META-INF/

META-INF/MANIFEST.MF

example/

example/Hello.class

Observe that the archive contains only our class, and not the GSON dependency. This is not an issue, as the OpenWhisk runtime provides this dependency.

We can now deploy our action:

$ wsk action create hello-java build/libs/hello-1.0.jar --main example.Hello

ok: created action hello-java

The action creation process is similar to other runtimes, with the notable difference that one must provide the name of the main class with the –main flag. We can finally confirm that our Java action is working as expected:

$ wsk action invoke -br hello-java -p name reader

{

"greeting": "Hello reader!"

}

On the shoulders of giants

Our previous example demonstrates the very first steps in running Java actions. As mentioned above, however, part of the appeal of Java is the rich ecosystem of libraries. To demonstrate the use of external dependencies, we build an OpenWhisk action to generate QR codes. Specifically, the action accepts text as an argument, and returns a base64-encoded PNG image or a QR code encoding the text. With the ZXing library, the action code is relatively short:

package qr;

import java.io.*;

import java.util.Base64;

import com.google.gson.JsonObject;

import com.google.zxing.*;

import com.google.zxing.client.j2se.MatrixToImageWriter;

import com.google.zxing.common.BitMatrix;

public class Generate {

public static JsonObject main(JsonObject args) throws Exception {

String text = args.getAsJsonPrimitive("text").getAsString();

ByteArrayOutputStream baos = new ByteArrayOutputStream();

OutputStream b64os = Base64.getEncoder().wrap(baos);

BitMatrix matrix = new MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, 300, 300);

MatrixToImageWriter.writeToStream(matrix, "png", b64os);

b64os.close();

String output = baos.toString("utf-8");

JsonObject response = new JsonObject();

response.addProperty("qr", output);

return response;

}

}

Building

If we build the code above as we did the first example, our .jar file will contain a single class Generate.class. The action will not run on OpenWhisk, since it will be missing the com.google.zxing dependencies. One way to address this issue is to create a so-called “fat jar” (or “über-jar”): a single archive containing, in addition to the action code, all classes from all dependencies.

We can configure Gradle to build such a file. Below is the build.gradle for the QR code action:

apply plugin: 'java'

version = '1.0'

repositories {

mavenCentral()

}

configurations {

provided

compile.extendsFrom provided

}

dependencies {

provided 'com.google.code.gson:gson:2.6.2'

compile 'com.google.zxing:core:3.3.0'

compile 'com.google.zxing:javase:3.3.0'

}

jar {

dependsOn configurations.runtime

from {

(configurations.runtime - configurations.provided).collect {

it.isDirectory() ? it : zipTree(it)

}

}

}

(The code above was modeled after this blog post.)

The Gradle configuration file does two important things:

  1. It declares a provided configuration in addition to the default compile and runtime configurations. We use providedfor dependencies that are required at build time but that should not be included in the final fat jar. In our case, this concerns the GSON library which, as we know, is already provided by the OpenWhisk runtime.
  2. It overrides the jar task to include all classes found in all dependencies, except those marked as provided.

With this new configuration in place, we can build our .jar file:

$ gradle jar

If we now inspect the contents of build/libs/qr-1.0.jar, we see over 600 included classes (and no GSON).

We can finally deploy our serverless QR generator:

$ wsk action create qr build/libs/qr-1.0.jar --main qr. Generate

$ wsk action invoke -br qr -p text 'Hello world!'

{

"qr": "iVBOR...YII="

}

Looking at base64-encoded images is not exactly as enticing as looking at the images themselves, unfortunately. Luckily, using <a href="https://stedolan.github.io/jq/">jq</a> and some pipes, we can build those images:

wsk action invoke -br qr -p text 'Hello world!' | jq -r .qr | base64 -D &gt; qr.png
More Compute Services stories
August 15, 2018

Building IBM Cloud Functions with TypeScript

Looking for a starter kit to help out with building IBM Cloud Functions with Typescript? Don't worry, we've got you covered in this article.

Continue reading

August 9, 2018

Which Way Now For Cloud Foundry?

Are you a Cloud Foundry user? If so, then you're the world-leading expert on how CF snaps into your technical environment. Take the Cloud Foundry Foundation's annual survey to make your voice heard.

Continue reading

August 6, 2018

Dispelling Five Myths of Low-Code App Development

More and more enterprises are adopting low-code development platforms to speed software delivery. Low-code platforms offer the necessary tools for both the business and IT to collaborate and build valuable enterprise apps at speed while maintaining control over the entire application lifecycle.

Continue reading