Tuesday, 19 April 2011

Array Operations in WS-BPEL

Share
Almost 3 years ago I wrote a short article about handling complex types in WS-BPEL. Today I will show you how to handle arrays.

I used Eclipse BPEL Designer, Apache ODE as a WS-BPEL engine, and soapUI as a Web Services testing tool.

Let's start.

Simple process

First, I created a synchronous process using Eclipse BPEL Designer wizard. I didn't modify the WSDL file I just added the following assign activity to the WS-BPEL stub:

<bpel:assign validate="no" name="Assign">
  <bpel:copy>
   <bpel:from>
    <bpel:literal xml:space="preserve"><tns:ArrayOperationsBusinessProcessResponse
     xmlns:tns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <tns:result></tns:result>
</tns:ArrayOperationsBusinessProcessResponse>
</bpel:literal>
   </bpel:from>
   <bpel:to variable="output" part="payload"></bpel:to>
  </bpel:copy>
  <bpel:copy>
   <bpel:from>
                    <![CDATA[concat('Hello ', $input.payload/tns:input, '! How are you?')]]>
  </bpel:from>
  <bpel:to part="payload" variable="output">
   <bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0"><![CDATA[tns:result]]></bpel:query>
  </bpel:to>
 </bpel:copy>
</bpel:assign>

To invoke it, I used soapUI and the following input message:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:arr="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
   <soapenv:Header/>
   <soapenv:Body>
      <arr:ArrayOperationsBusinessProcessRequest>
         <arr:input>Łukasz</arr:input>
      </arr:ArrayOperationsBusinessProcessRequest>
   </soapenv:Body>
</soapenv:Envelope>

The result was:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <ArrayOperationsBusinessProcessResponse xmlns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
         <tns:result xmlns:tns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">Hello Łukasz! How are you?</tns:result>
      </ArrayOperationsBusinessProcessResponse>
   </soapenv:Body>
</soapenv:Envelope>

Adding multiple input elements

OK, I had it working, now it was time to introduce the input array.

All what was required was to add maxOccurs="unbounded" to the WSDL file just like this:

<element name="input" maxOccurs="unbounded" type="string"/>

Now, I was able to invoke it this way:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:arr="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
   <soapenv:Header/>
   <soapenv:Body>
      <arr:ArrayOperationsBusinessProcessRequest>
         <arr:input>Łukasz</arr:input>
         <arr:input>Jerzy</arr:input>
         <arr:input>Izydor</arr:input>
         <arr:input>Budnik</arr:input>
      </arr:ArrayOperationsBusinessProcessRequest>
   </soapenv:Body>
</soapenv:Envelope>

But for the above input, the WS-BPEL returned the first input value (Łukasz).

Iterating over an array

When you iterate over an array in WS-BPEL keep in mind the following:

  • indexes start with 1!!
  • as a consequence to the above, the end condition is <= (less or equal)
  • when you index an element in an array, cast the counter value to number using number() function
  • don't forget to increment the counter otherwise you will end up in infinite loop :)

The updated code was:

<bpel:while name="While">
 <bpel:condition><![CDATA[$counter <= count($input.payload/tns:input)]]></bpel:condition>
 <bpel:sequence>
  <bpel:assign validate="no" name="Assign Output">
   <bpel:copy>
    <bpel:from>
                <![CDATA[concat($output.payload/tns:result, ' ', $input.payload/tns:input[number($counter)])]]>
    </bpel:from>
    <bpel:to part="payload" variable="output">
     <bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0"><![CDATA[tns:result]]></bpel:query>
    </bpel:to>
   </bpel:copy>
  </bpel:assign>
 </bpel:sequence>
</bpel:while>
<bpel:assign validate="no" name="Assign">
 <bpel:copy>
  <bpel:from><![CDATA[concat('Hello', $output.payload/tns:result, '! How are you?')]]></bpel:from>
  <bpel:to part="payload" variable="output">
   <bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0"><![CDATA[tns:result]]></bpel:query>
  </bpel:to>
 </bpel:copy>
</bpel:assign>M

After deployment, for the above SOAP input all names were returned.

Creating arrays

OK. So here comes a really difficult part. Iterating over an array is a piece of cake. Creating arrays is much harder. Why? Because WS-BPEL spec does not say anything about instantiating arrays.

There are two things that can be done in order to create arrays:

  • use XSLT transformations
  • use vendor-specific extensions and XSLT functions

I will show you both.

