IBM Support

Learn to loop in Sterling B2B Integrator without feeling loopy

Technical Blog Post


Abstract

Learn to loop in Sterling B2B Integrator without feeling loopy

Body

Looping is a common operation when processing data, but the nuts-and-bolts of configuring loops in Sterling B2B Integrator can mystify, especially if you've never created one. I believe the business of creating loops can be made easier if you keep several points in mind:

  1. Legislate
      Add a rule that specifies when to stop looping.
  2. Evaluate
      Place a  Sequence Start at the top of the loop to which we can return after each iteration.
      Place a Choice activity after the Sequence Start to evaluate whether to continue looping.
  3. Iterate.
      Add a Repeat activity just before the Sequence End. This allows us to return to the Sequence Start at the beginning of the loop.
  4. Vacate (exit the loop.)

The sample business process we'll use for our discussion is collectFromMboxProcEDI.  This BP grabs application files from a mailbox and calls an EDI Enveloping process for each file.

collectFromMboxProcEDI is called by a Routing Rule. While configuring Routing Rules for mailbox messages is outside our discussion, our documentation provides more information about Routing Rules.

When there are Extractable files in the mailbox, the routing rule initiates the business process. The first thing in ProcessData will be the eligible messages in the mailbox:

<?xml version="1.0" encoding="UTF-8"?>
<ProcessData>
  <RoutingRequest>
    <RoutingRequest>
      <RuleId>-3000520a:1637c4c5544:-30fe</RuleId>
      <MessageId>38</MessageId>
      <MessageId>39</MessageId>
      <MessageId>40</MessageId>
    </RoutingRequest>
  </RoutingRequest>
</ProcessData>

We see that in this case we have three messages. We'd like to process each one in turn, removing the top MessageId from the ProcessData as we go. Once the ProcessData has no more MessageId elements under /ProcessData/RoutingRequest/RoutingRequest, we're done. It's XPath to the rescue as we create our rule:

<rule name="more2Extract">
   <!-- Note the use of &gt; in lieu of the 'greater than' ( > ) symbol.

        Otherwise, the BPML interpreter would regard the symbol as being

        part of an XML tag.

   -->
   <condition>count(/ProcessData/RoutingRequest/RoutingRequest/MessageId) &gt; 0</condition>
</rule>

So, once the count of /ProcessData/RoutingRequest/RoutingRequest/MessageIds reaches 0, we cease looping.

We've made a wonderful start by establishing our end point. Now we need to start the loop. Our BPML so far looks like:

<process name="collectFromMboxProcEDI">
  <rule name="more2Extract">
    <condition>count(/ProcessData/RoutingRequest/RoutingRequest/MessageId) &gt; 0</condition>
  </rule>
  <sequence name="main_start">

 
After each iteration, we'll need a place to which we can return and find out if we need to continue.  After the 'main_start' Sequence Start, we add another Sequence Start, 'check4moreMsgs':
 
 <process name="collectFromMboxProcEDI">
  <rule name="more2Extract">
    <condition>count(/ProcessData/RoutingRequest/RoutingRequest/MessageId) &gt; 0</condition>
  </rule>
  <sequence name="main_start">
    <sequence name="check4MoreMsgs">

Next we'll add a  a Choice Activity (there's a Release Service in front of the Choice, which I'll explain):

<process name="collectFromMboxProcEDI">
  <rule name="more2Extract">
    <condition>count(/ProcessData/RoutingRequest/RoutingRequest/MessageId) &gt; 0</condition>
  </rule>
  <sequence name="main_start">
    <sequence name="check4MoreMsgs">

           
