Oracle B2B Java Callout to intercept incoming ebXML wire messages
Published on: Author: Richard Velden Category: OracleIn this article we will discuss Oracle B2B Callouts. Callouts are used to perform custom Java logic on incoming or outgoing messages. Typical use cases include adding or modifying (custom) HTTP headers, archiving messages, or doing XSL transformations from within B2B. Learn more.
For our use case we need to intercept an incoming ebXML wire message, and change some part of the wire message itself before processing it in B2B.
Use case description
In this use case we are receiving binary payloads, which are processed incorrectly in Oracle B2B. The incoming ebXML message has the following structure (see figure 1).
------=_Part_11_1995803378.1472136462013 Content-Type: text/xml;charset=UTF-8 Content-ID: <0> <ebXML envelop…..> ------=_Part_11_1995803378.1472136462013 Content-Type: application/octet-stream Content-Transfer-Encoding: binary Content-ID: <Payload-1> <gzipped file containing payload> ------=_Part_11_1995803378.1472136462013--
Oracle B2B seems to be having problems processing the GZIP payload in the second MIME part of the message. Changing the Content-Type from application/octet-stream to application/gzip will solve the issue, but our trading partner was not planning to perform this change.
Solution
We need to intercept the wire message in B2B and modify the Content-Type value to ‘application/gzip’.
Callouts to the rescue
As mentioned earlier, callouts can be used to perform some custom Java logic on incoming messages. By modifying the incoming wire-message we will be able to change the MIME Content-Type of the second message part. Oracle B2B provides 2 hooks on which to apply a callout:
- Agreement level
- Transport level
We need an inbound Transport Callout. This can be configured in B2B on either a Listening Channel, or on a Host Trading Partner Delivery Channel.
Listening Channels
“Listening channels are used globally. You do not need to select a listening delivery channel in an agreement. Listening channels are used for any trading partner to send inbound messages to Oracle B2B or for any back-end business application to send outbound messages to Oracle B2B.” [2]
Defining Listening Channels in B2B is performed in the Administration section under tab ‘Listening Channel’.
Listening channels can be defined for the following generic protocols (see figure 4).
Each listening channel can be configured to use a Callout. For ebXML we need either the ebXML protocol, or a Generic HTTP listening channel. Sadly this is not supported here.
Apart from defining our own listening channels, Oracle B2B supports several default HTTP listening channels:
- httpReceiver / transportServlet
- syncReceiver
- sequenceReceiver
- calloutReceiver
For receiving ebXML messages we normally use the generic httpReceiver. However, to use callouts we need to use the calloutReceiver instead. It can be found on the server: http://b2bhostname:7001/b2b/calloutReceiver
Configure Callout Receiver
By default, the callout receiver does not execute any Java Callout. Because it is possible to define multiple Callouts in Oracle B2B, the callout receiver needs to be instructed which specific callout to use. This configuration is performed by setting a MBean propery using the Enterprise Manager (em) console.
1) Login to EM Console
2) Right click soa-infra -> SOA Administration -> B2B Server Properties
This will bring you to the B2B Managed Bean properties: You can also use the MBean browser and search for the MBean using the pattern filter: oracle.as.soainfra.config:name=b2b,type=B2BConfig,Application=soa-infra
3) You can check the properties in this MBean, to verify whether property b2b.HTTPTransportCalloutName has been defined already. Click Properties to check all configured MBean properties.
4) Select the Operations Tab to set a new MBean property value.
5) Click setProperty to configure the HTTPTransportCallout for the CalloutReceiver. b2b.HTTPTransportCalloutName =<name of callout>
The name of the callout should correspond with the name used when defining the callout in B2B Administration.
6) Press the Invoke button to persist the changes.
Creating Java Callout
The actual implementation for the callout is listed in figure 9.
Quickly summarized: the message body is retrieved from B2B as a byte array. This ensures the binary stream is processed correctly. Converting to String will cause a character-set conversion which will wreck the binary stream. The ReplacingInputStream private class is used to find and replace a string value within a large bytearray [4].
package nl.qualogy.b2bcallout; import oracle.tip.b2b.callout.Callout; import oracle.tip.b2b.callout.CalloutContext; import oracle.tip.b2b.callout.CalloutMessage; import oracle.tip.b2b.callout.exception.CalloutDomainException; import oracle.tip.b2b.callout.exception.CalloutSystemException; import java.util.*; import java.io.*; public class MyCallout implements Callout { CalloutSystemException { try { CalloutMessage cm1 = (CalloutMessage)input.get(0); CalloutMessage cmOut = null; byte[] decode = null; decode = cm1.getBodyAsBytes(); byte[] search = "application/octet-stream".getBytes("UTF-8"); byte[] replacement = "application/gzip".getBytes("UTF-8"); // Find and replace in bytearray stream int b; while (-1 != (b = ris.read())) bos.write(b); // Use modified byte array as output cmOut = new CalloutMessage(bos.toByteArray()); output.add(cmOut); } } LinkedList<Integer> inQueue = new LinkedList<Integer>(); LinkedList<Integer> outQueue = new LinkedList<Integer>(); final byte[] search, replacement; byte[] replacement) { super(in); this.search = search; this.replacement = replacement; } private boolean isMatchFound() { Iterator<Integer> inIter = inQueue.iterator(); for (int i = 0; i < search.length; i++) if (!inIter.hasNext() || search[i] != inIter.next()) return false; return true; } // Work up some look-ahead. while (inQueue.size() < search.length) { int next = super.read(); inQueue.offer(next); if (next == -1) break; } } @Override // Next byte already determined. if (outQueue.isEmpty()) { readAhead(); if (isMatchFound()) { for (int i = 0; i < search.length; i++) inQueue.remove(); for (byte b : replacement) outQueue.offer((int) b); } else outQueue.add(inQueue.remove()); } return outQueue.remove(); } } }
Compile, package and load Callout
For compiling the Java callout we need to include the b2b.jar library. For some utility classes you could also consider including the utils.jar.
1) Compiling can be done as shown here:
javac –classpath $MW_HOME/modules/com.bea.core.utils_1.10.0.0.jar: $MW_HOME/soa/soa/modules/oracle.soa.b2b_11.1.1/b2b.jar nl/qualogy/b2bcallout/MyCallout.java
2) Packaging it into a jar file is simply done using:
jar cf MyCallout.jar nl
3) To update Oracle B2B on the fly to use this new callout you can use the following command:
ant -f $MW_HOME/soa/bin/ant-b2b-util.xml b2bupdatecalloutjars -Dpath="/home/oracle/callouts" -Dlibraryname=" MyCallout.jar"
Configure Callout
Once a callout has been compiled, packaged and loaded we are ready to configure Oracle B2B to use the callout.
1) Login to the Oracle B2B Console
2) Navigate to Administration -> Configuration
3) Here we will configure the folder where the callout jarfile is located. In our example we have used “/mycalloutdir”.
4) Save and navigate to the Callout tab
5) Create a new Callout by clicking the plus icon
- a. Name: MyCallout
- b. Implementation Class: nl.qualogy.b2bcallout.MyCallout
- c. Library Name: MyCallout.jar
6) Save
Testing Callout
For testing the callout we need to send some message to the calloutReceiver on Oracle B2B. Because our payload contains binary data, we can no longer use Telnet for sending messages to B2B (see previous article)
Sending a file stream to a particular port in Linux can also be performed by redirecting a file to the proper device. Please check the example below in which we send a file over TCP to our b2b host on port 7001.
cat b2bTelnetPost.txt > /dev/tcp/myb2bhost/7001
Next part: b2bTelnetPost.txt
Next part is the actual contents of this b2bTelnetPost.txt. Please mind the POST to /b2b/calloutReceiver.
POST /b2b/calloutReceiver HTTP/1.1 Host: myserver.qualogy.nl ChannelName: TransportServlet MSG_RECEIVED_TIME: Thu Apr 25 12:12:30 CEST 2017 SOAPAction: "ebXML" Content-Type: multipart/related; boundary="----=_Part_11_1995803378.1472136462013"; type="text/xml"; start="<0>"; start-info="text/xml" Content-Length: 3584 ------=_Part_11_1995803378.1472136462013 Content-Type: text/xml;charset=UTF-8 Content-ID: <0> <ebXML envelop…..> ------=_Part_11_1995803378.1472136462013 Content-Type: application/octet-stream Content-Transfer-Encoding: binary Content-ID: <Payload-1> <gzipped file containing payload> ------=_Part_11_1995803378.1472136462013--
References
[1] Oracle Documentation: Managing Callouts
[2] Oracle Documentation: Configuring Listening Channels
[3] Getting Started with Oracle SOA B2B Integration: A Hands-On Tutorial, by Scott Haaland, Krishnaprem Bhatia, Alan Perlovsky, Packt Publishing, ISBN: 9781849688864, 2013
[4] ReplacingInputStream source code