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;

[code]
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;

}

}
[/code]

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:

[code]
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’

}
[/code]

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):

[code]
$ gradle jar
[/code]

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:

[code]
$ jar -tf build/libs/hello-1.0.jar

META-INF/

META-INF/MANIFEST.MF

example/

example/Hello.class
[/code]

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:

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

ok: created action hello-java
[/code]

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:

[code]
$ wsk action invoke -br hello-java -p name reader

{

"greeting": "Hello reader!"

}
[/code]

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:

[code]
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;

}

}
[/code]

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:

[code]
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)

}

}

}
[/code]

(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:

[code]
$ gradle jar
[/code]

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:

[code]
$ 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
[/code]

More Compute Services stories
October 16, 2018

IBM Cloud Kubernetes Service: Deployment Patterns for Maximizing Throughput and Availability

With the upcoming release of Kubernetes version 1.12 on IBM Cloud Kubernetes Service, we are releasing the new IKS LoadBalancer 2.0 for public beta so that customers may test. This article discusses the capabilities of this LoadBalancer service and a few deployment patterns around it, providing examples along the way.

Continue reading

October 10, 2018

Kubernetes API Server Log Collection

With the latest IBM Cloud Kubernetes Service CLI plug-in, you can collect your Kubernetes API server logs and drop them in an IBM Cloud Object Storage (COS) bucket. These API server logs are an invaluable resource because they record every request that passes through the Kubernetes API server.

Continue reading

October 9, 2018

IBM Cloud Foundry Enterprise Environment: Real-World Usage

The Cloud Foundry Enterprise Environment (CFEE) offering provides key security isolation for application hosting for critical regulatory requirements, geo-location restrictions, and other important reasons. Check out some real-world examples of applications deployed on the CFEE.

Continue reading