Skip to content

Commit

Permalink
Allow parsing request and writing responses using java streams
Browse files Browse the repository at this point in the history
  • Loading branch information
ansman committed Dec 17, 2019
1 parent 7f3b1e2 commit 5357dda
Show file tree
Hide file tree
Showing 11 changed files with 660 additions and 215 deletions.
12 changes: 12 additions & 0 deletions src/main/kotlin/com/google/actions/api/ActionResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import com.google.api.services.actions_fulfillment.v2.model.AppResponse
import com.google.api.services.actions_fulfillment.v2.model.ExpectedIntent
import com.google.api.services.actions_fulfillment.v2.model.RichResponse
import com.google.api.services.dialogflow_fulfillment.v2.model.WebhookResponse
import java.io.IOException
import java.io.OutputStream

/**
* Defines requirements of an object that represents a response from the Actions
Expand Down Expand Up @@ -58,6 +60,16 @@ interface ActionResponse {
*/
val helperIntent: ExpectedIntent?

/**
* Writes the JSON representation of the response to the given output stream.
*
* This is more efficient than calling [toJson] first and then writing the string.
*
* @param outputStream The output stream to write to. Must be closed by the caller.
*/
@Throws(IOException::class)
fun writeTo(outputStream: OutputStream)

/**
* Returns the JSON representation of the response.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/main/kotlin/com/google/actions/api/ActionsSdkApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package com.google.actions.api
import com.google.actions.api.impl.AogRequest
import com.google.actions.api.response.ResponseBuilder
import org.slf4j.LoggerFactory
import java.io.InputStream

/**
* Implementation of App for ActionsSDK based webhook. Developers must extend
Expand Down Expand Up @@ -50,6 +51,11 @@ open class ActionsSdkApp : DefaultApp() {
return AogRequest.create(inputJson, headers)
}

override fun createRequest(inputStream: InputStream, headers: Map<*, *>?): ActionRequest {
LOG.info("ActionsSdkApp.createRequest..")
return AogRequest.create(inputStream, headers)
}

override fun getResponseBuilder(request: ActionRequest): ResponseBuilder {
val responseBuilder = ResponseBuilder(
usesDialogflow = false,
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/com/google/actions/api/DefaultApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.actions.api

import com.google.actions.api.response.ResponseBuilder
import org.slf4j.LoggerFactory
import java.io.InputStream
import java.util.concurrent.CompletableFuture

/**
Expand All @@ -42,6 +43,20 @@ abstract class DefaultApp : App {
abstract fun createRequest(inputJson: String, headers: Map<*, *>?):
ActionRequest

/**
* Creates an ActionRequest from the specified input stream and metadata.
*
* This is semantically equivalent to reading the stream as a String using
* UTF-8 encoding and then calling `createRequest` with the resulting
* string.
*
* @param inputStream The input stream. Must be closed by the caller
* @param headers Map containing metadata, usually from the HTTP request
* headers.
*/
abstract fun createRequest(inputStream: InputStream, headers: Map<*, *>?):
ActionRequest

