Surendra Gupta
Surendra Gupta

Reputation: 11

S4Hana(ERP) backend does not return localized error message due to additional ‘sap-language’ header is being added by the Cloud SDK

Note: Application is built in CAP Java Stack along with DWC framework. Technical user is configurated in destination service for making an API call.

Flow :

httpHeaders.put(DefaultErpHttpDestination.LOCALE_HEADER_NAME, dwcHeaderContainer.getHeader(HttpHeaders.ACCEPT_LANGUAGE)); 

ModificationResponse<PurchaseRequisition> s4ReqResponse = 
s4opRequistionService.createPurchaseRequisition(s4opRequistion).withHeaders(httpHeaders).executeRequest(destinationProvider.getDestination());          

However, Observed the underlying cloud SDK layer which 1st makes the call to destination service and retrieves the provided headers and then gets system current locale based on some constraint and adds a header again for ‘sap-language’ with the default system locale as 'en' which leads to problem in our case.

Calling stack trace :

Thread [SimpleAsyncTaskExecutor-60] (Suspended) 
CdsRequestHeaderFacade.tryGetRequestHeaders() line: 28  
RequestHeaderAccessor.tryGetHeaderContainer() line: 89  
DefaultLocaleFacade.getLocalesByHeaders() line: 48  
DefaultLocaleFacade.getCurrentLocales() line: 36    
DefaultLocaleFacade(LocaleFacade).getCurrentLocale() line: 27   
LocaleAccessor.getCurrentLocale() line: 53  
371621826.get() line: not available 
Option$None<T>(Option<T>).getOrElse(Supplier<? extends T>) line: 336    
DefaultErpHttpDestination(ErpHttpDestinationProperties).getLocale() line: 51    
DefaultErpHttpDestination.getHeadersToAdd() line: 188   
DefaultErpHttpDestination.getHeaders(URI) line: 169 
HttpClientWrapper.wrapRequest(HttpUriRequest) line: 98  
HttpClientWrapper.execute(HttpUriRequest) line: 116 
HttpClientWrapper.execute(HttpUriRequest) line: 35  
DefaultCsrfTokenRetriever.retrieveCsrfTokenResponseHeader(HttpClient, String, Map<String,String>) line: 91  
DefaultCsrfTokenRetriever.retrieveCsrfToken(HttpClient, String, Map<String,String>) line: 54    
ODataRequestCreate(ODataRequestGeneric).lambda$tryGetCsrfToken$5ab307ff$1(CsrfTokenRetriever, HttpClient) line: 266 
659039215.apply() line: not available   
Try<T>.of(CheckedFunction0<? extends T>) line: 75   
ODataRequestCreate(ODataRequestGeneric).tryGetCsrfToken(HttpClient, CsrfTokenRetriever) line: 266   
ODataRequestCreate(ODataRequestGeneric).tryExecuteWithCsrfToken(HttpClient, Supplier<HttpResponse>) line: 239   
ODataRequestCreate.execute(HttpClient) line: 93 
PurchaseRequisitionCreateFluentHelper(FluentHelperCreate<FluentHelperT,EntityT>).executeRequest(HttpDestinationProperties) line: 246    
xxxxxx.copyPR(Map<String,Object>) line: 176 
xxxxxx.s4AdapterResponseToxxxxxx(xxxxxx) line: 69   
xxxxxx.onApplicationEvent(xxxxxx) line: 58  
xxxxxxSpringListener.onApplicationEvent(ApplicationEvent) line: 1   
SimpleApplicationEventMulticaster.doInvokeListener(ApplicationListener, ApplicationEvent)   
SimpleApplicationEventMulticaster.invokeListener(ApplicationListener<?>, ApplicationEvent)
SimpleApplicationEventMulticaster.lambda$multicastEvent$0(ApplicationListener, ApplicationEvent)    
1824178544.run() line: not available    
DwcContextTaskDecorator.lambda$decorate$0(Map, Runnable) line: 33   
1126784716.run() line: not available    
Thread.run() line: 829  

