How to get Task Token in AWS Lambda Function - java

I have an AWS Step Function with the handlers implemented in Java.
My step function definition:
definition:
Comment: Steps for issuing a card
StartAt: RecipientFraudChecks
States:
RecipientFraudChecks:
Type: Task
Next: SaveTaskToken
Resource: arn:aws:lambda:eu-west-1:099720403855:RecipientFraudChecks
SaveTaskToken:
Type: Task
Resource: arn:aws:lambda:eu-west-1:12345678:function:SaveTaskToken
End: true
I have a Java project and all the Lambda Function handlers are defined there:
public class SaveTaskToken implements RequestHandler<Map<String,String>, String> {
....
#Override
public String handleRequest(Map<String, String> input, final Context context) {
// do the fraud checks
System.out.println("the context is: " + gson.toJson(context));
System.out.println("input: " + gson.toJson(input));
}
I'm running the step function locally using AWS SAM, and triggering according to this: https://docs.aws.amazon.com/step-functions/latest/dg/sfn-local-lambda.html#install-sam
Within context I would expect to see the Task Token, but I do not. Logs show:
the context is: {
"memoryLimit": 512,
"awsRequestId": "5065a9aa-1a4a-46fe-9b58-7dc2194f92b7",
"logGroupName": "aws/lambda/SaveTaskToken",
"logStreamName": "$LATEST",
"functionName": "SaveTaskToken",
"functionVersion": "$LATEST",
"invokedFunctionArn": "",
"cognitoIdentity": {
"identityId": "",
"poolId": ""
},
"logger": {}
}
In fact its nothing like the global Context I should expect in the docs.
What am I doing wrong? How can I get the Task Token?
EDIT
I added Parameters property to 'SaveTaskToken' and changed resource to arn:aws:states:::lambda:invoke.waitForTaskToken and know I can get the Task Token:
definition:
Comment: Steps for issuing a card
StartAt: RecipientFraudChecks
States:
RecipientFraudChecks:
Type: Task
Next: SaveTaskToken
Resource: arn:aws:lambda:eu-west-1:099720403855:RecipientFraudChecks
SaveTaskToken:
Type: Task
Resource: arn:aws:states:::lambda:invoke.waitForTaskToken
Parameters:
FunctionName: arn:aws:lambda:eu-west-1:12345678:function:SaveTaskToken
Payload:
taskToken
End: true
In the logs I can see:
the input is: {
"taskToken": "5286"
}
It has caused another problem - it overrides the input to the state machine. Im passing in the input:
{"giftCode": "xxx"}
In the first Lambda function, RecipientFraudChecks, I can get the input. However, in the second, since adding the Parameters property, I now can no longer get the input to the state machine, only the task token...
EDIT
Have implemented the answer here: https://stackoverflow.com/a/66995869/1246159
{
"Comment": "Steps for issuing a card",
"StartAt": "RecipientFraudChecks",
"States": {
"RecipientFraudChecks": {
"Type": "Task",
"Next": "PauseCardIfNecessary",
"ResultPath": "$.firstLambdaOutput",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:RecipientFraudChecks"
},
"PauseCardIfNecessary": {
"Type": "Task",
"Next": "GetOrCreateClient",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"Parameters": {
"FunctionName": "arn:aws:lambda:us-east-1:123456789012:function:PauseCardIfNecessary",
"Payload": {
"token.$": "$$.Task.Token",
"otherInput.$": "$"
}
}
},
"GetOrCreateClient": {
"Type": "Task",
"Next": "GetOrAccountClient",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:GetOrCreateClient"
},
"GetOrAccountClient": {
"Type": "Task",
"Resource": "arn:aws:lambda:us-east-1:123456789012:function:GetOrAccountClient",
"End": true
}
}
}
But I get another error, here are the logs:
arn: aws: states: eu-west-1: 123456789012: execution: HelloWorld5: cardIssue: {
"Type": "TaskStateExited",
"PreviousEventId": 5,
"StateExitedEventDetails": {
"Name": "RecipientFraudChecks",
"Output": "{\"inputToStep\":\"xxxx\",\"firstLambdaOutput\":\"output of recipient lambda\"}"
}
} arn: aws: states: eu-west-1: 123456789012: execution: HelloWorld5: cardIssue: {
"Type": "TaskStateEntered",
"PreviousEventId": 6,
"StateEnteredEventDetails": {
"Name": "PauseCardIfNecessary",
"Input": "{\"inputToStep\":\"xxxx\",\"firstLambdaOutput\":\"output of recipient lambda\"}"
}
} arn: aws: states: eu-west-1: 123456789012: execution: HelloWorld5: cardIssue: {
"Type": "ExecutionFailed",
"PreviousEventId": 7,
"ExecutionFailedEventDetails": {
"Error": "States.Runtime",
"Cause": "An error occurred while executing the state 'PauseCardIfNecessary' (entered at the event id #7). The value for the field 'token.$' must be a valid JSONPath expression"
}
}

Task token is not automatically passed in lambda context, it needs to be passed as input.
The context here is not context of lambda function, but the context of the task and to grab details from context, we can use $$. within step function definition,
Ex:
To get Task Token $$.Task.Token
To get start time $$.Execution.StartTime
Example Step function:
First Task executes a Lambda with step function input.
Appends the output of first lambda with step function input.
Second Task executes another lambda with resource waitForTaskToken, right here, we grab the task token and pass it along with output of previous step as input.
Step functions waits until it gets a SendTaskSuccess or SendTaskFailure
{
"StartAt": "fist-lambda-invoke-sync",
"States": {
"fist-lambda-invoke-sync": {
"Next": "second-lambda-invoke-task-token",
"Type": "Task",
"ResultPath": "$.firstLambdaOutput",
"Resource": "arn:aws:lambda:us-east-1:11112223333:function:myfirstlambda"
},
"second-lambda-invoke-task-token": {
"End": true,
"Type": "Task",
"Resource": "arn:aws:states:::lambda:invoke.waitForTaskToken",
"Parameters": {
"FunctionName": "arn:aws:lambda:us-east-1:11112223333:function:mysecondlambda",
"Payload": {
"token.$": "$$.Task.Token",
"otherInput.$": "$"
}
}
}
}
}
Input to Step function:
{
"inputToStep": "myValue"
}
Output of First Lambda: assuming it is a string value "10". It will be appended to input Json because of "ResultPath": "$.firstLambdaOutput"
{
"inputToStep": "myValue",
"firstLambdaOutput": "10"
}
Input to Second Lambda: Will receive below json as input with task token appended, because of "token.$": "$$.Task.Token" and "otherInput.$": "$"
{ otherInput: { inputToStep: 'myValue', firstLambdaOutput: '10' },
token: 'This is where Task Token generated by step function will be sent' }

Regarding the input, there seems to be an easy solution in the docs [1]:
Instead of hard-coding the event payload in the state machine definition, you can use the input from the state machine execution. The following example uses the input specified when you run the state machine as the event payload:
"Payload.$": "$"
There is another example in the docs [2]:
{
"StartAt":"GetManualReview",
"States":{
"GetManualReview":{
"Type":"Task",
"Resource":"arn:aws:states:::lambda:invoke.waitForTaskToken",
"Parameters":{
"FunctionName":"get-model-review-decision",
"Payload":{
"model.$":"$.new_model",
"token.$":"$$.Task.Token"
},
"Qualifier":"prod-v1"
},
"End":true
}
}
}
You can possibly change "model.$":"$.new_model" to something like "input.$":"$" and get the desired Lambda payload.
Which translates to the following in YAML:
Parameters:
Payload:
input.$: "$"
token.$: "$$.Task.Token"
[1] https://docs.aws.amazon.com/lambda/latest/dg/services-stepfunctions.html#services-stepfunctions-setup
[2] https://docs.amazonaws.cn/en_us/step-functions/latest/dg/connect-lambda.html

Related

MongoDB Panache Updating nested objects within Document

I have a model which looks like this:
{
"projectName": "MyFirstProject",
"projectId": "1234",
"testCaseList": [
{
"testCaseName": "TestCase1",
"steps": [
{
"Action": "Click on this",
"Result": "pass"
},
{
"Action": "Click on that",
"Result": "pass"
}
]
},
{
"testCaseName": "TestCase2",
"steps": [
{
"Action": "Click on him",
"Result": "pass"
},
{
"Action": "Click on her",
"Result": "pass"
}
]
}
]
}
However, as this is a nested object, I am having difficulties updating it using the method:
default PanacheUpdate update(String update, Object... params)
I am using Repository Pattern and below is my code snippet:
List<TestCase> newTestCaseList = ...;
update("testCaseList", newTestCaseList).where("projectId=?1",projectId);
which actually throws the following error:
org.bson.json.JsonParseException: JSON reader was expecting ':' but found ','.
at org.bson.json.JsonReader.readBsonType(JsonReader.java:149)
at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:82)
at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:41)
at org.bson.codecs.BsonDocumentCodec.readValue(BsonDocumentCodec.java:101)
at org.bson.codecs.BsonDocumentCodec.decode(BsonDocumentCodec.java:84)
at org.bson.BsonDocument.parse(BsonDocument.java:63)
at io.quarkus.mongodb.panache.runtime.MongoOperations.executeUpdate(MongoOperations.java:634)
at io.quarkus.mongodb.panache.runtime.MongoOperations.update(MongoOperations.java:629)
My Current Approach
What currently works for me is to use default void update(Entity entity) instead when updating nested objects.
This however presents a few considerations:
Extra code is required to fetch the entire document, parse through, and update the required fields
Since update(Entity entity) works on a document level, it will also update unchanged parts of the document, which isn't ideal.
I guess the encountered error states nothing but a limitation of Panache for mongoDB for the moment through the standard offered PanacheQL.
The issue should be worked-around using native mongoDB Java API that can be accessed through the PanacheMongoEntityBase#mongoCollection:
mongoCollection().updateOne(
eq("projectId", projectId),
new Document("$set", new Document("testCaseList", newTestCaseList))
);

