How to build actions with Java Client Library on Google AppEngine

Last week, a new client library has been released. The name is “Actions on Google Java/Kotlin Client Library.

Until now, most developers can use a client library called “Actions on Google Client Library for Node.js”. That is, we had only the way to build actions with Node.js. But, currently, we can build actions with Java programming language easily using the Java/Kotlin Client Library.

The abstract of usage was described on the following article:

In this article, I introduce how to build actions with the Java/Kotlin Client Library and to deploy them to the Google AppEngine.

Prerequisite

Before building actions, you need to install the following:

  • Java Development Kit Version 1.8 or higher.
  • Gradle version 5.1.1 or higher.
  • Google Cloud SDK
  • App Engine SDK for Java (you can install it with the following command)
gcloud components install app-engine-java
  • You already have your Actions on Google project and your Dialogflow agent that has already been connected to the AoG project.

Also, you have a project on Google Cloud Platform, which the billing has already been enabled.

If you don’t know how to build actions, you can study it with codelabs:

Create Project

Let’s get started to build your action. First, create your new project with Gradle. In this article, we use “simple-fulfillment-java” as the project name and “jp.eisbahn.actions.simpleaction” as the package name (The “jp.eisbahn” is the domain I have).

$ mkdir simple-fulfillment-appengine
$ cd simple-fulfillment-appengine
$ gradle init

The gradle command asks you some questions. Answer them with the following:

  • Select type of project to generate: 1 (basic)
  • Select build script DSL: 1 (groovy)
  • Project name: “simple-fulfillment-java”

Some files including build.gradle file are generated.

Edit Build File

After creating your project, the build.gradle file is empty. Replace it with the following content:

buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.+'
}
}
repositories {
jcenter()
mavenCentral()
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'com.google.cloud.tools.appengine'
dependencies {
compile 'com.google.appengine:appengine-api-1.0-sdk:+'
compile group: 'com.google.actions', name: 'actions-on-google', version: '1.0.0'
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
}
appengine {
deploy {
}
}
group = 'jp.eisbahn.actions.simpleaction'
version = '1.0-SNAPSHOT'
sourceCompatibility = '1.8'
targetCompatibility = '1.8'

Create Fulfillment Code

Next, you need to create your fulfillment code for your action. Create a source directory with the following command:

$ mkdir -p src/main/java/jp/eisbahn/actions/simpleaction

Then, create your fulfillment file called “SimpleApp.java” in the simpleaction directory with the following content:

package jp.eisbahn.actions.simpleaction;
import com.google.actions.api.ActionRequest;
import com.google.actions.api.ActionResponse;
import com.google.actions.api.DialogflowApp;
import com.google.actions.api.ForIntent;
import com.google.actions.api.response.ResponseBuilder;
public class SimpleApp extends DialogflowApp {
    @ForIntent("Default Welcome Intent")
public ActionResponse welcome(ActionRequest request) {
ResponseBuilder builder = getResponseBuilder(request);
builder.add("Hello, Google AppEngine!");
return builder.build();
}
}

In the code above, the SimpleApp class extends the DialogflowApp class. That is, the class can handle requests from Dialogflow Agent. And, you can create intent handler methods using ForIntent annotation.

The first argument has information of the request. And, you can create the ActionResponse instance with the ResponseBuilder class.

You can know how to build responses by reading the API reference above.

Create Servlet Code

By the way, the SimpleApp class does not have any abilities to handle HTTP requests. Instead, you need to create a handler class to handle requests and responses. In this article, create a Java Servlet code to handle HTTP requests and response.

Create your fulfillment file called “SimpleServlet.java” in the simpleaction directory with the following content:

package jp.eisbahn.actions.simpleaction;
import com.google.actions.api.App;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
@WebServlet(name = "SimpleServlet", urlPatterns = {"/"}, loadOnStartup = 1)
public class SimpleServlet extends HttpServlet {
    private App app = new SimpleApp();
    @Override
protected void doPost(
HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String requestBody =
req.getReader().lines().collect(Collectors.joining());
Map<String, String> headersMap =
Collections.list(req.getHeaderNames())
.stream()
.collect(Collectors.toMap(
name -> name,
req::getHeader));
try {
String responseBody =
app.handleRequest(requestBody, headersMap).get();
resp.setContentType("application/json; charset=utf-8");
resp.getWriter().write(responseBody);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
resp.setStatus(
HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
resp.getWriter().write(e.getMessage());
}
}
}

To call the SimpleApp instance, you need the following information:

  • The string of the request body.
  • The Map<String, String> object that has all request header entries.

Then, call the handleRequest() method of the SimpleApp instance with these arguments. If the result of calling the method is successfully, the CompletableFuture<String> object is returned. This is for asynchronous like Promise in JavaScript. In the code above, the actual result is retrieved by calling the get() method.

The result is a string value of the response represents as JSON string. The SimpleServlet sends the response string with the Writer object retrieved by calling the getWriter() method.

Create Configuration File for AppEngine

Last, you need to prepare a configuration file for AppEngine. Create a new directory where you put the file by the following command:

$ mkdir -p src/main/webapp/WEB-INF

Then, create the following file called “appengine-web.xml” into the WEB-INF directory:

<?xml version="1.0" encoding="UTF-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<runtime>java8</runtime>
<threadsafe>true</threadsafe>
</appengine-web-app>

Deploy Your Code to AppEngine

Ok, you created all files. Now, you can deploy your code to the AppEngine. First, if other project is selected, you need to specify your target project that you want to deploy your code.

$ gcloud config set project <YOUR_PROJECT_ID>

Let’s deploy your code by the following command:

$ gradle appengineDeploy

If the deployment is successfully, you can test it by the following command:

$ curl -v -X POST -d "{}" https://<PROJECT_ID>.appspot.com/

It’s Ok, if you receive like the following output:

< HTTP/2 500 
< x-cloud-trace-context: 4ba6987ee3351d91cd43fff194b79962;o=1
< date: Mon, 21 Jan 2019 00:36:39 GMT
< content-type: text/html
< server: Google Frontend
< content-length: 55
< alt-svc: quic=":443"; ma=2592000; v="44,43,39,35"
<
* Curl_http_done: called premature == 0
* Connection #0 to host azure-test-65d3c.appspot.com left intact
java.lang.Exception: Intent handler not found - INVALID%

The message “Intent handler not found” was returned by the Actions on Google Java/Kotlin Client Library.

After deploying, register the URL as the fulfillment webhook URL on your Dialogflow agent. Then, invoke your action on the Actions simulator. You should get the response “Hello, Google AppEngine!” from the Google Assistant.

Conclusion

If you’re a Java programmer, you can build your actions for Google Assistant with the Actions on Google Java/Kotlin Client Library and the Java language that you are used to. I’m happy if this article becomes useful as you when you’re interested in building actions for the Google Assistant.

Last, you can get the complete code set from the following GitHub repository: