Handling of etags in batch request using SAP Cloud SDK - sap-cloud-sdk

I am trying to carry out a batch request including a create, update and a delete (all are different salesorders). As per this question here which deals with something similar, I have done a get for the items I want to update and delete before I add them to the batch request. I am using the SalesOrder.builder() to prepare the SalesOrder I want to create.
final ErpHttpDestination destination = DestinationAccessor.getDestination(DESTINATION_NAME)
.asHttp().decorate(DefaultErpHttpDestination::new);
final SalesOrderItem salesOrderItem1 = SalesOrderItem.builder().material(material)
.requestedQuantityUnit(requestedQuantityUnit).build();
final SalesOrder salesOrder1 = SalesOrder.builder().distributionChannel(distributionChannel)
.salesOrderType(salesOrderType).salesOrganization(salesOrganization)
.organizationDivision(organizationDivision).soldToParty(soldToParty)
.item(salesOrderItem1).build();
final SalesOrder orderToUpdate = new GetSingleSalesOrderCommand(orderToUpdateID, destination,
new DefaultSalesOrderService()).execute();
orderToUpdate.setSoldToParty(updateSoldToParty);
final SalesOrder orderToDelete = new GetSingleSalesOrderCommand(orderToDeleteID, destination,
new DefaultSalesOrderService()).execute();
SalesOrderServiceBatch service = new DefaultSalesOrderServiceBatch(
new DefaultSalesOrderService());
BatchResponse bRes = service.beginChangeSet().createSalesOrder(salesOrder1).updateSalesOrder(orderToUpdate)
.deleteSalesOrder(orderToDelete).endChangeSet().execute(destination);
I am then logging the BatchResponse and see I am getting a Batch Response Failure:
eTag handling not supported for http method 'POST'
I have searched for this error but can't find any resolution to it. Any ideas?
Thanks.
UPDATE: Increasing the logging to DEBUG I can see the batch request that is being sent and can see that there is an if-match header being added to the create request, which doesn't make sense as it can't match something that doesn't exist yet.
"msg":"--batch_123\r\nContent-Type: multipart/mixed;
boundary=changeset_(changeset number)\r\n\r\n--
changeset_(changeset number)\r\nContent-Type:
application/http\r\nContent-Transfer-Encoding: binary\r\n\r\nPOST
/sap/opu/odata/sap/API_SALES_ORDER_SRV/A_SalesOrder HTTP/1.1\r\nContent-
Length:
193\r\nIf-Match: W/\"datetimeoffset'2020-05-
01T11%3A51%3A16.8631720Z'\"\r\nAccept:
application/json;odata=verbose\r\nContent-Type:......
The I get the error:
Inner Error:
"msg":"batch
responseFailure(com.sap.cloud.sdk.odatav2.connectivity.ODataException:
null: <?xml version=\"1.0\" encoding=\"utf-8\"?><error
xmlns=\"http://schemas.microsoft.com/ado/2007/08/dataservices/metadata\">
<code>/IWFND/CM_MGW/537</code><message xml:lang=\"en\">eTag handling not
supported for http method 'POST'</message><innererror>...
However, what does work is if I wrap each request in its own changeset e.g.
service
.beginChangeSet().createSalesOrder(order).endChangeSet()
.beginChangeSet().updateSalesOrder(orderToUpdate).endChangeSet()
.beginChangeSet().deleteSalesOrder(orderToDelete).endChangeSet()
.execute(destination);

Edit:
This is fixed as of version 3.25.0.
Initial Answer:
This seems to be a bug. I was able to reproduce this with a different service and the behaviour is the same: The if-match header is incorrectly applied to the POST operation as well.
When debugging it seems like the request is build up correctly with the header only being present on update and delete. However, it seems that when the batch request is serialised to JSON it gets added to all requests.
So until this is fixed the workaround is isolating these operations via change sets, as you already pointed out.

Looks like eTag handling is not supported for your endpoint.
Now you can do the following to omit eTag headers:
orderToUpdate.setVersionIdentifier(null);
orderToDelete.setVersionIdentifier(null);
However I'm not sure how 'POST' fits the error description, because update uses PATCH and delete uses DELETE. The only POST that I expect would be coming from create. But we do not add headers for entity version identifiers (eTag) in OData create operation. If the same error still comes up, please try again without running createSalesOrder(salesOrder1).

Related

How to get SAP CloudSdk BatchRequest not to ignore filter parameter on Batch Query?

We are currently struggeling with Batch Query,
which seems to ignore the filter expressions on S4 side caused by a wrong URL encoding.
/sap/opu/odata/sap/ZP2M_A_CONTRACT_SEARCH_HDR_CDS/ZP2M_A_CONTRACT_SEARCH_HDR?$filter=PurchaseContractID eq %274600002020%27&$select=*&$format=json
Executing the query using FluentHelperRead.execute(HttpClient)
the returned list of entities contains the expected result with exactly one entity.
Executing the query as Batch Query the following request is logged in console:
GET ZP2M_A_CONTRACT_SEARCH_HDR?%24filter%3DPurchaseContractID+eq+%25274600002020%2527%26%24select%3D*%26%24format%3Djson HTTP/1.1
The collected list from all batch result parts contains all entities.
It seems, that the query URL is encoded in wrong way
and that S4 ignored the filter expressions when encoded in this way.
e.g. $filter is encoded to %24filter which is ignored by S4.
This seems to be a bug in BatchRequestImpl.getRequest(ODataQueryImpl) method,
where URL encoding is done a 2nd time on already encoded URL parts.
if(systemQuery.indexOf("$format=json&$count=true") != -1)
{
systemQuery = systemQuery.substring(0, systemQuery.indexOf("$format=json&$count=true") -1);
keysUrl.append("/$count");
}
systemQuery = URLEncoder.encode(systemQuery, "UTF-8"); // this code line which encodes the query 2nd time
keysUrl.append("?");
The code line systemQuery = URLEncoder.encode(systemQuery, "UTF-8"); located in
  BatchRequestImpl(1.38.0) - line 295
  BatchRequestImpl(1.42.2) - line 307
encodes the systemQuery string again (including the already encoded parts of FilterExpression as well).
When undoing the changes of this code line in debugger and replacing the scapces by %20 or '+' the Batch Query looks like that
GET ZP2M_A_CONTRACT_SEARCH_HDR?$filter=PurchaseContractID%20eq%20%274600002020%27&$select=*&$format=json HTTP/1.1
GET ZP2M_A_CONTRACT_SEARCH_HDR?$filter=PurchaseContractID+eq+%274600002020%27&$select=*&$format=json HTTP/1.1
and it returns the expected result (exactly 1 entity).
This wrong encoding appears when using these library versions:
sdk-bom: 3.16.1
connectivity: 1.38.0
This issue appears in newest SDK versions as well:
sdk-bom: 3.21.0
connectivity: 1.39.0
This issue appears with connectivity JAR in newest version too:
sdk-bom: 3.21.0
connectivity: 1.40.2
Debugging together with a ABAP/S4 colleague figures out,
that S4 only applies filter expressions, if the keyword $filter is found in request,
%24filter%3D is ignored (the cause why we get all entities running the Batch Query).
My suggestion to solve it would be
// decode query first (to decode the filter expression)
systemQuery = URLDecoder.decode(systemQuery, "UTF_8");
// encode query
systemQuery = org.apache.commons.httpclient.util.URIUtil.encodeQuery(systemQuery, "UTF_8");
My code, how I am calling the batchRequest:
FluentHelperRead<?, MyEntity, ?> queryApi = myService.getAll... // with adding some filter expression
BatchRequestBuilder batchRequestBuilder = BatchRequestBuilder.withService(MyService.DEFAULT_SERVICE_PATH);
ODataQuery query = queryApi.toQuery();
batchRequestBuilder.addQueryRequest(query);
HttpClient httpClient = HttpClientAccessor
.getHttpClient(DefaultErpHttpDestinationAccessor.get());
BatchRequest request = batchRequestBuilder.build();
BatchResult result = request.execute(httpClient);
// ... evaluate response
I think, this is a general issue in the Cloud SDK.
Would is be possible to get this fixed in next Cloud SDK release?
Can you share your code for Batch request? Do you use BatchRequestImpl directly?
The thing is SAP Cloud SDK relies on some dependencies one of which introduces the BatchRequestImpl and if it's called directly the bug is on the dependency side. I have already informed them to investigate this double encoding issue. Unfortunately, we can't directly influence how fast it is resolved and sometimes it takes longer than we'd like.
The good news, we're working on replacing this dependency with our own implementation to solve exactly this kind of problem. The batch is work in progress and should be available in Beta around the end of next month for OData V4 and hopefully around the same time for OData V2 (it's not a hard commitment and depends on other priorities).
From here we have to wait for whatever happens first:
The bug is fixed on the dependency side
Internal OData client implementation is ready together with Batch
I hope it helps and explains current solution path. If you share a bit around your deadlines and the potential impact we'll be happy to consider that.
This has been fixed within the dependency and as of version 3.25.0 the SAP Cloud SDK includes the fix.

