Every developer must have those moments when you feel you are completely stuck with some problem. You feel like you are never going to solve the issue at hand. The attempt is doomed, you have failed. Well, let’s get a good night’s sleep, who knows what will happen. And the next day, that glorious moment when all of a sudden you see the light, you get going again and you resolve the issue. It may very well be that those moment define what really is so great about programming. Getting unstuck.
The other night, I was working with the Oracle Enterprise Service Bus, building a Domain Service that is called with the name of a specific domain of allowable values, reads data synchronously from a file that contains a great number of domains, filters the results from the file read and returns the filtered results, enriched with the name of the domain as originally requested. But I could not figure out how to filter the results from the File Adapter based on information from the Service Request. I really got stuck.
Sometimes a single remark in some article or an off-hand comment in a tutorial or a bit of information Googled up from the internet can force that breakthrough. The exact right piece of information at the exact right time. My precious piece of knowledge to get unstuck came from a Discussion Thread on the OTN Forum on Oracle Enterprise Service Bus:
Data enrichment in Oracle ESB started by Allan from Glasgow. I have to thank Allan for asking my questions for me and Alistair from Vancouver and Dave Berry from Oracle for providing the bits and pieces I was in need of. Thanks guys!
So what that incredible clue I got from this OTN thread? It is called ESBREQUEST. It is a parameter you can add (or have added) to the XSLT document used to map the reply from the service invoked by one of the Routing Rules in the Routing Service. The parameter makes the content of the Request that went into the Routing Service available during the transformation of the results. This data from the original request can be added to the results (enrichment) or used to filter the results. I will show a simple example of using the ESBREQUEST parameter.
I have created the DomainValuesService, a very simple service that is called with a request defined as follows:
<complexType name="DomainServiceCallType"> <sequence> <element name="name" type="string" /> <element name="language" type="string" minOccurs="0" maxOccurs="1"/> <element name="matchRegExpPattern" type="string" minOccurs="0" maxOccurs="1"/> <element name="likePattern" type="string" minOccurs="0" maxOccurs="1" /> <element name="maximumNumberOfElements" type="int" minOccurs="0" maxOccurs="1"/> </sequence> </complexType>
It requires the name of a domain and can also handle a language, a search/match pattern and a maximum number of elements to return. The reply returned by this Synchronous service is defined through the next XSD complexType:
<complexType name="DomainServiceResponseType"> <sequence> <element name="name" type="string" /> <element name="language" type="string" minOccurs="0" maxOccurs="1"/> <element name="numberOfElements" type="int" minOccurs="0" maxOccurs="1"/> <element name="DomainValue" type="ds:DomainValueType" minOccurs="0" /> </sequence> </complexType> <complexType name="DomainValueType"> <sequence> <element name="value" type="string" /> <element name="label" type="string" /> <element name="description" type="string" minOccurs="0" maxOccurs="1" /> </sequence> </complexType>
This specifies a reply that can hold the allowable values for a single domain – a child element DomainValue within the root element.
The domain values are being maintained in an XML file that lives on the file system. The ESB Service needs to receive the request for the values in a specific domain, read the file with all the domain, filter the results read from the file to only contain values from the domain requested, return the results in the format specified in the DomainServiceResponseType.
First of all, I create a File Adapter Service that synchronously reads the file that holds the domains. This file is specified through and XSD:
<?xml version="1.0" encoding="windows-1252" ?> <schema elementFormDefault="qualified" attributeFormDefault="qualified" targetNamespace="https://technology.amis.nl/blog/DomainService/FileBased" xmlns:ds="https://technology.amis.nl/blog/DomainService/FileBased" xmlns="http://www.w3.org/2001/XMLSchema"> <element name="domains" type="ds:domainsType"></element> <complexType name="domainsType"> <sequence> <element name="domain" type="ds:domainType" minOccurs="0" maxOccurs="unbounded"/> </sequence> </complexType> <complexType name="domainType"> <sequence> <element name="Name" type="string" minOccurs="1"/> <element name="domainValue" type="ds:ValueType" minOccurs="0" maxOccurs="unbounded"/> </sequence> </complexType> <complexType name="ValueType"> <sequence> <element name="value" type="string"/> <element name="label" type="string"/> <element name="description" type="string" minOccurs="0" maxOccurs="1"/> </sequence> </complexType> </schema>
The file looks like this:
<?xml version="1.0" encoding="UTF-8" ?> <domains > <domain> <Name>color</Name> <domainValue> <value>r</value> <label>Red</label> <description>The beautiful red color</description> </domainValue> <domainValue> <value>g</value> <label>Green</label> ... </domain> <domain> <Name>gender</Name> <domainValue> <value>m</value> <label>Male</label> <description>Man the hunter, Marsian and all</description> </domainValue> <domainValue> ... </domain> </domains>
This is a special File Adapter Service: it reads a file yet it is considered outbound. It does not poll for files, it does not write a file. It reads one particular file – as if it were a database table!
After having set up the File Adapter Service, I create a Routing Service: it receives the Service invocation, calls the File Adapter Service to read the file and then has to transform and filter the results from the file read into the reply to be returned to the caller.
Press OK and go define the Service(s) to be invoked:
I have indicated that I want to invoke the SynchRead operation on the ReadFileDomains File Adapter Service. This service call will return the XML document with all the domains. Now I want to filter that document in order to only reply from this Routing Service with the requested domain. I need to set up the proper Transformation Map to that end:
Press the transformation icon after the Reply Transformation Map field. The following, crucial popup opens:
“0” border=”0″ align=”bottom” src=”https://technology.amis.nl/wp-content/uploads/images/esbrequest_replytransformationMap.jpg” />
If you check the check box Include Request in the Reply Payload you will have an extra parameter ESBREQUEST to retrieve data from during the transformation! When you look at the source of the XSLT Mapper document, it is specified as follows:
Note that the $ESBREQUEST parameter starts at the root-element of the Request element that was sent into the Routing Service. in this example, that means that the $ESBREQUEST parameter is an XML fragment starting with a domainServiceCall element.
The esbsvc file created for the Routing Service is where the check Include Request in the Reply Payload check box reemerges:
... <routingRules> <routingRule guid="8CF28D209AF611DBBFE9995917951383" subscriptionType="ANY" executionType="Immediate" status="ENABLED"> <targetOperation guid="68E67E509AF611DBBFE9995917951383" qname="DomainValuesService.ReadFileDomains.SynchRead" servic.... <replyHandler> <transformation attachRequestPayload="true"> <xslFileURL>domains_To_domainServiceResponse.xsl</xslFileURL> </transformation> </replyHandler> </routingRule> </routingRules> ....
If the attachRequestPayload attribute of the transformation element is not set to true (and by default it is set to false), there will be no ESBREQUEST parameter available during the transformation. There does not seem to be a way to include the request into the payload later on: if you miss the checkbox during initial creation of the mapper file, you cannot get it back through the GUI. It seems that the best you can do is close all files in JDeveloper (even better, close the project) edit the esbsvc file for the Router Service through a text editor on the file system, refresh the files in JDeveloper (or reopen the project) and edit the mapper XSL file.
Making use of the ESBREQUEST parameter in the transformation
The importance of the ESBREQUEST parameter of course lies in what we can do with it. As it contains all information that we received in the request into the Routing Service, we can use that information for filtering or other transformation purposes. In my domains example, I want to create a Reply that only contains the requested domain. Furthermore, I want the name of the domain as requested to also appear in the reply (basically copying that value from the ESBREQUEST parameter into the reply).
The essential part of the XSL document is shown here:
<xsl:param name="ESBREQUEST"/> <xsl:template match="/"> <out1:domainServiceResponse> <out1:name> <xsl:value-of select="$ESBREQUEST//out1:name"/> </out1:name> <xsl:for-each select="/domains/domain[Name=$ESBREQUEST//out1:name]/domainValue"> <out1:DomainValue> <out1:value> <xsl:value-of select="value"/> </out1:value> <out1:label> <xsl:value-of select="label"/> </out1:label> <out1:description> <xsl:value-of select="description"/> </out1:description> </out1:DomainValue> </xsl:for-each> </out1:domainServiceResponse> </xsl:template> </xsl:stylesheet>
Note that I have left out the namespace definitions.
Using this transformation map with the filter on domains using the XPath expression [Name=$ESBREQUEST//out1:name] fueled by data from the request, I can quickly extract the domain that is requested.
In general the ESBREQUEST parameter allows me to manipulate the reply from any synchronous RouterService using the content from the request into the service. I can add information from the request to the reply as well as use the data from the request to perform filtering and other manipulation. That is really useful!