Adding fragments in GraphQL query from Java

After long hours session of finding any documentation, I am forced to ask the community.
Currently the query I am making from Java code(also tried the same with Postman) to query GraphQL:
Request:
{"query":"{findPublisherByLicenseNumber(licenseNumber : \"UAE_DXB_CMH-971\") {name licenseNumber}}"}
Response:
{
"data": {
"findPublisherByLicenseNumber": {
"name": "Cantaloupe Media House",
"licenseNumber": "UAE_DXB_CMH-971"
}
}
}
This is the query which I have made in my backend to query endpoint: http://mg-api.com/mg-graphQL
As of now I have mentioned two fields only but actual Schema has many.
type Publisher {
ID: ID!
name: String!
licenseNumber: String!
address: String
contactNumber: String
mobileNumber: String
areaCode: Int!
...
... #few more fields
books: [Book]
}
extend type Query {
findAllPublishers: [Publisher]!
findPublisherByLicenseNumber(licenseNumber: String!) : Publisher!
}
The fragment that I want to add in Query:
fragment publisherInfo on Publisher {
ID
name
licenseNumber
address
contactNumber
areaCode
}
Tried like this
{"query":"{findPublisherByLicenseNumber(licenseNumber : \"UAE_DXB_CMH-971\") {...publisherInfo}}", "fragment" : "fragment publisherInfo on Publisher {ID name licenseNumber address contactNumber areaCode }"}
but got ValidationError with message : Undefined fragment publisherInfo.
{
"data": null,
"errors": [
{
"message": "Validation error of type UndefinedFragment: Undefined fragment pubInfo # 'findPublisherByLicenseNumber'",
"locations": [
{
"line": 1,
"column": 67,
"sourceName": null
}
],
"description": "Undefined fragment pubInfo",
"validationErrorType": "UndefinedFragment",
"queryPath": [
"findPublisherByLicenseNumber"
],
"errorType": "ValidationError",
"path": null,
"extensions": null
}
]
}
When I try to achieve the same with GraphiQL UI then it works.
I have not started to look to call this from UI Frameworks where I need to use graphql.js in Vanilla JS/Angular/React/Vue etc.
As of now I am looking to utilize this from my backend code logic as there is a middleware between UI and my graphql service.
The only thing I found was left unanswered and if answered then totally in vague manner : S.O Question
Any help is appreciated.
SOLVED
Need to pass fragment before method;
{"query":"fragment pubInfo on Publisher {name licenseNumber contactNumber} {findPublisherByLicenseNumber(licenseNumber :\"UAE_DXB_CMH-971\") {...pubInfo}}"}
SOLVED
Need to pass fragment before/after method call body {} in the same Query;
{"query":"fragment pubInfo on Publisher {name licenseNumber contactNumber} {findPublisherByLicenseNumber(licenseNumber :\"UAE_DXB_CMH-971\") {...pubInfo}}"}