How to make a httpsrequest 'Get' in apex and then update a record in Salesforce

Overview:
We have a third party what hosts a text value at a given endpoint. Using a 'Get' request to a url where we also pass a key and parameters returns a string values (of decimal numbers and a space).
I created some apex code, including #InvocableMethod, so I could all the apex from a flow where I pass in the URL, and then the text is returned to the flow. I then go on to update a record.
Here is the Method,
there is also a class, FR_Amount_Variables , storing the URL and String #InvocableVariable values.
public class FR_Amount_Sync {
#InvocableMethod(label='FR Amount Raised Get')
public static List<FR_Amount_Variables>getFRamount (List<FR_Amount_Variables> inputURL) {
FR_Amount_Variables amtvar = new FR_Amount_Variables();
List<FR_Amount_Variables> getFRamount = new List<FR_Amount_Variables>();
string endpoint = inputURL[0].URL;
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint(endpoint);
request.setMethod('GET');
HttpResponse response = http.send(request);
string Amounts= response.getBody();
Amounts= Amounts.replaceAll( '\\s+', '');
if(String.isEmpty(Amounts)){
Boolean isEmpty = true;
Amounts = '0.00';
}
decimal amountss = decimal.valueOf(Amounts);
amtvar.amount = amountss;
getFRamount.add(amtvar);
return getFRamount;
}
}
The image of the flow can be seen below
Update Flow
Issue:
When I run the flow in Debug mode, set the 3 input variables and run, the flow executes the apex and updates the specified record correctly.
Likewise if I preset the flow's input variables (add a default value), and the just run the flow, the apex and record updates succeed with the record being update with the correct value from the 3rd party.
The issue is when I try to automatically run the flow, either by Process Builder, or by Mass Action Scheduler, I receive system exception errors.
An Apex error occurred:
System.CalloutException: You have uncommitted work pending. Please commit or rollback before calling out
and
An Apex error occurred: System.CalloutException: Callout loop not allowed
respectively.
I was wondering if there is anyway to trigger a flow that doesn't trigger an error. Otherwise is there a way I can make a httprequest 'get' callout and then update a record with the received record.
We cannot do DML before the Callout in the same transaction.
DML can be done after the Callout.
So, the best practice is to do Callout using future method. In this way, the flow will handle the DML operations.
For example, check this link -
https://www.infallibletechie.com/2020/04/how-to-do-callout-from-flow-in.html

