Setting up your development environment
We encourage you to use an IDE to write Java code. We provide a skeleton gradle project which you can use to setup your development environment.
Clone this github repo. This is a gradle project. You may need to modify build.gradle file to make it work with your favourite IDE.
If you would like to not use the above project & create your own from scratch, then you will find the below information useful:
1) http://104.155.191.79:8081/nexus/content/repositories/thirdparty is the URL of our custom mavel repository which hosts our custom code jar.
Group id: com.altin Artifact id: custom-code Version: 1.0.4
2) We also use the following dependencies (represented in gradle format):
compile("com.amazonaws:aws-lambda-java-core:1.1.0")
compile group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.0.1'
compile group: 'javax.ws.rs', name: 'javax.ws.rs-api', version: '2.0.1'
compile 'org.glassfish.jersey.containers:jersey-container-servlet:2.14'
compile group: 'org.glassfish.jersey.media', name: 'jersey-media-json-jackson', version: '2.19'
compile 'org.glassfish.jersey.core:jersey-client:2.23.1'
Introduction
The skeleton Java code is below.
Please note that class name & package name has to be MorphCustomCode & com.altin.custom_code. They cannot be changed.
package com.altin.custom_code;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import morph.base.beans.CustomCodeResponse;
import java.util.Map;
public class MorphCustomCode implements RequestHandler<Map<String, Object>, CustomCodeResponse> {
@Override
public CustomCodeResponse handleRequest(Map<String, Object> input, Context context) {
}
}
input
contains the customer and user scope variables. Customer scope variables are present in "userVariables"
key and Conversation scope variables are present in "flowVariables"
key.
A sample input JSON looks like:
{
"userVariables": {
"city": "New York",
"age": 17,
"orders": [
"#o-111",
"#o-122"
]
},
"flowVariables": {
"booking date": "1st July 2017"
}
}
Do not worry about the context
argument.
CustomCodeResponse
Let's see what's there in the response that we need to return.
public class CustomCodeResponse {
private List<Action> actions;
public CustomCodeResponse() {
}
public List<Action> getActions() {
return this.actions;
}
public void setActions(List<Action> actions) {
this.actions = actions;
}
public String toString() {
return "CustomCodeResponse{actions=" + this.actions + '}';
}
}
Above is the source code of the bean that we need to return. Simple enough. The most important thing is the Action
bean. Lets look at it.
Action
There are three types of Actions:
Action Bean | Description |
---|---|
GoToFlowAction | This is jump module of UI. It redirects to a conversation. |
PublishMessageAction | Used to send a message back to user. |
SetVariableAction | This action is used to set an attribute value. The attribute can either be of FLOW or USER scope. |
The usage of these actions is described in examples below.
Examples
1) Extract a value of an attribute and setting some attributes
In the below code we are reading the phone number of the user & checking if it exists in our database. If it does then we are setting some of the attributes. If it does not then we are taking the user to another conversation named "PHONE_DOES_NOT_EXIST"
.
package com.altin.custom_code;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import jersey.repackaged.com.google.common.collect.Lists;
import jersey.repackaged.com.google.common.collect.Maps;
import morph.base.actions.Action;
import morph.base.actions.StringVariable;
import morph.base.actions.VariableScope;
import morph.base.actions.impl.GoToFlowAction;
import morph.base.actions.impl.SetVariableAction;
import morph.base.beans.CustomCodeResponse;
import java.util.List;
import java.util.Map;
/**
* @author ishanjain
* @since 18/07/17
*/
public class MorphCustomCode implements RequestHandler<Map<String, Object>, CustomCodeResponse> {
private static Map<String, NumberDetails> numberVsDetails = Maps.newHashMap();
static {
List<NumberDetails> numbers = Lists.newArrayList();
numbers.add(new NumberDetails("9711xxx400", "Sumit Mehra", "Apple", "iPhone 6S", null));
numbers.add(new NumberDetails("8130xxx599", "Rahul Sachar", "Xiaomi", "Redmi 4", null));
for (NumberDetails number : numbers) {
numberVsDetails.put(number.number, number);
}
}
@Override
public CustomCodeResponse handleRequest(Map<String, Object> input, Context context) {
/**
* This is how you read variable. If can be userVariables or flowVariables, depending on the scope of the variable
*/
Map<String, Object> userVariables = (Map<String, Object>) input.get("userVariables");
/**
* Read the variable, using the name of the variable
*/
String phoneNumber = (String) userVariables.get("_PHONE_NUMBER");
List<Action> actions = Lists.newArrayList();
if (numberVsDetails.get(phoneNumber) != null) {
NumberDetails numberDetails = numberVsDetails.get(phoneNumber);
/**
* Setting a variable
*/
actions.add(new SetVariableAction("NAME", VariableScope.FLOW, new StringVariable().value(
numberDetails.name)));
actions.add(new SetVariableAction("COMPANY", VariableScope.FLOW, new StringVariable().value(
numberDetails.handsetCompany)));
actions.add(new SetVariableAction("MODEL", VariableScope.FLOW, new StringVariable().value(
numberDetails.handsetModel)));
CustomCodeResponse customCodeResponse = new CustomCodeResponse();
customCodeResponse.setActions(actions);
return customCodeResponse;
}
CustomCodeResponse customCodeResponse = new CustomCodeResponse();
GoToFlowAction e = new GoToFlowAction(null);
e.setNextFlowTitle("PHONE_DOES_NOT_EXIST");
actions.add(e);
return customCodeResponse;
}
private static class NumberDetails {
private String number;
private String name;
private String handsetCompany;
private String handsetModel;
private String link;
public NumberDetails(String number, String name, String handsetCompany, String handsetModel, String link) {
this.number = number;
this.name = name;
this.handsetCompany = handsetCompany;
this.handsetModel = handsetModel;
this.link = link;
}
}
}
2) Sending a message to the user
Let's see how we can reply back to the user. There are various types of messages that you can send. They are:
1) statement (Text message with optional buttons) 2) carousel (A horizontal carousal message) 3) list (A vertical carousal message) 4) media (Image/Video message)
All of these messages can contain suggestions (Quick Replies).
suggestions (Quick Replies)
Below code will demonstrate how to add suggestions to any kind of message. The code assumes statement (text) message.
TextMessagePayload payload = new TextMessagePayload();
payload.setText(
"Thank you for booking the appointment. Please click on the button below to pay appointment fees.");
Button button = new Button();
button.setTitle("Pay");
button.setButtonType(Button.ButtonType.URL);
button.setUrl(url);
button.setWebviewHeightRatio(Button.WebviewHeightRatio.TALL);
payload.setButtons(Collections.singletonList(button));
ArrayList<SuggestionElement> suggestionElements = Lists.newArrayList();
SuggestionElement suggestion = new SuggestionElement();
suggestion.setImageUrl("http://image-url.com");
suggestion.setTitle("Back");
suggestion.setPayload("back");
suggestionElements.add(suggestion);
payload.setSuggestionElements(suggestionElements);
message object
Message object has different attributes based on the message type. The type of the message can be one of the following:
statement
Represents a text message
Property | Description | Required |
---|---|---|
type | value=statement | Y |
text | The text to publish | Y |
buttons | An array of Button objects | N |
suggestionElements | An array of Suggestion Object | N |
Example code:
TextMessagePayload payload = new TextMessagePayload();
payload.setText(
"Thank you for booking the appointment. Please click on the button below to pay appointment fees.");
Button button = new Button();
button.setTitle("Pay");
button.setButtonType(Button.ButtonType.URL);
button.setUrl("http://www.google.com");
button.setWebviewHeightRatio(Button.WebviewHeightRatio.TALL);
payload.setButtons(Collections.singletonList(button));
carousel
Property | Description | Required |
---|---|---|
type | value=carousel | Y |
carousalElements | An array of CarousalElement objects | Y |
suggestionElements | An array of Suggestion Object | N |
Example object:
//Making a new Payload
CarousalMessagePayload carousalMessagePayload = new CarousalMessagePayload();
publishMessageAction.setSimplifiedMessage(simplifiedMessage);
//List of carousal elements. Each element is a card.
List<Element> elements = new ArrayList<Element>();
//Making one card
Element element = new Element();
element.setImageUrl("www.imageUrl.com");
element.setTitle("Title");
element.setSubtitle("Subtitle");
//Adding a button
ArrayList<Button> buttons = new ArrayList<Button>();
Button button = new Button();
button.setTitle("Button");
button.setButtonType(Button.ButtonType.URL);
buttons.add(button);
element.setButtons(buttons);
button.setUrl("http://morph.ai");
elements.add(element);
list
Property | Description | Required |
---|---|---|
type | value=list | Y |
carousalElements | An array of CarousalElement objects | Y |
suggestionElements | An array of Suggestion Object | N |
It is similar to carousal code. Use ListMessagePayload instead of CarousalMessagePayload. Rest is same.
media
Property | Description | Required |
---|---|---|
type | value=media | Y |
mediaUrl | The public URL of the media | Y |
mediaType | Either "image" or "video" | Y |
Example code:
MediaMessagePayload mediaMessagePayload = new MediaMessagePayload();
//can be image or video
mediaMessagePayload.setMediaType("image");
mediaMessagePayload.setMediaUrl("http://morph.ai/logo.png");
3) Making an API call
Example code:
package com.altin.custom_code;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import morph.base.beans.CustomCodeResponse;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.util.Map;
/**
* @author ishanjain
* @since 07/08/17
*/
public class MorphCustomCode implements RequestHandler<Map<String, Object>, CustomCodeResponse> {
@Override
public CustomCodeResponse handleRequest(Map<String, Object> input, Context context) {
Client client = ClientBuilder.newBuilder()
.withConfig(new ClientConfig().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true))
.register(JacksonFeature.class).build();
WebTarget target = client.target("http://you-api-call-url.com");
Invocation.Builder request = target.request();
Response response = request.get();
String responseStr = response.readEntity(String.class);
return null;
}
}
FAQs
How to parse JSON response
You can use Jackson to parse the JSON API response.
An example:
Lets assume your endpoint is http://you-api-call-url.com
The response that it sends is in the following format:
{
"customerId": "abc",
"customerInfo": {
"name": "Jane",
"lastName": "Doe"
}
}
There are 2 ways you can parse this JSON using Jackson.
You can either create a class & convert the JSON response to that class's object or you can convert it to JsonNode and read relevant things from the response. You can find code for both ways below.
Using JsonNode
package com.altin.custom_code;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import morph.base.beans.CustomCodeResponse;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.util.Map;
/**
* @author ishanjain
* @since 07/08/17
*/
public class MorphCustomCode implements RequestHandler<Map<String, Object>, CustomCodeResponse> {
@Override
public CustomCodeResponse handleRequest(Map<String, Object> input, Context context) {
Client client = ClientBuilder.newBuilder()
.withConfig(new ClientConfig().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true))
.register(JacksonFeature.class).build();
WebTarget target = client.target("http://you-api-call-url.com");
Invocation.Builder request = target.request();
Response response = request.get();
String responseStr = response.readEntity(String.class);
try {
JsonNode jsonNode = new ObjectMapper().readTree(responseStr);
String customerId = jsonNode.get("customerId").asText();
JsonNode customerInfo = jsonNode.get("customerInfo");
String customerName = customerInfo.get("name").asText();
String lastName = customerInfo.get("lastName").asText();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Create a new class
package com.altin.custom_code;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import morph.base.beans.CustomCodeResponse;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.jackson.JacksonFeature;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Response;
import java.util.Map;
/**
* @author ishanjain
* @since 07/08/17
*/
public class MorphCustomCode implements RequestHandler<Map<String, Object>, CustomCodeResponse> {
@Override
public CustomCodeResponse handleRequest(Map<String, Object> input, Context context) {
Client client = ClientBuilder.newBuilder()
.withConfig(new ClientConfig().property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, true))
.register(JacksonFeature.class).build();
WebTarget target = client.target("http://you-api-call-url.com");
Invocation.Builder request = target.request();
Response response = request.get();
Customer customer = response.readEntity(Customer.class);
String customerId = customer.getCustomerId();
Customer.CustomerInfo customerInfo = customer.getCustomerInfo();
return null;
}
private static class Customer {
private String customerId;
private CustomerInfo customerInfo;
public String getCustomerId() {
return customerId;
}
public void setCustomerId(String customerId) {
this.customerId = customerId;
}
public CustomerInfo getCustomerInfo() {
return customerInfo;
}
public void setCustomerInfo(CustomerInfo customerInfo) {
this.customerInfo = customerInfo;
}
private static class CustomerInfo {
private String name;
private String lastName;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
}
}