Before I started creating arrays I added two new elements to my response for storing odd and even input words:

<element name="oddInput" maxOccurs="unbounded" type="string"/>
<element name="evenInput" maxOccurs="unbounded" type="string"/>

Manipulating arrays using XSLT transformations

For this task I used WS-BPEL function called bpel:doXslTransform. As a first argument in takes an XSTL stylesheet file, the second on is the XML input, the following arguments are XSLT params in name-value pairs.

So first, the XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
 <xsl:output method="xml"/>
 <xsl:template match="/">
  <!-- <root /> element prevents org.w3c.dom.DOMException: HIERARCHY_REQUEST_ERR -->
  <root><xsl:apply-templates /></root>
 </xsl:template>
 <xsl:template
  match="*[local-name() = 'ArrayOperationsBusinessProcessRequest' and namespace-uri() = 'http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations']">
   <xsl:apply-templates select="*[local-name() = 'input' and namespace-uri() = 'http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations' and position() mod 2 = 0]" />
 </xsl:template>

 <xsl:template match="@*|node()">
  <xsl:copy>
   <xsl:apply-templates select="@*|node()"/>
  </xsl:copy>
 </xsl:template>
</xsl:stylesheet>

And the WS-BPEL invoking the XSLT:

<bpel:assign validate="no" name="Do XSLT Transform">
 <bpel:copy>
  <bpel:from>bpel:doXslTransform("ArrayCopy.xslt", $input.payload)
  </bpel:from>
  <bpel:to part="payload" variable="output">
   <bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0">
                <![CDATA[tns:evenInput]]>
   </bpel:query>
  </bpel:to>
 </bpel:copy>
</bpel:assign>

No that bad. I mean, the XSLT is no that complicated, but it's not what you would like to do every time you need to create an array :)

Manipulating arrays using Apache ODE specific functions

OK. So Apache ODE provides ode namespace which contains many, many useful functions (I encourage you to check it out). Among those functions is function called: ode:insert-as-last-into. It expects two arguments, the first one is a destination array, the second one is an element to be inserted.

The usage of this function is straig forward. Using XSLT I copied even words, below I copy odd words:

<bpel:while name="While">
 <bpel:condition><![CDATA[$counter <= count($input.payload/tns:input)]]></bpel:condition>
 <bpel:sequence>
  <bpel:assign validate="no" name="Assign Output">
   <bpel:copy>
    <bpel:from>
                <![CDATA[concat($output.payload/tns:result, ' ', $input.payload/tns:input[number($counter)])]]>
    </bpel:from>
    <bpel:to part="payload" variable="output">
     <bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0"><![CDATA[tns:result]]></bpel:query>
    </bpel:to>
   </bpel:copy>

  </bpel:assign>
  <bpel:if name="If">
   <bpel:condition><![CDATA[number($counter) mod 2 = 1]]></bpel:condition>
   <bpel:assign validate="no" name="Assign Odd">
    <bpel:copy>
     <bpel:from>
                        <![CDATA[ode:insert-as-last-into($output.payload/tns:oddInput, $input.payload/tns:input[number($counter)])]]>
     </bpel:from>
     <bpel:to part="payload" variable="output">
      <bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0">
                            <![CDATA[tns:oddInput]]>
      </bpel:query>
     </bpel:to>
    </bpel:copy>
   </bpel:assign>
  </bpel:if>
 </bpel:sequence>
</bpel:while>

And that's all.

Summary

Using the soapUI, for the given input request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:arr="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
   <soapenv:Header/>
   <soapenv:Body>
      <arr:ArrayOperationsBusinessProcessRequest>
         <arr:input>Łukasz</arr:input>
         <arr:input>Jerzy</arr:input>
         <arr:input>Izydor</arr:input>
         <arr:input>Budnik</arr:input>
      </arr:ArrayOperationsBusinessProcessRequest>
   </soapenv:Body>
</soapenv:Envelope>

the WS-BPEL response was:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
   <soapenv:Body>
      <ArrayOperationsBusinessProcessResponse xmlns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
         <tns:result xmlns:tns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">Hello Łukasz Jerzy Izydor Budnik! How are you?</tns:result>
         <tns:oddInput xmlns:tns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
            <input>Łukasz</input>
            <input>Izydor</input>
         </tns:oddInput>
         <tns:evenInput xmlns:tns="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">
            <input xmlns:arr="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">Jerzy</input>
            <input xmlns:arr="http://jee-bpel-soa.blogspot.com/ws-bpel/array-operations">Budnik</input>
         </tns:evenInput>
      </ArrayOperationsBusinessProcessResponse>
   </soapenv:Body>
