In the background an Integration with a REST interface may be calling one with a SOAP interface and vice verse. Mapping errors from SOAP to REST or the other way around is not trivial. In this blog article I explain how you could handle this with when using OIC Integration in combination with Oracle Fusion Cloud ERP, at least for most of the cases (no guarantee I cover all 😉 ).
The way your Integration returns a fault is determined by a combination of the type of the adapter (SaaS, SOAP or REST) and how you catch an error coming from the back-end service. As explained in the blog post Fault Handling in OIC you have the most control over how the Integration propagates a fault from a back-end service when invoking it within a Scope and catching it using a Fault Handler with a Fault Return. In this article I assume you follow that pattern.
OIC determines the format of the Fault Return. In case of a SOAP fault this will be a SOAP 1.1 Fault for which the values of the elements are predetermined, except for the <detail>. When using Fault Return the fields you defined in the <fault> of the WSDL end up as structured elements in the <detail> of the fault of the integration. As you can see in this example, I used a format that practically wraps a "SOAP fault" in the <detail> of the actual SOAP fault so that I can give my consumer the best information.
In case of a REST fault all the elements of the Fault Return are fixed but its values are not. In the following example I return a 406 Not Acceptable with a message indicating that the username is missing in the header:
When integrating with Fusion ERP Cloud you typically use one or more of the following options (in order of decreasing preference):
- Using the Oracle ERP Cloud Adapter
This is the preferred way because it provides you 1 single Connection for which you only have to configure the ERP Cloud Host and credentials. When using it lets you search for or browse through all business objects it supports or events you can subscribe to. When invoking a service of the adapter, the fault is returned as a "ServiceException". When using one of the REST resources it returns an APIInvocationError. - Using REST services
Some REST services provide features not available through the Cloud Adapter and more and more REST services are added over time (but also being added to the Cloud Adapter). In practice you may find a few use cases for them, so brace for ending up with a hybrid situation. It's more work to use a REST service than the Cloud Adapter. You still only configure 1 Connection with the base URL and credentials but when using it, for each individual object you must figure out the URI (the last part of the URL that specifies the resource(s) and optional template parameter) and then you have to copy & paste the sample request and response payloads. On the bright side: the fault returned by a REST services is the same APIInvocationError like any other REST service and, as I explain below, mapping an APIInvocationError is much easier with the REST services than with the Cloud Adapter services. - Using BIP Report Service
For a few reasons this is a last resort, only to be used when the previous 2 are not an option. The modeled Faults returned by the BIP Report Service are AccessDeniedException, OperationFailedException, InvalidParametersException.
For reasons of completeness: ERP Cloud also provides SOAP services but except for the option of being able to call them using a tool like SoapUI (for trial & error to determine a request that actually works) they do not offer any added value over the adapter as in the background that uses the same services. In this article this option is therefore not considered.
When you don't catch and handle a fault from a back-end service, in case of a SOAP Integration your Integration will return a fault where both the <faultstring> may contain the text "ICS runtime execution error" or CDATA with the useful fault information in the "blob" of CDATA in the <detail>.
In case of an REST Integration an HTTP status code 500 is returned (no matter what back-end error) with, if you are lucky, some information about the actual cause in the errorDetails.title.
All this is not very helpful for the consumer, especially not when the Integrations are used in a UI or by people with little to no knowledge of ERP. In that case you may want to return a fault populated with useful information about the back-end fault. Now how to do that?
ServiceException
It has a top level Code, Message and Severity element plus a Detail element with a Code, Message and Severity of its own (and a Detail with ... etc. but forget about that). The Code is not always filled out and often (always?) the Code and Message at top-level are pretty generic and not very useful for the consumer. Therefore, you probably want to use the <detail><message> instead. But in practice perhaps it depends. To be honest I don't know. For the limited use cases I dealt with, I ended up using the <detail><message>. Make sure you figure it out for the type of faults you want to catch.
What I ended up doing is this:
ServiceException -> SOAP fault
<faultCode> |
Use a nested choose-when-others to map the <ServiceErrorMessage><detail><code> if there is, otherwise the <ServiceErrorMessage><code> otherwise the <ServiceErrorMessage><exceptionClassName> |
<faultString> |
The <ServiceErrorMessage><message> |
<faultActor> |
"Client" when the issue is at the client-side, "Server" otherwise |
<detail> |
<ServiceErrorMessage><detail><message> |
Makes it look like this:
I could have made it a bit smarter by checking if the string "JBO-" is in <ServiceErrorMessage><message> and then use the substring-before the ":".
ServiceException -> REST fault
type |
when <ServiceErrorMessage><code> is "env:Client" then "https://www.w3.org./Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1", otherwise "https://www.w3.org./Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1" |
title |
when <ServiceErrorMessage><code> is "env:Client" then the title that best fits the actual cause if you are able to get that from the <ServiceErrorMessage><code> or - when you are lazy - "Bad Request", otherwise "Internal Server Error" |
detail |
"Client" when APIInvocationError.errorDetails.errorCode starts with "4", "Server" otherwise |
errorCode |
when <ServiceErrorMessage><code> is "env:Client" then the code that best fits the actual cause or 400 when you are lazy, otherwise 500 |
Makes it looks like this:
APIInvocationError
As mentioned above a REST error is a straightforward APIInvocationError, with top-level elements "type", "title", "detail", "errorCode" and "errorDetails". On it's turn "errorDetails" has subelements "type", "instance", "title", "errorPath" and "errorCode" and these are what gives you the information of the actual back-end error.
What I ended up doing is this:
APIInvocationError -> SOAP fault
<faultCode> |
APIInvocationError.errorDetails.errorCode |
<faultString> |
APIInvocationError.errorDetails.title |
<faultActor> |
"Client" when APIInvocationError.errorDetails.errorCode starts with "4", "Server" otherwise |
<detail> |
APIInvocationError.errorDetails.instance |
Makes it look like this:
APIInvocationError -> REST Fault
As I explained in >Fault Handling in OIC< (https://kettenisblogs.blogspot.com/2020/08/fault-handling-in-oic.html) this is concerns a very straightforward mapping:
type |
APIInvocationError.errorDetails.type |
title |
APIInvocationError.errorDetails.title |
detail |
APIInvocationError.errorDetails.instance |
errorCode |
APIInvocationError.errorDetails.errorCode |
Make it look like this:
BIP Report Service
It is very uncommon to run in any of the modeled faults once you created and tested an Integration using the BIP Report Service. I remember seeing it only once in many months, and forgot what caused it. Runtime errors happen often enough: user password expired, BIP report not properly deployed, response size bigger than 1MB, etc.BIP Report Service error -> SOAP Fault
<faultCode> |
when <fault><details> contains "CASDK" then the substring-before the ":" of the <fault><details>, otherwise the <fault><errorCode> |
<faultString> |
when the <fault><details> contains "Fault Reason" then the substring after "Fault Reason : " and before "]]", otherwise the <fault><reason> |
<faultActor> |
“Server” when <fault><details> contains “env:Sender”, “Server” otherwise |
<detail> |
<fault><details> |
type |
when <fault><details> contains “env:Sender” then "https://www.w3.org./Protocols/rfc2616/rfc2616-sec10.html#sec10.4.1", otherwise "https://www.w3.org./Protocols/rfc2616/rfc2616-sec10.html#sec10.5.1" |
title |
when
<fault><details> contains “env:Sender” then "Bad Request", otherwise "Internal Server Error" |
detail |
<fault><details> |
errorCode |
when <fault><details> contains “env:Sender” then "400", otherwise “500" |
Makes it look like this: