How to build actions with Java Client Library on Azure App Service

Yoichiro Tanaka
Google Developer Experts
6 min readJan 23, 2019

Finally, a new client library for Actions on Google 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 Azure App Service.

Prerequisite

You need to prepare the following:

  • Java Development Kit Version 1.8 or higher.
  • Maven version 3.6.0 or higher.
  • You have already registered your account on Azure.
  • You have already installed Azure CLI and signed in to the Azure.
  • You have already understood how to build actions for Google Assistant on Google Cloud Platform or Amazon Web Services.
  • You already have your Actions on Google project and your Dialogflow agent that has already been connected to the AoG project.

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

Also, you need to sign in to Azure with Azure CLI. If you didn’t sign in yet, execute the following commands:

$ az login
$ az account set --subscription <SUBSCRIPTION_ID>

Create Your Resource Group

Let’s get started to create your action. First, create a new resource group using the following commands:

$ az group create --name <RESOURCE_GROUP_NAME> --location <LOCATION_NAME>

Create Project

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

$ mvn archetype:generate -DgroupId=jp.eisbahn.actions.simpleaction -DartifactId=simple-action -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false

After executing the command above, you have the simple-action directory and some files are generated in the directory. But some files are unnecessary for this article. Delete the following files and directories:

  • src/main/webapp/WEB-INF/web.xml
  • src/main/webapp/index.jsp

Edit pom.xml File

After creating your project, replace pom.xml file with the following content:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jp.eisbahn.actions.simpleaction</groupId>
<artifactId>simple-action</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<name>simple-action Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.actions</groupId>
<artifactId>actions-on-google</artifactId>
<version>1.0.1</version>
</dependency>
</dependencies>
<build>
<finalName>simple-action</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>1.5.2</version>
<configuration>
<schemaVersion>v2</schemaVersion>
<resourceGroup>__RESOURCE_GROUP_NAME__</resourceGroup>
<appName>__APP_NAME__</appName>
<region>__LOCATION_NAME__</region>
<pricingTier>__PRICE_TEAR_NAME__</pricingTier>
<runtime>
<os>windows</os>
<javaVersion>1.8</javaVersion>
<webContainer>tomcat 8.5</webContainer>
</runtime>
<deployment>
<resources>
<resource>
<directory>${project.basedir}/target</directory>
<includes>
<include>*.war</include>
</includes>
</resource>
</resources>
</deployment>
</configuration>
</plugin>
</plugins>
</build>
</project>

In the code above, the azure-webapp-maven-plugin plugin element has some placeholders. You need to replace the following placeholders:

  • __RESOURCE_GROUP_NAME__: The resource group name you created.
  • __APP_NAME__: The identifier for your application.
  • __LOCATION_NAME__: The location name you want to create the app.
  • __PRICING_TIER_NAME__: The pricing tier name.

The azure-webapp-maven-plugin generates a new app service automatically, if it does not exists. Decide their names now and replace them.

For Java Development Kit version 11

If you’re using Java Development Kit version 11 or higher, you will see an error at executing mvn command with the pom.xml file above. The reason is that JAXB was deprecated. To fix this issue, you need to add the following definition to the azure-webapp-maven-plugin plugin element:

<plugin>
<groupId>com.microsoft.azure</groupId>
<artifactId>azure-webapp-maven-plugin</artifactId>
<version>1.5.2</version>
<configuration>
...
</configuration>
<!-- Add the following definition -->
<dependencies>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.4.0-b180830.0359</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.4.0-b180830.0438</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-core</artifactId>
<version>2.3.0.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</plugin>

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 simpleactiondirectory 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, Azure App Service!");
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 simpleactiondirectory 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.

Deploy Your Code

Good job! You have all necessary files. Now, you can deploy your action to Azure.

Execute the following command to deploy your action:

$ mvn package azure-webapp:deploy

If you see like the following output, the deployment was succeeded.

[INFO] Scanning for projects...
[INFO]
[INFO] -----------< jp.eisbahn.actions.simpleaction:simple-action >------------
[INFO] Building simple-action Maven Webapp 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
...
[INFO] Deploying the war file simple-action.war...
[INFO] Successfully deployed the artifact to https://<APP_NAME>.azurewebsites.net
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 25.064 s
[INFO] Finished at: 2019-01-23T15:57:41+09:00
[INFO] ------------------------------------------------------------------------

You can test it by the following command:

$ curl -v -X POST -d "{}" https://<APP_NAME>.azurewebsites.net/

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

< HTTP/1.1 500  
< Content-Length: 55
< X-Powered-By: ASP.NET
< Set-Cookie: ARRAffinity=932c5351a290b46f876f6d4453f2bdc8625e8682cbfa134e04eb88e0677cb1fc;Path=/;HttpOnly;Domain=<APP_NAME>.azurewebsites.net
< Date: Wed, 23 Jan 2019 07:02:34 GMT
<
* Connection #0 to host <APP_NAME>.azurewebsites.net 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, Azure App Service!” 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:

--

--