суббота, 23 апреля 2011 г.

Array Operations in WS-BPEL

Array Operations in WS-BPEL

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

Комментариев нет:

Отправить комментарий