Sample Http Out Going request and response looks like :

http-outgoing-13 >> "POST /sap/opu/odata/sap/API_PURCHASEREQ_PROCESS_SRV/A_PurchaseRequisitionHeader HTTP/1.1[\r][\n]"
http-outgoing-13 >> "sap-language: de[\r][\n]"
http-outgoing-13 >> "Accept: application/json[\r][\n]"
http-outgoing-13 >> "RequestID: xxxxx[\r][\n]"
http-outgoing-13 >> "RepeatabilityCreation: 2022-09-19T08:50:00.889315900Z[\r][\n]"
http-outgoing-13 >> "X-CorrelationID: [\r][\n]"
http-outgoing-13 >> "x-csrf-token: xxxxx==[\r][\n]"
http-outgoing-13 >> "Content-Type: application/json[\r][\n]"
http-outgoing-13 >> "Authorization: Basic XXXX[\r][\n]"
http-outgoing-13 >> "sap-language: en[\r][\n]"
http-outgoing-13 >> "Content-Length: 1383[\r][\n]"
http-outgoing-13 >> "Host: XXXX[\r][\n]"
http-outgoing-13 >> "Connection: Keep-Alive[\r][\n]"
http-outgoing-13 >> "User-Agent: Apache-HttpClient/4.5.13 (Java/11.0.16)[\r][\n]"
http-outgoing-13 >> "Cookie: XXX%3d; sap-usercontext=sap-language=en&sap-client=xxx[\r][\n]"
http-outgoing-13 >> "Accept-Encoding: gzip,deflate[\r][\n]"
http-outgoing-13 >> "[\r][\n]"
http-outgoing-13 >> "body_xxx"
http-outgoing-13 << "HTTP/1.1 400 Bad Request[\r][\n]"
http-outgoing-13 << {"error":{"code":"06/101","message":{"lang":"en","value":"No master record exists for supplier EPRINT"}"}

If you notice, there are two 'sap-language' header goes to backend as mentioned above and S4 backend layer considers the last header attribute and returns the error message always in English. Our expectation is error messages should come based on user’s locale…

Following queries :

Is there any way by which application layer can instruct to Cloud SDK like Not to append again 'sap-language' header and give preference to what is being passed by the custom header as ‘sap-language’ As ‘de’ in our case?

Is it the bug from Cloud SDK?

Any recommendation or inputs to address this issue..

Upvotes: 1

Views: 188

Answers (2)

Surendra Gupta
Surendra Gupta

Reputation: 11

@Alexander, Tried with the following code, however did not work as expected.

Could you plz check once and suggest if i am missing something or anything w.r.t version which needs to be upgraded in order to adopt the recommendation

ModificationResponse<PurchaseRequisition> s4ReqResponse =
          RequestHeaderAccessor.executeWithHeaderContainer(dwcHeaderContainer.getHeaders(), () -> {
            ModificationResponse<PurchaseRequisition> s4Response = null;¬
            try {
              s4Response = s4opRequistionService.createPurchaseRequisition(s4opRequistion)
                  .executeRequest(destinationProvider.getDestination());
              return s4Response;
            } catch (ODataException e) {
              logger.error(EXCEPTION_FROM_COPYPR);
              logAndSendErrorMessageToCore(e, requisition);
            }
            return s4Response;
          }
          );
  • Also ensured dwcHeaderContainer consists the values as such {accept-language=de} along with few other attributes.
  • Still stack trace looks similar and could not found a place underneath where it picks the header value from the supplied header values via RequestHeaderAccessor. executeWithHeaderContainer()

StackTrace:

Thread [SimpleAsyncTaskExecutor-41] (Suspended) 
CdsRequestHeaderFacade.tryGetRequestHeaders() line: 28  
RequestHeaderAccessor.tryGetHeaderContainer() line: 89  
DefaultLocaleFacade.getLocalesByHeaders() line: 48  
DefaultLocaleFacade.getCurrentLocales() line: 36    
DefaultLocaleFacade(LocaleFacade).getCurrentLocale() line: 27   
LocaleAccessor.getCurrentLocale() line: 53  
2065479632.get() line: not available    
Option$None<T>(Option<T>).getOrElse(Supplier<? extends T>) line: 336    
DefaultErpHttpDestination(ErpHttpDestinationProperties).getLocale() line: 51    
DefaultErpHttpDestination.getHeadersToAdd() line: 188   
DefaultErpHttpDestination.getHeaders(URI) line: 169 
HttpClientWrapper.wrapRequest(HttpUriRequest) line: 98  
HttpClientWrapper.execute(HttpUriRequest) line: 116 
HttpClientWrapper.execute(HttpUriRequest) line: 35  
DefaultCsrfTokenRetriever.retrieveCsrfTokenResponseHeader(HttpClient, String, Map<String,String>) line: 91  
DefaultCsrfTokenRetriever.retrieveCsrfToken(HttpClient, String, Map<String,String>) line: 54    
ODataRequestCreate(ODataRequestGeneric).lambda$tryGetCsrfToken$5ab307ff$1(CsrfTokenRetriever, HttpClient) line: 266 
1607932978.apply() line: not available  
Try<T>.of(CheckedFunction0<? extends T>) line: 75   
ODataRequestCreate(ODataRequestGeneric).tryGetCsrfToken(HttpClient, CsrfTokenRetriever) line: 266   
ODataRequestCreate(ODataRequestGeneric).tryExecuteWithCsrfToken(HttpClient, Supplier<HttpResponse>) line: 239   
ODataRequestCreate.execute(HttpClient) line: 93 
PurchaseRequisitionCreateFluentHelper(FluentHelperCreate<FluentHelperT,EntityT>).executeRequest(HttpDestinationProperties) line: 246    
S4Adapter.lambda$0(PurchaseRequisition, Map) line: 177  
64962500.call() line: not available 
ThreadContextCallable<T>.call() line: 229   
ThreadContextExecutor(AbstractThreadContextExecutor<ExecutorT>).execute(Callable<T>) line: 320  
RequestHeaderAccessor.executeWithHeaderContainer(RequestHeaderContainer, Callable<T>) line: 185 
RequestHeaderAccessor.executeWithHeaderContainer(Map<String,String>, Callable<T>) line: 160 
S4Adapter.copyPR(Map<String,Object>) line: 173  

Upvotes: 0

Alexander D&#252;mont
Alexander D&#252;mont

Reputation: 938

Answers

Is it the bug from Cloud SDK?

Not really, to me this looks like an incorrect API usage:

Your Destination is of type DefaultErpHttpDestination. It is significantly different from regular destination types, because it automatically adds headers sap-client and sap-language. It seems like the class cannot resolve the request headers at runtime, therefore falls back to system default en.

You are adding headers via OData API, while technically possible it's unreasonable for your actual call. As it results in duplicate header entry.


Is there any way for Cloud SDK not to append again 'sap-language' header?

Yes, do not cast to ErpHttpDestination explicitly.


Recommendation

Instead of fiddling with the destination or odata request, I would suggest to use the following code:

ModificationResponse<PurchaseRequisition> s4ReqResponse =
  RequestHeaderAccessor.executeWithHeaderContainer(
    dwcHeaderContainer.getHeaders(), 
    () -> s4opRequistionService
      .createPurchaseRequisition(s4opRequistion)
      .executeRequest(destinationProvider.getDestination()));

Reason:

  • Keep benefits of ErpHttpDestination
  • Enable correct locale resolution by leveraging the request headers and establishing a ThreadContext.

Upvotes: 0

Related Questions