/**
* @return A ResponseBuilder for this App.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/main/kotlin/com/google/actions/api/DialogflowApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package com.google.actions.api

import com.google.actions.api.impl.DialogflowRequest
import com.google.actions.api.response.ResponseBuilder
import java.io.InputStream

/**
* Implementation of App for Dialogflow based webhook. Developers must extend
Expand Down Expand Up @@ -48,6 +49,10 @@ open class DialogflowApp : DefaultApp() {
return DialogflowRequest.create(inputJson, headers)
}

override fun createRequest(inputStream: InputStream, headers: Map<*, *>?): ActionRequest {
return DialogflowRequest.create(inputStream, headers)
}

override fun getResponseBuilder(request: ActionRequest): ResponseBuilder {
val responseBuilder = ResponseBuilder(
usesDialogflow = true,
Expand Down
462 changes: 315 additions & 147 deletions src/main/kotlin/com/google/actions/api/impl/AogRequest.kt

Large diffs are not rendered by default.

63 changes: 35 additions & 28 deletions src/main/kotlin/com/google/actions/api/impl/DialogflowRequest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import com.google.api.services.actions_fulfillment.v2.model.*
import com.google.api.services.dialogflow_fulfillment.v2.model.*
import com.google.gson.*
import com.google.gson.reflect.TypeToken
import java.io.InputStream
import java.io.InputStreamReader
import java.lang.reflect.Type
import java.util.*

Expand Down Expand Up @@ -168,38 +170,43 @@ internal class DialogflowRequest internal constructor(
}

companion object {

fun create(body: String, headers: Map<*, *>?): DialogflowRequest {
val gson = Gson()
return create(gson.fromJson(body, JsonObject::class.java), headers)
}

fun create(json: JsonObject, headers: Map<*, *>?): DialogflowRequest {
val gsonBuilder = GsonBuilder()
gsonBuilder
.registerTypeAdapter(WebhookRequest::class.java,
WebhookRequestDeserializer())
.registerTypeAdapter(QueryResult::class.java,
QueryResultDeserializer())
.registerTypeAdapter(Context::class.java,
ContextDeserializer())
.registerTypeAdapter(OriginalDetectIntentRequest::class.java,
OriginalDetectIntentRequestDeserializer())

val gson = gsonBuilder.create()
val webhookRequest = gson.fromJson<WebhookRequest>(json,
WebhookRequest::class.java)
val aogRequest: AogRequest
private val gson = GsonBuilder()
.registerTypeAdapter(WebhookRequest::class.java,
WebhookRequestDeserializer())
.registerTypeAdapter(QueryResult::class.java,
QueryResultDeserializer())
.registerTypeAdapter(Context::class.java,
ContextDeserializer())
.registerTypeAdapter(OriginalDetectIntentRequest::class.java,
OriginalDetectIntentRequestDeserializer())
.create()

fun create(body: String, headers: Map<*, *>?): DialogflowRequest =
create(gson.fromJson(body, WebhookRequest::class.java), headers)

fun create(json: JsonObject, headers: Map<*, *>?): DialogflowRequest =
create(gson.fromJson(json, WebhookRequest::class.java), headers)

fun create(inputStream: InputStream, headers: Map<*, *>?): DialogflowRequest =
create(
gson.fromJson(InputStreamReader(inputStream), WebhookRequest::class.java),
headers
)

private fun create(
webhookRequest: WebhookRequest,
headers: Map<*, *>?
): DialogflowRequest {

val originalDetectIntentRequest =
webhookRequest.originalDetectIntentRequest
webhookRequest.originalDetectIntentRequest
val payload = originalDetectIntentRequest?.payload
if (payload != null) {
aogRequest = AogRequest.create(gson.toJson(payload), headers,
partOfDialogflowRequest = true)
val aogRequest = if (payload != null) {
AogRequest.create(gson.toJson(payload), headers,
partOfDialogflowRequest = true)
} else {
aogRequest = AogRequest.create(JsonObject(), headers,
partOfDialogflowRequest = true)
AogRequest.create(JsonObject(), headers,
partOfDialogflowRequest = true)
}

return DialogflowRequest(webhookRequest, aogRequest)
Expand Down
32 changes: 26 additions & 6 deletions src/main/kotlin/com/google/actions/api/smarthome/SmartHomeApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.google.home.graph.v1.HomeGraphApiServiceProto
import io.grpc.ManagedChannelBuilder
import io.grpc.auth.MoreCallCredentials
import java.io.FileInputStream
import java.io.InputStream
import java.util.concurrent.CompletableFuture

abstract class SmartHomeApp : App {
Expand All @@ -49,6 +50,18 @@ abstract class SmartHomeApp : App {
return SmartHomeRequest.create(inputJson)
}

/**
* Builds a [SmartHomeRequest] object from an [InputStream].
*
* This is semantically equivalent as reading the input stream as an UTF-8 string and then calling createRequest
* with the resulting string.
*
* @param inputStream The input stream to read from. The stream must be closed by the caller.
* @return A parsed request object
*/
fun createRequest(inputStream: InputStream): SmartHomeRequest =
SmartHomeRequest.create(inputStream)

/**
* The intent handler for action.devices.SYNC that is implemented in your smart home Action
*
Expand Down Expand Up @@ -140,17 +153,24 @@ abstract class SmartHomeApp : App {

return try {
val request = createRequest(inputJson)
val response = routeRequest(request, headers)

val future: CompletableFuture<SmartHomeResponse> = CompletableFuture()
future.complete(response)
future.thenApply { this.getAsJson(it) }
.exceptionally { throwable -> throwable.message }
handleRequest(request, headers)
.thenApply { getAsJson(it) }
.exceptionally { throwable -> throwable.message }
} catch (e: Exception) {
handleError(e)
}
}

fun handleRequest(request: SmartHomeRequest, headers: Map<*, *>?): CompletableFuture<SmartHomeResponse> =
try {
val response = routeRequest(request, headers)
CompletableFuture.completedFuture(response)
} catch (e: Exception) {
CompletableFuture<SmartHomeResponse>()
.apply { completeExceptionally(e) }
}


@Throws(Exception::class)
private fun routeRequest(request: SmartHomeRequest, headers: Map<*, *>?): SmartHomeResponse {
when (request.javaClass) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@

package com.google.actions.api.smarthome

import com.google.gson.JsonObject
import org.json.JSONObject
import org.json.JSONTokener
import java.io.IOException
import java.io.InputStream

/**
* A representation of the JSON payload received during a smart home request.
Expand All @@ -32,8 +36,13 @@ open class SmartHomeRequest {
}

companion object {
fun create(inputJson: String): SmartHomeRequest {
val json = JSONObject(inputJson)
fun create(inputStream: InputStream): SmartHomeRequest =
create(JSONObject(JSONTokener(inputStream)))

fun create(inputJson: String): SmartHomeRequest =
create(JSONObject(inputJson))

private fun create(json: JSONObject): SmartHomeRequest {
val requestId = json.getString("requestId")
val inputs = json.getJSONArray("inputs")
val request = inputs.getJSONObject(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,25 @@ import com.google.protobuf.Struct
import com.google.protobuf.util.JsonFormat
import org.json.JSONException
import org.json.JSONObject
import org.json.JSONStringer
import org.json.JSONTokener
import org.json.JSONWriter
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.io.StringWriter

/**
* A representation of the JSON payload that should be sent during a smart home request.
*
* @see <a href="https://developers.google.com/actions/smarthome/develop/process-intents">Public documentation</a>
*/
open class SmartHomeResponse {
open fun writeToStream(outputStream: OutputStream) {
val writer = OutputStreamWriter(outputStream)
build().write(writer)
writer.flush()
}

open fun build(): JSONObject {
return JSONObject() // Return empty object
}
Expand Down
Loading

0 comments on commit 5357dda

Please sign in to comment.