In Spring Integration, I have message like following :
{
"name":"House",
"attributeIds": [1,3,5]
}
I need to enrich/transform this message using some Rest Service, which will give me the attribute values.
For example http://restservice.com/attributes?id=1,3,5 will answer me with
{"attributes": [
{"id": 1, "value":"Waterproof"},
{"id": 3, "value":"SoundProof"},
{"id": 5, "value":"Concrete"}
]}
And the final object should look like this:
{
"name":"House",
"attributes": [
{"id": 1, "value":"Waterproof"},
{"id": 3, "value":"SoundProof"},
{"id": 5, "value":"Concrete"}
]
}
How can this be achieved?
Should it be like this? https://www.youtube.com/watch?time_continue=273&v=DHPsWDgEUXg
InboundAdapter -> Enricher -> Request Channel -> Service Activator -> Enricher -> Outbound Adapter?
This is indeed a typical task for Content Enricher.
So, what you need is to deserialize that incoming JSON into a plain Map. Use a request-payload-expression="payload.attributeIds" to have that list of ids as a payload for sub-flow request.
A subscriber on the request-channel could be just simple Spring Integration HTTP Outbound Gateway to call that REST service and get an attributes message back.
This gateway can just come without an output-channel to produce its result back into a content-enricher via replyChannel header.
When this reply message comes to the content-enricher, a simple <int:property name="attributes"> can be used to populate that new option in the request Map.
Afterwards you can remove an attributeIds key from that map and serialize it back to JSON if needed.
UPDATE
Here is a sample how it could be possible with Java DSL and Spring Boot: https://github.com/artembilan/sandbox/tree/master/spring-integration-enricher
Related
I am currently using SDK version 3.39.0 and version 0004 of the API_MKT_CONTACT service definition to try to create a new Contact with an AdditionalID in Marketing Cloud with the following code:
ODataRequestUpdate contactRequest =
contactService
.updateContactOriginData(contact)
.withHeader("Sap-Cuan-RequestTimestamp", getFormattedTime(System.currentTimeMillis()))
.withHeader("Sap-Cuan-SequenceId", "UpdatePatch")
.withHeader("Sap-Cuan-SourceSystemType", "EXT")
.withHeader("Sap-Cuan-SourceSystemId", "sdk-test")
.toRequest();
var additionalId =
AdditionalID.builder()
.externalContactID(pii.getId().toString())
.originOfContact(origin)
.originOfContact_2("EMAIL") //ContactAdditionalOrigin
.externalContactID_2(pii.getEmail()) //ContactAdditionalID
.build();
var additionalIdRequest = contactService
.updateAdditionalIDs(additionalId)
.replacingEntity()
.withHeader("Sap-Cuan-RequestTimestamp", getFormattedTime(System.currentTimeMillis()))
.withHeader("Sap-Cuan-SourceSystemType", "EXT")
.withHeader("Sap-Cuan-SourceSystemId", "sdk-test")
.toRequest();
// use low level API as a work around for https://github.com/SAP/cloud-sdk/issues/156
ODataRequestBatch requestBatch = new ODataRequestBatch(ContactService.DEFAULT_SERVICE_PATH, ODataProtocol.V2);
requestBatch
.beginChangeset()
.addUpdate(contactRequest)
.addUpdate(additionalIdRequest)
.endChangeset();
HttpClient httpClient = HttpClientAccessor.getHttpClient(contactsDestination);
ODataRequestResultMultipartGeneric batchResult = requestBatch.execute(httpClient);
batchResult.getResult(additionalIdRequest);
This results in the following error:
{
"error": {
"code": "SY/530",
"message": {
"lang": "en",
"value": "Inline component is not defined or not allowed (HTTP PUT)"
},
"innererror": {
"application": {
"component_id": "CEC-MKT-DM-IC",
"service_namespace": "/SAP/",
"service_id": "API_MKT_CONTACT_SRV",
"service_version": "0004"
},
"transactionid": "3B63A2A6CC9205E0E00604E1D31F1CDF",
"timestamp": "20210315142401.8432680",
"Error_Resolution": {
"SAP_Transaction": "For backend administrators: use ADT feed reader \"SAP Gateway Error Log\" or run transaction /IWFND/ERROR_LOG on SAP Gateway hub system and search for entries with the timestamp above for more details",
"SAP_Note": "See SAP Note 1797736 for error analysis (https://service.sap.com/sap/support/notes/1797736)",
"Batch_SAP_Note": "See SAP Note 1869434 for details about working with $batch (https://service.sap.com/sap/support/notes/1869434)"
},
"errordetails": []
}
}
}
I am using this documentation as a guide for building my requests (under the section "Create Contacts with Additional IDs"). When I run the example code in Postman it works as expected. Note that the payload for the AdditionalIDs is an empty JSON object.
So I enabled HTTP wire logs and noticed that the SDK seems to be including the following payload:
PUT AdditionalIDs(ContactAdditionalOrigin='EMAIL',ContactAdditionalID='wade.watts#theoasis.com',ContactID='ae46e174-52a3-4de6-8caa-57213151b295',ContactOrigin='<CONTACT_ORIGIN>') HTTP/1.1
Sap-Cuan-SourceSystemId: sdk-test
Accept: application/json
Sap-Cuan-SourceSystemType: EXT
Content-Type: application/json
Sap-Cuan-RequestTimestamp: '2021-03-15T14:24:00.828'
{"ContactOrigin":"<CONTACT_ORIGIN>","ContactID":"ae46e174-52a3-4de6-8caa-57213151b295","ContactAdditionalOrigin":"EMAIL","ContactAdditionalID":"wade.watts#theoasis.com","ContactAdditionalIdUUID":null,"ContactUUID":null,"ContactAddlIDIsInvalid":null,"MarketingAreas":[]}
Unfortunately, I can't seem to find a way to omit the payload/inline component while using the SDK so that it matches the example code. Is this an issue with the SDK or am I doing something wrong? Any help would be much appreciated!
Cheers!
UPDATE
Applying the suggested workaround from #matkuhr I changed my additionalIdRequest above to this and it worked:
ODataEntityKey key = new ODataEntityKey(ODataProtocol.V2)
.addKeyProperty(AdditionalID.EXTERNAL_CONTACT_ID.getFieldName(), mcContact.getContactId())
.addKeyProperty(AdditionalID.ORIGIN_OF_CONTACT.getFieldName(), origin)
.addKeyProperty(AdditionalID.ORIGIN_OF_CONTACT_2.getFieldName(), "EMAIL")
.addKeyProperty(AdditionalID.EXTERNAL_CONTACT_I_D_2.getFieldName(), mcContact.getEmailAddress());
var request = new ODataRequestUpdate(
ContactService.DEFAULT_SERVICE_PATH,
"AdditionalIDs",
key,
"{}",
UpdateStrategy.REPLACE_WITH_PUT,
null,
ODataProtocol.V2);
request.addHeader("Sap-Cuan-RequestTimestamp", getFormattedTime(System.currentTimeMillis()));
request.addHeader("Sap-Cuan-SourceSystemType", "EXT");
request.addHeader("Sap-Cuan-SourceSystemId", "sdk-test);
It's not you doing something wrong, it's also not the SDK, it's the service. The service seems to substantially deviate from the OData V2 conventions as well as basic HTTP conventions.
You can work around this by leveraging the low-level APIs of the SDK even more. Create the update request fully manually with the payload the service requires, e.g.:
ODataEntityKey key = new ODataEntityKey(ODataProtocol.V2)
.addKeyProperty(Contact.XYZ.getFieldName(), contact.getXyz())
request = new ODataRequestUpdate(
contactService.getServicePath(),
contact.getEntityCollection(),
key,
"{ }", // this will be the payload
UpdateStrategy.REPLACE_WITH_PUT,
null,
ODataProtocol.V2);
request.addHeader("key", "val");
// add more headers & parameters if needed and execute
Is there an Outlook REST API that gives me all the urls extracted from the body of the message? Some thing like what EWS's EntityExtractionResult does?
No there isn't. However, you can retrieve extended properties, so you should be able to request the PidNameExtractedUrls property.
If you dig through those open specs, you should find these details on PidNameExtractedUrls:
Property set: PSETID_XmlExtractedEntities {23239608-685D-4732-9C55-4C95CB4E8E33}
Property name: XmlExtractedUrls
So that would mean I could make the following request (assuming you're using the Outlook endpoint, not Graph):
GET https://outlook.office.com/api/v2.0/me/messages?$expand=SingleValueExtendedProperties($filter=PropertyId eq 'String {23239608-685D-4732-9C55-4C95CB4E8E33} Name XmlExtractedUrls')
For Graph, you would replace PropertyId with id.
That will include something like this in the message entities that have this property set:
"SingleValueExtendedProperties": [
{
"PropertyId": "String {23239608-685d-4732-9c55-4c95cb4e8e33} Name XmlExtractedUrls",
"Value": "<?xml version=\"1.0\" encoding=\"utf-16\"?><UrlSet><Version>15.0.0.0</Version><Urls><Url StartIndex=\"0\"><UrlString>https://www.google.com</UrlString></Url><Url StartIndex=\"23\"><UrlString>https://developer.microsoft.com/outlook</UrlString></Url></Urls></UrlSet>"
}
]
I have an Azure Logic App with a queue trigger. The queue message is JSON. When I send "Message text" to an Azure function, I get
UnsupportedMediaType
{
"Message": "The WebHook request must contain an entity body formatted as JSON."
}
I'd assumed this would work directly.I tried setting request body to
#{json(<Message text>)}
where is the select dynamic content item, but I get red message "Enter a valid json".
What's the trick to making this connection? Do I have to pass in and then parse out "Message text" in my function? Again, I assumed it would do that automagically.
The #{} syntax indicates string interpolation. This means that your expression #{json(<Message text>)} de-serializes the message text to json, and then serializes it again.
Hence the expression that you want to use is
#json(<Message text>)
For future readers.
I was passing some (what seems to be to be valid) json to my webhook.
And kept getting the
"Message": "The WebHook request must contain an entity body formatted as JSON."
error.
:(
Finally, I found a json "expression" that did its voodoo and got rid of the error. I argument from the json-expression was my previous action's output, which was valid json. It apparently needs just a little help!
The raw (non designer) code was:
"GenericWebHookCsharpOne": {
"type": "Function",
"inputs": {
"body": "#json( body('MyPreviousAppLogicActionWhichIsAnAzureFunction'))",
"method": "POST",
"function": {
"id": "/xxxxxxxxxxxxxxxxxxxxxxxx
}
I am currently using the following mapping template to pass data sent to an AWS API Gateway endpoint to AWS Kinesis Firehose stream:
{
"DeliveryStreamName": "[STREAMNAME]",
"Record": {
"Data": "$util.base64Encode($input.body)"
}
}
What I would like to do is: adding information to the $input.body that is being encoded like the $context.identity.sourceIp of the client making the request.
How can I go about this when the output being passed to Kinesis Firehose needs to be Base64-encoded? Ideally I would like my data that is being posted to Kinesis Firehose look like this:
{
"x": 1,
"y": 2,
"z": 3,
..., // all the properties from the JSON-request by the client
"clientIp": "x.x.x.x" // property added by API-Gateway into client's object
}
After a little bit more digging I managed to get the following to work:
#set($inputRoot = $input.path('$'))
#set($data = "{
#foreach($key in $inputRoot.keySet())
""$key"": $input.json($key),
#end
""clientIP"": ""$context.identity.sourceIp"",
}")
{
"DeliveryStreamName": "[STREAMNAME]",
"Record": {
"Data": "$util.base64Encode($data)"
}
}
I was not aware that you could do a #foreach inside a #set. Note that you also have to use double-quotes to get this right.
If I add parameters to the url in the Objective-C code, is it possible to read it from the client?
Example:
- (NSURL *)serverURL {
return [NSURL URLWithString:#"http://rap.eclipsesource.com/demo?parametername=value"];
}
In the Client-JavaCode I can get the value of the parameter like this:
String parameter = RWT.getRequest().getParameter("parametername");
If I access the "app" with the browser I get a value for the parameter. If I access the app with the TabrisClient the value is null.
Is there a way to get the value also in the TabrisClient?
Update:
The server does not directly extract the query string from the request URL, but from the first JSON message received from the client. The web client provides the parameter queryString in the head part of the first UI request. Example:
{
"head": {
"queryString": "foo=23&bar=42",
"requestCounter": ...
},
"operations": [
...
]
}
You would have to fake this behavior in your Tabris client. I'd suggest that you file an issue against Tabris to provide API to set startup parameters.
Original answer:
If you're going to hard-code the parameter in the tabris client anyway, you could set the variable based on the connected client:
parameter = (RWT.getClient() instanceof WebClient)
? RWT.getRequest.getParameter("parametername")
: "tabris-value";
BTW, access to request parameters is going to change in RAP 3.0. Instead of RWT.getRequest().getParameter(), a ClientService will provide the parameters.