Can't seem to find the issue with the requestID parameter for the request header

I am trying to pull data from a REST API that uses a "similar standard to JSON RPC". The params I am passing look right according to the documentation here and here.
The error I am receiving is ...message:"Header missing request ID".... I am unsure what I am missing that would properly declare the requestID.
I have looked at the documentation provided via the API I am trying to pull data from but it's not very helpful considering it's all in PHP and cURL. I am trying to complete this task using python-requests.
getParams = {'method': 'getCustomers', 'params':{'where':'', 'limit': 2}, 'id': 'getCustomers'}
Result:
{"result":null,"error":{"code":102,"message":"Header missing request ID","data":[]},"id":null}
The return result should contain a list of All Customers and their attributes in JSON format.
Turns out there is nothing wrong with the code I am using. There is an issue with the API I am attempting to call.
In my situation, I was getting the same error back and was required to send a X-Request-ID header. I fixed it by adding the following to my headers:
headers = {
'X-Request-ID': str(uuid.uuid1()) # generate GUID based on host & time
...
Note that (for me) the GUID needed to be of a specific format (e.g. matching the Regex ^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$
taken from https://www.geeksforgeeks.org/how-to-validate-guid-globally-unique-identifier-using-regular-expression/). For example, it still gave the same error if I just sent "test".

SOAP UI how to configure a PUT request body programmatically

I'm configuring some requests programmatically in my test cases, I can set headers, custom properties, teardown scripts, etc. however I can't find how to set a standard json body for my put requests.
Is there any possibility from the restMethod class ?
So far I end up getting the method used :
restService = testRunner.testCase.testSuite.project.getInterfaceAt(0)
resource = restService.getOperationByName(resource_name)
request = resource.getRequestAt(0)
httpMethod = request.getMethod()
if (httpMethod.toString().equals("PUT"))
but then I'm stuck trying to find how to set a standard body for my PUT requests.
I try with the getRequestParts() method but it didn't give me what I expected ...
can anyone help, please
thank you
Alexandre
I’ve managed this. I had a tests of tests where I wanted to squirt the content of interest into the “bare bones” request. Idea being that I can wrap this in a data driven test. Then, for each row in my data spreadsheet I pull in the request body for my test. At first I simply pulled the request from a data source value in my spreadsheet, but this became unmanageable in my spreadsheet.
So, another tactic. In my test data sheet (data source) I stored the file name that contains the payload I want to squirt in.
In the test itself, I put in a groovy step immediately before the the step I want to push the payload into.
The groovy script uses the data source to firstly get the file name containing the payload, I then read the contents of the file.
In the step I want to push the data into, I just use a get from data, e.g. {groovyStep#result}.
If this doesn’t completely make sense, let me know and I’ll update with screenshot when I have access to SoapUi.

Set metadata in REST request to put blob in AZURE

i am able to upload file to azure blob using REST api provided by Azure.
i want to set metadata at the time i am doing request for put blob, when i am setting it into header as shown here i am unble to upload file and getting following exception org.apache.http.client.ClientProtocolException.
from the last line of the code below
HttpPut req = new HttpPut(uri);
req.setHeader("x-ms-blob-type", blobType);
req.setHeader("x-ms-date", date);
req.setHeader("x-ms-version", storageServiceVersion);
req.setHeader("x-ms-meta-Cat", user);
req.setHeader("Authorization", authorizationHeader);
HttpEntity entity = new InputStreamEntity(is,blobLength);
req.setEntity(entity);
HttpResponse response = httpClient.execute(req);
regarding the same, i have two questions.
can setting different metadata, avoid overwriting of file? See my question for the same here
if Yes for first question, how to set metadata in REST request to put blob into Azure?
please help
So a few things are going here.
Regarding the error you're getting, it is because you're not adding your metadata header when calculating authorization header. Please read Constructing the Canonicalized Headers String section here: http://msdn.microsoft.com/en-us/library/windowsazure/dd179428.aspx.
Based on this, you would need to change the following line of code (from your blog post)
String canonicalizedHeaders = "x-ms-blob-type:"+blobType+"\nx-ms-date:"+date+"\nx-ms-version:"+storageServiceVersion;
to
String canonicalizedHeaders = "x-ms-blob-type:"+blobType+"\nx-ms-date:"+date+"\nx-ms-meta-cat"+user+"\nx-ms-version:"+storageServiceVersion;
(Note: I have just made these changes in Notepad so they may not work. Please go to the link I mentioned above for correctly creating the canonicalized headers string.
can setting different metadata, avoid overwriting of file?
Not sure what you mean by this. You can update metadata of a blob by performing Set Blob Metadata operation on a blog.

Resources