Oracle B2B Java Callout

Oracle B2B Java Callout to intercept incoming ebXML wire messages

Published on: Category: Oracle

In 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).

Figure 1: ebXML message example
  1. ------=_Part_11_1995803378.1472136462013
  2. Content-Type: text/xml;charset=UTF-8
  3. Content-ID: <0>
  4.  
  5. <ebXML envelop…..>
  6. ------=_Part_11_1995803378.1472136462013
  7. Content-Type: application/octet-stream
  8. Content-Transfer-Encoding: binary
  9. Content-ID: <Payload-1>
  10.  
  11. <gzipped file containing payload>
  12. ------=_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
Figure 2: B2B callout schematic [3]

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’.

Figure 3: Listening Channel configuration

Listening channels can be defined for the following generic protocols (see figure 4).

Figure 4: Oracle B2B supported Listening Channel Protocols

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

Figure 5: Finding the B2B Server Properties MBean

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

Figure 6: B2B MBean Attributes

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.

Figure 7: B2B MBean Operations

5) Click setProperty to configure the HTTPTransportCallout for the CalloutReceiver. b2b.HTTPTransportCalloutName =<name of callout>

Figure 8: B2B MBean: Setting callout property

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].

Figure 9: Java source code for Callout
  1. package nl.qualogy.b2bcallout;
  2.  
  3. import oracle.tip.b2b.callout.Callout;
  4. import oracle.tip.b2b.callout.CalloutContext;
  5. import oracle.tip.b2b.callout.CalloutMessage;
  6. import oracle.tip.b2b.callout.exception.CalloutDomainException;
  7. import oracle.tip.b2b.callout.exception.CalloutSystemException;
  8. import java.util.*;
  9. import java.io.*;
  10.  
  11.  
  12. public class MyCallout implements Callout {
  13.  
  14. public void execute(CalloutContext arg0, List input,
  15. List output) throws CalloutDomainException,
  16. CalloutSystemException {
  17. try {
  18. CalloutMessage cm1 = (CalloutMessage)input.get(0);
  19. CalloutMessage cmOut = null;
  20.  
  21. String msg = cm1.getBodyAsString();
  22. byte[] decode = null;
  23.  
  24. decode = cm1.getBodyAsBytes();
  25.  
  26. byte[] search = "application/octet-stream".getBytes("UTF-8");
  27. byte[] replacement = "application/gzip".getBytes("UTF-8");
  28.  
  29. // Find and replace in bytearray stream
  30. InputStream ris = new ReplacingInputStream(bis, search, replacement);
  31.  
  32. int b;
  33. while (-1 != (b = ris.read()))
  34. bos.write(b);
  35.  
  36. // Use modified byte array as output
  37. cmOut = new CalloutMessage(bos.toByteArray());
  38. output.add(cmOut);
  39.  
  40. } catch (Exception e) {
  41. System.out.println("Exception: " + e.getMessage() );
  42. }
  43.  
  44. }
  45.  
  46.  
  47. class ReplacingInputStream extends FilterInputStream {
  48.  
  49. LinkedList<Integer> inQueue = new LinkedList<Integer>();
  50. LinkedList<Integer> outQueue = new LinkedList<Integer>();
  51. final byte[] search, replacement;
  52.  
  53. protected ReplacingInputStream(InputStream in, byte[] search,
  54. byte[] replacement) {
  55. super(in);
  56. this.search = search;
  57. this.replacement = replacement;
  58. }
  59.  
  60. private boolean isMatchFound() {
  61. Iterator<Integer> inIter = inQueue.iterator();
  62. for (int i = 0; i < search.length; i++)
  63. if (!inIter.hasNext() || search[i] != inIter.next())
  64. return false;
  65. return true;
  66. }
  67.  
  68. private void readAhead() throws IOException {
  69. // Work up some look-ahead.
  70. while (inQueue.size() < search.length) {
  71. int next = super.read();
  72. inQueue.offer(next);
  73. if (next == -1)
  74. break;
  75. }
  76. }
  77.  
  78.  
  79. @Override
  80. public int read() throws IOException {
  81.  
  82. // Next byte already determined.
  83. if (outQueue.isEmpty()) {
  84.  
  85. readAhead();
  86.  
  87. if (isMatchFound()) {
  88. for (int i = 0; i < search.length; i++)
  89. inQueue.remove();
  90.  
  91. for (byte b : replacement)
  92. outQueue.offer((int) b);
  93. } else
  94. outQueue.add(inQueue.remove());
  95. }
  96.  
  97. return outQueue.remove();
  98. }
  99.  
  100. }
  101.  
  102. }

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”.

Figure 10: Configure Oracle B2B Callout Directory

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

Figure 11: Configure Oracle B2B Callout

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.

Figure 12: ebXML post example using calloutReceiver
  1. POST /b2b/calloutReceiver HTTP/1.1
  2. Host: myserver.qualogy.nl
  3. ChannelName: TransportServlet
  4. Cache-Control: no-cache
  5. MSG_RECEIVED_TIME: Thu Apr 25 12:12:30 CEST 2017
  6. SOAPAction: "ebXML"
  7. Content-Type: multipart/related; boundary="----=_Part_11_1995803378.1472136462013"; type="text/xml"; start="<0>"; start-info="text/xml"
  8. Content-Length: 3584
  9. Connection: Keep-Alive
  10. ------=_Part_11_1995803378.1472136462013
  11. Content-Type: text/xml;charset=UTF-8
  12. Content-ID: <0>
  13.  
  14. <ebXML envelop…..>
  15. ------=_Part_11_1995803378.1472136462013
  16. Content-Type: application/octet-stream
  17. Content-Transfer-Encoding: binary
  18. Content-ID: <Payload-1>
  19.  
  20. <gzipped file containing payload>
  21. ------=_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

Richard Velden
About the author Richard Velden

Oracle Fusion Middleware Developer at Qualogy. Specializes in integration and cloud development using Oracle technologies such as: SOA Suite, Service Bus, Integration and Process Cloud.

More posts by Richard Velden