<!--
      remove values from the previous loop (on the 1st loop, there will be nothing to remove)
      -->

      <operation name="Release Service">
        <participant name="ReleaseService"/>
        <output message="ReleaseServiceTypeInputMessage">
          <assign to="TARGET">/ProcessData/*[name() = 'ContentType' or
            name() = 'CreateDateTime' or name() = 'DocumentId' or
            name() = 'ERROR_SERVICE'  or name() = 'ExtractableCount' or
            name() = 'INVOKE_ID_LIST' or name() = 'MailboxPath' or
            name() = 'MessageId'      or name() = 'MessageName' or
            name() = 'MessageSize'    or name() = 'message_to_child' or
            name() = 'Prev_NotSuccess_Adv_Status' or
            name() = 'PrimaryDocument']</assign>
          <assign to="." from="*"/>
        </output>
     
   <choice>
          <select>
            <case ref="more2Extract" activity="processMsgs"/>
          </select>
          <sequence name="processMsgs">

 

As you can see, there's a Release Service in front of the Choice, which seems to come from out of the blue. However, this is not our first rodeo, so we know that as we extract messages from the mailbox, we'll see new elements placed in the ProcessData.  As we reach the top of the loop, we'll release ProcessData elements left over from the previous loop (if any - the first time through, the Release Service will have no elements to release.)

Meanwhile, back at the Choice, we see that, based on a given condition, a Choice will invoke a sequence of steps. In this case, our choice is a simple one: continue or stop. Since we need no steps if the choice is to stop, we need only configure the steps for the instance where we continue.
      
At this point, you're probably way ahead of me. You can see that we evaluate the condition in the 'more2Extract' rule, and if it evaluates to TRUE (that is, if the number of MessageIds is greater than 0) , we kick off a sequence of steps beginning with the sequence name 'processMsgs'. Let's look at the business process in its entirety:

<process name="collectFromMboxProcEDI">
  <rule name="more2Extract">
    <condition>count(/ProcessData/RoutingRequest/RoutingRequest/MessageId) &gt; 0</condition>
  </rule>
  <sequence name="main_start">
    <sequence name="check4MoreMsgs">
     
<!--
        remove values from the previous loop (on the 1st loop, there will be nothing to remove)
      -->

      <operation name="Release Service">
        <participant name="ReleaseService"/>
        <output message="ReleaseServiceTypeInputMessage">
          <assign to="TARGET">/ProcessData/*[name() = 'ContentType' or
            name() = 'CreateDateTime' or name() = 'DocumentId' or
            name() = 'ERROR_SERVICE'  or name() = 'ExtractableCount' or
            name() = 'INVOKE_ID_LIST' or name() = 'MailboxPath' or
            name() = 'MessageId'      or name() = 'MessageName' or
            name() = 'MessageSize'    or name() = 'message_to_child' or
            name() = 'Prev_NotSuccess_Adv_Status' or
            name() = 'PrimaryDocument']</assign>
          <assign to="." from="*"/>
        </output>
        <input message="inmsg">
          <assign to="." from="*"/>
        </input>
      </operation>
      <choice>
        <select>
          <case ref="more2Extract" activity="processMsgs"/>
        </select>
        <sequence name="processMsgs">
          <assign to="MessageId" from="/ProcessData/RoutingRequest/RoutingRequest/MessageId[1]/text()"/>
          <operation name="Release Service">
            <participant name="ReleaseService"/>
            <output message="ReleaseServiceTypeInputMessage">
              <assign to="TARGET">/ProcessData/RoutingRequest/RoutingRequest/MessageId[1]</assign>
              <assign to="." from="*"/>
            </output>
            <input message="inmsg">
              <assign to="." from="*"/>
            </input>
          </operation>
          <operation name="MailboxExtractBegin">
            <participant name="MailboxExtractBegin"/>
            <output message="outmsg">
              <assign to="ExtractableCount">1</assign>
              <assign to="CommitNow">NO</assign>
              <assign to="." from="*"/>
            </output>
            <input message="inmsg">
              <assign to="." from="*"/>
            </input>
          </operation>
          <operation name="Invoke Business Process Service">
            <participant name="InvokeBusinessProcessService"/>
            <output message="outmsg">
              <assign to="WFD_NAME">SimpleDocExtract_with_Immediate_Enveloping</assign>
              <assign to="NOTIFY_PARENT_ON_ERROR">NONE</assign>
              <assign to="." from="*"/>
            </output>
            <input message="inmsg">
              <assign to="." from="*"/>
            </input>
          </operation>
          <operation name="MailboxExtractCommitService">
            <participant name="MailboxExtractCommit"/>
            <output message="MailboxExtractCommitServiceTypeInputMessage">
              <assign to="MessageId" from="/ProcessData/MessageId/text()"/>
              <assign to="." from="*"/>
            </output>
            <input message="inmsg">
              <assign to="." from="*"/>
            </input>
          </operation>
          <repeat ref="check4MoreMsgs"/>
          <onFault>
            <sequence>
              <assign to="." from="DOMToDoc(ERROR_SERVICE, 'PrimaryDocument', 'yes', 'ERROR_SERVICE')"/>
              <assign to="message_to_child" from="/ProcessData/PrimaryDocument | /ProcessData/MessageId" append="true"/>
              <operation name="Invoke Business Process Service">
                <participant name="InvokeBusinessProcessService"/>
                <output message="InvokeBusinessProcessServiceTypeInputMessage">
                  <assign to="INVOKE_MODE">ASYNC</assign>
                  <assign to="WFD_NAME">reportError</assign>
                  <assign to="." from="*"/>
                </output>
                <input message="inmsg">
                  <assign to="." from="*"/>
                </input>
              </operation>
              <!-- after a failed message, keep processing -->
              <repeat ref="check4MoreMsgs"/>
            </sequence>
          </onFault>
        </sequence>
      </choice>
    </sequence>
  </sequence>
</process>

Woah! That's a lot isn't it?  Let's consider each operation:

We assign the text value of /ProcessData/RoutingRequest/RoutingRequest/MessageId[1] to /ProcessData/MessageId. We don't commit the message just yet (that is, we don't yet indicate that the extraction succeeded.)

<sequence name="processMsgs">
  <assign to="MessageId" from="/ProcessData/RoutingRequest/RoutingRequest/MessageId[1]/text()"/>
 
<!-- once we assign /ProcessData/RoutingRequest/RoutingRequest/MessageId[1]/text() to /ProcessData/MessageId, we can release it -->
  <operation name="Release Service">
    <participant name="ReleaseService"/>
    <output message="ReleaseServiceTypeInputMessage">
      <assign to="TARGET">/ProcessData/RoutingRequest/RoutingRequest/MessageId[1]</assign>
      <assign to="." from="*"/>
    </output>
    <input message="inmsg">
      <assign to="." from="*"/>
    </input>
  </operation>

  <!-- here is where the loop really gets going.  we extract a message from the mailbox -->
  <operation name="MailboxExtractBegin">
    <participant name="MailboxExtractBegin"/>
    <output message="outmsg">
      <assign to="ExtractableCount">1</assign>
      <assign to="CommitNow">NO</assign>
      <assign to="." from="*"/>
    </output>
    <input message="inmsg">
      <assign to="." from="*"/>
    </input>
  </operation>

We call a subprocess. What happens if it errors?  The subprocess will report its own errors (that's how I configured it):

<operation name="Invoke Business Process Service">
<participant name="InvokeBusinessProcessService"/>
  <output message="outmsg">
    <assign to="WFD_NAME">SimpleDocExtract_with_Immediate_Enveloping</assign>
    <assign to="NOTIFY_PARENT_ON_ERROR">NONE</assign>
    <assign to="." from="*"/>
  </output>
  <input message="inmsg">
    <assign to="." from="*"/>
  </input>
</operation>
   

We now commit the message and go back to the top of the loop ...

<operation name="MailboxExtractCommitService">
<participant name="MailboxExtractCommit"/>
  <output message="MailboxExtractCommitServiceTypeInputMessage">
    <assign to="MessageId" from="/ProcessData/MessageId/text()"/>
    <assign to="." from="*"/>
  </output>
  <input message="inmsg">
    <assign to="." from="*"/>
  </input>
</operation>

<repeat ref="check4MoreMsgs"/>

... unless we encounter an error - in that case, we call a BP that reports the error, then we head back to the top of the loop (a failed message does not stop us from continuing.)

<onFault>
  <sequence>

    <!-- the DOMToDoc() call merits further discussion.  When a message has an error,
         we get a node in the ProcessData describing the error:

         <ERROR_SERVICE>
           <WFD_NAME>collectFromMboxProcEDI</WFD_NAME>
           <SERVICE_NAME>MailboxExtractBegin</SERVICE_NAME>
           <STEP_ID>12</STEP_ID>
           <ADV_STATUS>Mailbox Message Is Not Extractable</ADV_STATUS>
           <BASIC_STATUS>1</BASIC_STATUS>
           <WFC_ID>b2b-apollo:node1:1639786ed06:403287</WFC_ID>
         </ERROR_SERVICE>

                   Let's use it as input to our e-mail that reports the error.

        -->
    <assign to="." from="DOMToDoc(ERROR_SERVICE, 'PrimaryDocument', 'yes', 'ERROR_SERVICE')"/>

    <!-- message_to_child allows us to limit the amount of data we send to another process -->
    <assign to="message_to_child" from="/ProcessData/PrimaryDocument | /ProcessData/MessageId" append="true"/>
    <operation name="Invoke Business Process Service">
    <participant name="InvokeBusinessProcessService"/>
      <output message="InvokeBusinessProcessServiceTypeInputMessage">
        <assign to="INVOKE_MODE">ASYNC</assign>
        <assign to="WFD_NAME">reportError</assign>
        <assign to="." from="*"/>
      </output>
      <input message="inmsg">
        <assign to="." from="*"/>
      </input>
    </operation>
   
<!-- after a failed message, keep processing -->
    <repeat ref="check4MoreMsgs"/>
  </sequence>
</onFault>


After that, we end our sequences and our choice, and the BP is complete.

        </sequence>
      </choice>
    </sequence>
  </sequence>
</process>

Though this example pertained to mailbox messages, the looping concepts discussed here should apply to any loop you create in Sterling B2B Integrator. This is by no means an exhaustive discussion of looping.  Nevertheless, I hope that this has answered some of the questions you may have had about creating loops in Sterling B2B Integrator. Before we go, though, let's look at a situation were we need to choose between two activities, say, whether to process an XML file or an EDI file.  Here is an example from the out-of-the-box business process, EDIOutboundBootstrap:

<choice>
  <select>
    <case ref="found_XML" negative="true" activity="process_EDI"/>
    <case ref="found_XML" activity="process_XML"/>
  </select>

As you can see, if the 'found_XML' rule returns TRUE, the steps beginning with the Sequence Start 'process_XML' will be executed.  If the 'found_XML' rule returns FALSE (that is, negative="true") the steps beginning with the  Sequence Start 'process_EDI' will be executed. 

Want more info about looping in SBI?  I encourage you to check out my colleague Carsten Michel's blog entry

[{"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Product":{"code":"SS3JSW","label":"IBM Sterling B2B Integrator"},"Component":"","Platform":[{"code":"PF025","label":"Platform Independent"}],"Version":"","Edition":"","Line of Business":{"code":"LOB59","label":"Sustainability Software"}}]

UID

ibm11120653