Use Special Character "$" in Kotlin/Java Variable Name

Expected
The ability to use the special character "$" in the naming of a Kotlin or Java variable name. The API for EventRegistry (Example 4) requires the naming of the sub query as "$query". Is there a Kotlin or Java workaround in order to use "$" in a variable name?
Documentation
Example 4 - Request Body
{
"action": "getArticles",
"query": {
"$query": {
"$and": [
{
"dateStart":"2017-04-22",
"dateEnd":"2017-04-22"
},
{
"$or":[
{
"conceptUri":{
"$or": ["http://en.wikipedia.org/wiki/Barack_Obama"]
}
},
{
"keyword":"Trump"
}
]
},
{
"categoryUri":"dmoz/Business"
}
]
}
},
"articlesPage": 1,
"articlesCount": 100,
"articlesSortBy": "socialScore",
"articlesSortByAsc": false,
"articlesArticleBodyLen": -1,
"includeArticleSocialScore": true,
"resultType": "articles",
"apiKey": "YOUR_API_KEY"
}
Result
Lint error is thrown when attempting the following:
data class Query(val $query: SubQuery)
For Kotlin
surround the name with backticks (ascii code 96):
`$query`

How can we set value of context variable of watson assistant using java?

Is it possible to set the value of context variable from java. I have modified the node json and added a action in that action in the result property I want to set data from my local database. So I am getting the action in java code and trying to set the value of result property of action object, below is the code which i am trying but it is not working. can some one suggest a better approach for this.
if(response.getActions()!=null) {
System.out.println("actions : "+response.getActions());
for (int i = 0; i < response.getActions().size(); i++) {
System.out.println(" i : "+response.getActions().get(i).getName());
if(response.getActions().get(i).getName()=="Apply_For_Loan") {
response.getActions().get(i).setResultVariable("123");
}
}
}
For setting value of result_variable in to the context below is my code.
if(response.getActions().get(i).getName().equals("Apply_For_Loan")) {
System.out.println("in action");
Assistant service = new Assistant("2018-12-12");
service.setUsernameAndPassword(userId, password);
service.setEndPoint(endPoint);
String result=response.getActions().get(i).getResultVariable();
Context context = response.getContext();
context.put(result, "123");
MessageOptions msg=new MessageOptions.Builder(watsonId)
.input(new InputData.Builder("Apply For Loan").build())
.context(context)
.build();
response=service.message(msg).execute();
System.out.println("msg : "+response);
}
Below is the response which i am getting after re-executing the assistant call.
{
"output": {
"generic": [
{
"response_type": "text",
"text": "Hello RSR, you loan application of 5420 is created. Please note the Loan Number for future use "
}
],
"text": [
"Hello RSR, you loan application of 5420 is created. Please note the Loan Number for future use "
],
"nodes_visited": [
"node_1_1544613102320",
"node_1_1544613102320"
],
"log_messages": []
},
"input": {
"text": "Apply For Loan"
},
"intents": [
{
"intent": "ApplyForLoan",
"confidence": 1.0
}
],
"entities": [],
"context": {
"number": 9.971070056E9,
"$Loan_Number": "123",
"system": {
"initialized": true,
"dialog_stack": [
{
"dialog_node": "node_1_1544613102320"
}
],
"dialog_turn_counter": 3.0,
"dialog_request_counter": 3.0,
"_node_output_map": {
"node_1_1544613102320": {
"0": [
0.0
]
}
},
"branch_exited": true,
"branch_exited_reason": "completed"
},
"Mail_Id": "Email_Id",
"conversation_id": "b59c7a02-2cc6-4149-ae29-602796ab22e1",
"person": "RSR",
"rupees": 5420.0
},
"actions": [
{
"name": "Apply_For_Loan",
"type": "client",
"parameters": {
"pername": "RSR",
"loanamount": 5420.0
},
"result_variable": "$Loan_Number"
}
]
}
In above response $Loan_Number is the result variable which i have updated from java code and the same result_variable i am using in the output text to return the $Loan_Number, but in output text it is still coming blank and in actions also it is still coming blank?
The simple answer to your question is no, you can't set anything in the JSON Model using setResultVariable. It should work like this:
1) Define an action on your Dialognode
The result_variable defines the <result_variable_name>, e.g. where the result of that action should be stored in the conversation context. If you execute some server side action, you read the result from the specified context location. In this example, the location is context.result_of_action, the context might be omitted. More details are here.
2) Process the action in Java Client
DialogNodeAction action = response.getActions().get(0);
final String result_variable = action.getResultVariable();
The result_variable is like a key. You obtain the value from your DB and add it to the context:
Context context = response.getContext();
context.put(result_variable, "value from DB");
new MessageOptions.Builder("Your ID")
.input(new InputData.Builder("Your response").build())
.context(context) // setting context
.build();
Finally the Watson Assistant Dialog (or some other App) can get the result from the Client:
"context": {
"result_of_action": "value from DB",
The Objects you read using the Java API are being parsed from JSON using Gson and need to be re-parsed back to JSON when construction a new API Call.
Edit after question update
In your example:
"actions": [
{
"name": "Apply_For_Loan",
"type": "client",
"parameters": {
"pername": "RSR",
"loanamount": 5420.0
},
"result_variable": "$Loan_Number"
}
the key result_variable tells other modules in your app where to find the result of your action. So $Loan_Number should not be the value but the key to the value in the session context (a kind of method contract).
Let's say you set the value to context.my_result, then your Watson Assistant should be able to access the "123" from DB in the next dialog node under "context.my_result".

ElasticSearch mapping for dynamic keys for indexing a map

I have a sample json which I want to index into elasticsearch.
Sample Json Indexed:
put test/names/1
{
"1" : {
"name":"abc"
},
"2" : {
"name":"def"
},
"3" : {
"name":"xyz"
}
}
where ,
index name : test,
type name : names,
id :1
Now the default mapping generated by elasticsearch is :
{
"test": {
"mappings": {
"names": {
"properties": {
"1": {
"properties": {
"name": {
"type": "string"
}
}
},
"2": {
"properties": {
"name": {
"type": "string"
}
}
},
"3": {
"properties": {
"name": {
"type": "string"
}
}
},
"metadataFieldDefinition": {
"properties": {
"name": {
"type": "string"
}
}
}
}
}
}
}
}
If the map size increases from 3 ( currently) to suppose thousand or million, then ElasticSearch will create a mapping for each which may cause a performance issue as the mapping collection will be huge .
I tried creating a mapping by setting :
"dynamic":false,
"type":object
but it was overriden by ES. since it didnt match the indexed data.
Please let me know how can I define a mapping so that ES. doesnot creates one like the above .
I think there might be a little confusion here in terms of how we index documents.
put test/names/1
{...
document
...}
This says: the following document belongs to index test and is of type name with id 1. The entire document is treated as type name. Using the PUT API as you currently are, you cannot index multiple documents at once. ES immediately interprets 1, 2, and 3 as a properties of type object, each containing a property name of type string.
Effectively, ES thinks you are trying to index ONE document, instead of three
To get many documents into index test with a type of name, you could do this, using the CURL syntax:
curl -XPUT"http://your-es-server:9200/test/names/1" -d'
{
"name": "abc"
}'
curl -XPUT"http://your-es-server:9200/test/names/2" -d'
{
"name": "ghi"
}'
curl -XPUT"http://your-es-server:9200/test/names/3" -d'
{
"name": "xyz"
}'
This will specify the document ID in the endpoint you are index to. Your mapping will then look like this:
"test": {
"mappings": {
"names": {
"properties": {
"name": {
"type": "string"
}
}
}
}
}
Final Word: Split your indexing up into discrete operations, or check out the Bulk API to see the syntax on how to POST multiple operations in a single request.

Categories

Resources