</soapenv:Envelope>

So it worked!

Cheers!
Łukasz

8 comments:

Mihnea said...

Hello!

I am currently working on a school project involving web services composition using bpel. I'm having a hard time finding documentation so I'm in a dead end right now.
I have several web services which i need to invoke.
A method in one of them is returning an Integer ArrayList which needs to be passed on to another method from another web service.
In BPEL I have an invoke action on the first method which returns a [0..*] int variable which is passed on to the invoke action for the other method as it's input variable through an assign action. This variable is also a [0..*] int.
The first invoke action returns a correct array 1,4,5 but the assign action passes only the first element of the array to the next invoke action.
I would sure appreciate any help. Thanks!

Łukasz said...

Hi Mihnea,

When you query the element using /person/address it always will return you only the first address and not the whole collection.

On a several occasions I had such problems, but I managed to solve them by changing the Web Service to create a parent element for (in this case) collection of address elements. In this case it could be:

/person/addresses

and it would store address 1, address 2, and so on:

/person/addresses/address[0]
/person/addresses/address[1]

But this solution is only available when you have control over the external Web Service.

hope this helps,
Łukasz

Mihnea said...

Hi Lukasz! Thanks for the quick answer. Well my methods in my web services look like this:

public List method1(@WebParam(name = "arg0") int id)
{...}
public List method2(@WebParam(name = "arg0") List listOfIds)
{...}

My BPEL assign between the 2 invocations of the web services looks like this:

<bpel:assign validate="no" name="Assign2">
<bpel:copy>
<bpel:from part="parameters" variable="Web1PLResponse">
<bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0"><![CDATA[return[0]]]></bpel:query>
</bpel:from>
<bpel:to part="parameters" variable="Web2PLRequest1">
<bpel:query queryLanguage="urn:oasis:names:tc:wsbpel:2.0:sublang:xpath1.0"><![CDATA[arg0]]></bpel:query>
</bpel:to>
</bpel:copy>
</bpel:assign>

And this are the variables type declarations:

<xs:complexType name="Web1PLResponse">
<xs:sequence>
<xs:element maxOccurs="unbounded" name="return" type="xs:int"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="Web2PLRequest1">
<xs:sequence>
<xs:element maxOccurs="unbounded" name="arg0" type="xs:int"/>
</xs:sequence>
</xs:complexType>

Are you saying I should modify the web methods? How exactly?

Thanks!

Łukasz said...

Hi Mihnea,

Arrays are always a problem in WS-BPEL. Whenever I have to deal with arrays I try (if I can of course) to modify participating Web Services. I wrote once a WS-BPEL process for a warehouse company. There was a method that returned list of lookup results:

List findProducts(Order order);

I changed that to:

LookupResults findProducts(Order order);

and LookupResults was:

public class LookupResults {
private List lookupResult;
/* getters and setters here */
}

This way in WS-BPEL I was able to access all lookup results by accessing the parent "lookupResults" element and this way I was able to pass it to booking service as an argument.

Does this technique can be applied in your case? If so I would go into this direction.

hope this helps,
Łukasz

Mihnea said...

I tried your idea and IT WORKED!!! Thank you, Łukasz!

Mihnea said...

Hi Łukasz! Sorry to bother you again.
I need to generate my bpel process programmatically (generate the .bpel file from a Java program). I'm currently using BPEL Designer plugin in Eclipse to create my processes, but now i need to create them "by hand", write a program to generate the bpel process file. I'm having a hard time implementing the code. I'm trying to use BPELFactory and BPELWriter classes, but I keep running into all sort of problems (including resolving the necessary imports). If you did this before could you help me with some examples, references, ideas.

Thank you, Mihnea!

Łukasz said...

Hi Mihnea,

I'm happy it worked for you.

When it comes to generating WS-BPEL files I must say I never used any high-level API. I write WS-BPEL processes by hand or use Eclipse BPEL Designer.

If you find something interesting and useful drop me an email or a leave a comment.

If you just need to automatically generate <invoke /> activities, why don't you just create an XML template and change the activities on the fly? (but I don't know what you are aiming to do...)

cheers,
Łukasz

Mihnea said...

Hi Lukasz,

Could you detail some more please.
Are you using XQuery to change BPEL activities on the fly? Are there any examples available because I can't find much.

Thanks