Friday, March 30, 2012

Base64Encode and Base64Decode in BPEL

Introduction

When working with files or messages which are not linked to an XML schema (for example opaque files when using the FileAdapter), it is often a technical requirement to perform a Base64Encoding or Base64Decoding on the message. There is no ready to use XPath expression available to perform this action, however SOA Suite 11g provides Java classes to perform encoding and decoding functionality. In BPEL these can be used in a Java embedding activity. 

In this entry I will provide 2 working example BPEL processes ready for use to perform Base64Encoding anbd decoding. Also I will give some tips on Java embedding in BPEL. I will focus on BPEL 2.0.

An alternative is to use a custom XPath function to do the encoding/decoding. I have not looked into this yet (time constraints). I can imagine BPEL development using such a custom function will go faster since you don't have to invoke a separate process to perform the action. Traceability is however something to mind here.

Implementation

To implement Java embedding in BPEL there are some things you need to keep in mind. Of course the first is; can I use Oracle supplied libraries or do I need to implement external ones. It is preferred to use Oracle supplied libraries because of several reasons;

- Oracle provides support for them
- You can avoid conflicts with libraries already present
- Your project has a smaller footprint on the server and you don't need to do additional configuration to extend your process with additional libraries

Make the required classes available in the classpath of the BPEL project.


If you decide to use external JAR's, you can do this by adding a JAR file to SCA-INF/lib directory of the project and under Project properties (right click the project) specifying the JAR in 'Libraries and classpath'. Also under Deployment click edit and specify that the JAR which is added to the project is deployed as part of the SAR. See http://docs.oracle.com/cd/E15586_01/integration.1111/e10224/bp_java.htm for more information. In this example I will use Oracle supplied libraries so I will not describe this topic further here/now.

Import the classes in BPEL


Import the required classes in BPEL by using for example the following tag to import the XMLElement class

<import location="oracle.xml.parser.v2.XMLElement" importType="http://schemas.oracle.com/bpel/extension/java"/>


Plenty of examples on the exact syntax of the import statement can be found; http://vaibhav-ambavale.blogspot.com/2011/03/java-embedding-in-bpel-20.html. You can also use tricks like; <bpelx:exec import="java.lang.*"/> to make life easier so you don't have to specify the entire package name  of commonly used classes in your embedding activity.

Create the Java embedding activity


There are some things to mind when using the Java embedding activity;
- it does not provide auto-completion and syntax checking (development is hard! only use short pieces of easy debuggable code)
- you have to use the full package name of the class unless you import the class as shown above
- you have to initialize the variables you are going to assign to, else the setVariableData method will throw a selectionFailure exception
- using the oracle.xml.parser.v2.XMLElement class makes it easy to work with variables in the BPEL process (see sample below)

I've used the default synchronous BPEL 2.0 process template in the wizard as a start and have not altered the message formats (string in, string out). This is reflected in the used Java code. Mind that using these code samples, you need to add the import location statement in the BPEL process as shown before. The encoding and decoding process have a similar structure; first initialize the output, then a Java embedding activity which is available under the Oracle Extensions as can be seen in the below screenshot.


Base64Encode



addAuditTrailEntry("Base64encode started");  
try {  
XMLElement input = (XMLElement) getVariableData("inputVariable","payload","client:input");  
java.lang.String input_str = input.getTextContent();  
addAuditTrailEntry("Base64encode input_str="+input_str);  
oracle.soa.common.util.Base64Encoder encoder = new oracle.soa.common.util.Base64Encoder();      
java.lang.String encoded = null;     
encoded = encoder.encode(input_str);  
addAuditTrailEntry("Base64encode encoded="+encoded);  
setVariableData("outputVariable","payload","client:result",encoded);  
} catch (Exception e) {  
  addAuditTrailEntry("Base64encode Exception: "+e.getMessage());  
}  
addAuditTrailEntry("Base64encode ended");

Base64Decode



addAuditTrailEntry("Base64decode started");   
try {   
XMLElement input = (XMLElement) getVariableData("inputVariable","payload","client:input");   
String input_str = input.getTextContent();   
addAuditTrailEntry("Base64decode input_str="+input_str);   
oracle.soa.common.util.Base64Decoder decoder = new oracle.soa.common.util.Base64Decoder();       
String decoded = null;      
decoded = decoder.decode(input_str);   
addAuditTrailEntry("Base64decode decoded="+decoded);   
setVariableData("outputVariable","payload","client:result",decoded);   
} catch (Exception e) {   
  addAuditTrailEntry("Base64decode Exception: "+e.getMessage());   
}   
addAuditTrailEntry("Base64decode ended");

You can download the complete sample with the encode and decode processes ready for deployment here; http://dl.dropbox.com/u/6693935/blog/Base64Utils.zip

Tuesday, March 27, 2012

File processing using a Spring component

Introduction

The Oracle SOA Suite 11g FileAdapter provides (among other things) functionality to read a variety of text file formats such as CSV and transform them into XML. This makes it easier to use them in for example BPEL. Usually the FileAdapter provides enough functionality to correctly parse files. Sometimes however, this is not possible. An example of such a file;

h1;h2;h3;h4
A;B;C;D
E;F;G;H
Total: 2

h1 to h4 are header fields. The second and third line are the records and the last line indicates how many records are present in the file.

The last line cannot be excluded from parsing by the FileAdapter and will lead to errors. As a workaround and to provide some flexibility in file parsing, the following solution can be implemented. Keep in mind that this solution will cause problems for large files since the file will in memory be converted to XML.

Read the file as opaque using the FileAdapter. Create a Java class which has a public method with a byte array as input parameter and some parsing parameters. Wrap the Java class as a Spring component. Then you can use the component in a BPEL process as JDeveloper will generate XSD's/WSDL's for the Spring component for you.

Implementation

FileAdapter

In BPEL define a file adapter and select not to use a schema;


Java class parsefile

A Java class does the parsing. You can of course expand this example. Currently empty lines are also processed as entries, which might not be what you want. This class is also specific for the example given in the introduction (but is flexible enough for related file formats). I have chosen to return an ArrayList of ArrayLists. The first (outer) ArrayList contains the records of the file and the inner ArrayList contains the items in the record separated on a separator regular expression. The header lines can be ignored and you can specify to skip lines starting with a specific string. If you want to be able to skip multiple strings (in order to make a selection in the file of lines to process), you can change the type of the last parameter of the parse function to ArrayList and alter the code to loop over that.

Create the following Java class;


package nl.smeets.myfilereader;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

import java.util.ArrayList;

public class parsefile {
    public parsefile() {
        super();
    }

    public ArrayList<ArrayList<String>> parse(byte[] bytes_in,
                                              String separator,
                                              Integer ignorefirstlines,
                                              String ignoreStartsWith) {
        ArrayList<ArrayList<String>> l_retval =
            new ArrayList<ArrayList<String>>();
        ByteArrayInputStream ba_in = new ByteArrayInputStream(bytes_in);
        BufferedReader br_in =
            new BufferedReader(new InputStreamReader(ba_in));
        Integer linecounter = 0;
        String line = null;
        try {
            while ((line = br_in.readLine()) != null) {
                if (!line.startsWith(ignoreStartsWith) &&
                    linecounter >= ignorefirstlines) {
                    l_retval.add(parseLine(line, separator));
                }
                linecounter++;
            }
        } catch (FileNotFoundException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            //Close the BufferedReader
            try {
                if (br_in != null)
                    br_in.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return l_retval;
    }

    ArrayList<String> parseLine(String input, String separator) {
        ArrayList<String> retval = new ArrayList<String>();
        String[] splitVals = input.split(separator);
        for (String addVal : splitVals) {
            retval.add(addVal);
        }
        return retval;
    }

    public static void main(String[] args) {
        //test; skips first line and all lines starting with Total. uses ; as separator
        //parsefile parsefile = new parsefile();
        //System.out.println(parsefile.parse("h1;h2;h3;h4\ne1;f1;g1;h1\ne2;f2;g2;h2\nTotal: 1".getBytes(), ";", 1, "Total"));
    }
}


Extract interface

Right-click the Java file, Refactor, Extract interface


Spring bean

Use the wizard to create a Spring bean and define it as follows;


<?xml version="1.0" encoding="windows-1252" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:lang="http://www.springframework.org/schema/lang"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:sca="http://xmlns.oracle.com/weblogic/weblogic-sca"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.5.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/tool http://www.springframework.org/schema/tool/spring-tool-2.5.xsd http://xmlns.oracle.com/weblogic/weblogic-sca META-INF/weblogic-sca.xsd">
 
<bean name="ParseFileBean" class="nl.smeets.myfilereader.parsefile"/>
<sca:service name="ParseFileBeanService" target="ParseFileBean" type="nl.smeets.myfilereader.Iparsefile"/>
</beans>


First make sure the Java interface and class are compiled to avoid errors such as the one below;



Now you can use the composite editor to expose the Spring bean or use it in a BPEL process.


BPEL

Use BPEL to bring it all together. In BPEL you can assign the parameters of the parsing Java method. In this example I've hardcoded them in the process but of course you can also use BPEL preferences (described in a previous post; http://javaoraclesoa.blogspot.com/2012/02/changing-properties-of-bpel-process-at.html) which can be defined in the composite.xml file and modified at runtime so if the format of the file changes, you don't have to redeploy the BPEL process. Don't forget to update the configuration plan supplied to reflect your local environment. *.txt files from (logical path) READ_FILE_DIR are read and xml files are written as output to logical path WRITE_FILE_DIR.


Input;

h1;h2;h3;h4
A;B;C;D
E;F;G;H
Total: 2

Output;

<?xml version="1.0" encoding="UTF-8" ?>
<parseResponse xmlns="http://myfilereader.smeets.nl/">
   <return xmlns:ns0="http://myfilereader.smeets.nl/types" xmlns="">
      <item>A</item>
      <item>B</item>
      <item>C</item>
      <item>D</item>
   </return>
   <return xmlns:ns0="http://myfilereader.smeets.nl/types" xmlns="">
      <item>E</item>
      <item>F</item>
      <item>G</item>
      <item>H</item>
   </return>
</parseResponse>



You can download the entire example here; https://www.dropbox.com/s/ytgwjz7zjlvscmj/DemoReadFile.zip?dl=0

Sunday, March 25, 2012

Oracle SOA Suite Cluster part 2

Introduction

This is the second part in which I describe the configuration of a Weblogic Cluster. Quick and easy. This does not conform to the Oracle Enterprise Deployment Guide (http://docs.oracle.com/cd/E16764_01/core.1111/e12036/toc.htm) and should thus not be used for enterprise installations. I for example don't install Oracle HTTP server.

This part consists of installation of
- Load balancer
- RCU (Repository creation utility)
- Weblogic server and SOA Suite
- Configure the Nodemanager
- Transport the managed domain to the other machine
- Start the cluster

Load balancer

By using the load balancer URL when referring to other processes, I can do process call distribution. The idea of this setup is illustrated in part 1.

For the load balancer I used pound which can be downloaded at; http://pkgs.repoforge.org/pound/

I downloaded it and installed the rpm using (as root); rpm -i pound-2.4.3-1.el5.rf.x86_64.rpm

I used the below configuration. The host running the load balancer (and database) is 192.168.1.160. The hosts which will run the Weblogic servers are 192.168.1.161 and 192.168.1.162.

User        "nobody"
Group       "nobody"

## Main listening ports
ListenHTTP
    Address 192.168.1.160
    Port    8001
End
Service
        BackEnd
                Address 192.168.1.161
                Port    8001
        End
        BackEnd
                Address 192.168.1.162
                Port    8001
        End
        Session
                Type Cookie
                ID   "JSESSIONID"
                TTL  300
        End
End

RCU

As shown in the image below, I installed the required schema's. No errors this time with the Oracle Enterprise Scheduler schema such as when I used the Database XE 11r2 installation thus it is a good idea to use the Enterprise Edition database.


Weblogic server and SOA Suite

The below is based on the Oracle documentation; http://docs.oracle.com/cd/E23943_01/web.1111/e13709/setup.htm#CLUST431

I choose 2 different virtual machines to install the Weblogic servers on. I did this to be able to identify the challenges which need to be overcome when the server instances are running on different machines (and with different IP addresses). It is of course also possible to install the Weblogic cluster on a single machine, but that would not be sufficient to identify points of attention when developing against a production clustered environment at customers.

First a few basic things;
If by accident, you run the installer of the Weblogic server or SOA Suite as a different user, remove the installation, reinstall and get the below exception;  you can do chmod -R 777 /tmp to fix it (https://forums.oracle.com/forums/thread.jspa?threadID=1085777)

weblogic.security.SecurityInitializationException: The dynamic loading of the OPSS java security policy provider class oracle.security.jps.internal.policystore.JavaPolicyProvider failed due to problem inside OPSS java security policy provider. Exception was thrown when loading or setting the JPSS policy provider. Enable the debug flag -Djava.security.debug=jpspolicy to get more information. Error message: JPS-01538: The default policy provider was not found. at weblogic.security.service.CommonSecurityServiceManagerDelegateImpl.loadOPSSPolicy(CommonSecurityServiceManagerDelegateImpl.java:1394)

Configure the nodemanager

To make it possible to use the nodemanager, you need to configure SSL for the server, keystore, truststore etc. Look at; http://wls4mscratch.wordpress.com/2010/05/31/configure-node-manager-over-ssl-custom-certifcates/.

I started as followed;

Host 1;

keytool -genkey -alias server01 -keyalg RSA -keysize 1024 -dname "CN=Puneeth, OU=Oracle, O=BEA, L=Denver, ST=Colorado, C=US" -keypass password -keystore identity.jks -storepass password
keytool -selfcert -v -alias server01 -keypass password -keystore identity.jks -storepass password -storetype jks
keytool -export -v -alias server01 -file rootCA01.der -keystore identity.jks -storepass password
keytool -import -v -trustcacerts -alias server01 -file rootCA01.der -keystore trust.jks -storepass password

Host 2;

keytool -genkey -alias server02 -keyalg RSA -keysize 1024 -dname "CN=Puneeth, OU=Oracle, O=BEA, L=Denver, ST=Colorado, C=US" -keypass password -keystore identity.jks -storepass password
keytool -selfcert -v -alias server02 -keypass password -keystore identity.jks -storepass password -storetype jks
keytool -export -v -alias server02 -file rootCA02.der -keystore identity.jks -storepass password
keytool -import -v -trustcacerts -alias server02 -file rootCA02.der -keystore trust.jks -storepass password

Copy rootCA01.der and rootCA02.der to both servers. Then on server 1 do;
keytool -import -v -trustcacerts -alias server02 -file rootCA02.der -keystore trust.jks -storepass password
and on server 2 do;
keytool -import -v -trustcacerts -alias server01 -file rootCA01.der -keystore trust.jks -storepass password

I also did the changes to nodemanager.properties, setDomainEnv, startNodemanager and the Weblogic server SSL certificate configuration as described at the previously mentioned location. This SSL configuration will prevent the following error from occurring when the nodemanager is started and you try to access it from the Weblogic console.

<Mar 25, 2012 1:31:34 PM CEST> <Warning> <Security> <BEA-090482> <BAD_CERTIFICATE alert was received from localhost.localdomain - 127.0.0.1. Check the peer to determine why it rejected the certificate chain (trusted CA configuration, hostname verification). SSL debug tracing may be required to determine the exact reason the certificate was rejected.>
<Mar 25, 2012 1:31:34 PM> <WARNING> <Uncaught exception in server handlerjavax.net.ssl.SSLKeyException: [Security:090482]BAD_CERTIFICATE alert was received from localhost.localdomain - 127.0.0.1. Check the peer to determine why it rejected the certificate chain (trusted CA configuration, hostname verification). SSL debug tracing may be required to determine the exact reason the certificate was rejected.>

Transport the domain

Use pack to create a template and transport it to the other machine; http://docs.oracle.com/cd/E23943_01/web.1111/e14144/tasks.htm#WLDPU136

pack.sh is present in several locations;
/u01/app/fmw/Oracle_SOA1/common/bin/pack.sh
/u01/app/fmw/oracle_common/common/bin/pack.sh
/u01/app/fmw/wlserver_10.3/common/bin/pack.sh

I've used the one in the OracleSOA1 folder

On the machine on which you created the domain;
/u01/app/fmw/Oracle_SOA1/common/bin/pack.sh -managed=true -domain=/u01/app/fmw/user_projects/domains/soa_domain -template=misakimw1.jar -template_name="misakimw1"

On the other machine which will be running the managed domain;
/u01/app/fmw/Oracle_SOA1/common/bin/unpack.sh -domain=/u01/app/fmw/user_projects/domains/soa_domain -template=/home/oracle/misakimw1.jar

Starting the cluster

First start the database machine. Pound (the load balancer) is optional at this point and becomes important when starting with experiments on process call load balancing. Then start the nodemanager on both machines. Then start the administration server. From the Weblogic console you can then start both managed domains. This is how it looks in the enterprise manager when it is all started;

When you deploy, you deploy to the admin server. The admin server makes sure the different managed servers get the processes.

I will start experimenting on this setup shortly and compare it to production environments.

Saturday, March 17, 2012

Oracle SOA Suite Cluster part 1


Introduction

Most customers use clustered production environments for the SOA Suite to provide (amongst others) high availability and failover. The clustered environments are often only available in production and not in development or test. For developers, working with clustered environments, provide several challenges such as processes running simultaneously on different machines which can cause timing issues and locks.

In order to be able to identify these challenges and tackle them before they occur in the production environment, I wanted to setup a small cluster environment myself. What I wanted to achieve was that I could use the IP of the load balancer to deploy processes and that deployed processes (composites) could refer to the load balancer instead of to individual instances. In this way when a process calls another process, it could be on another Weblogic server instance.

I was interested in how the Weblogic instances would communicate and what would be synchronized between the instances. I choose to use one database for both Weblogic servers since I’m not much interested in the database functioning. Of course in a production environment, you would use a RAC setup for the database as well.

Below is a simple image of the initial idea. This might change when I learn more about this setup and the options available!


Building such a setup requires installation of
- Server and OS
- Database
- Load balancer
- Weblogic Server
- SOA Suite

Because this is a lot of material to cover, I've split this post up into several parts.

Setup

Server and Operating System

For the servers I've used Oracle Virtualbox. For the operating system I've used Oracle Enterprise Linux (5.8 x86_64). Make sure your swap file is at least 4Gb. Otherwise the installers (amongst others of the application server and database) might start complaining at a later stage. I've used 30Gb dynamically expanding HD images. 30Gb will be more then enough for my setup and since it is dynamically expanding, the images on disk will remain a lot smaller if disk space is not used. For memory I've done the following (my desktop PC only has 12Gb RAM);

- server DB: 3Gb
- server MW1: Adminserver + Managed domain 1 3Gb
- server MW2: Managed domain 2 2Gb

Also I've used DHCP (setup on a local router) to assign static IP's and hostnames based on MAC address. I've encountered several problems during the database installation which might have been related to this choice.

Several other things to keep in mind during the installation of Oracle Enterprise Linux;

Public Yum repository
Several packages are required (for the database installation and the Virtualbox guest additions) which can be downloaded from Oracle's public Yum repository. Oracle has provided usage instructions on http://public-yum.oracle.com.

I've used the following repositories; ol5_u8_base, ol5_UEK_base. After this you can do (as root);

yum install gcc make automake autoconf kernel-headers kernel-devel elfutils-libelf-devel gcc-c++ libstdc++-devel sysstat libaio-devel glibc-devel binutils compat-libstdc++-33 elfutils-libelf-devel gcc gcc-c++ glibc glibc-common libaio libaio-devel libgcc libstdc++

Then install the guest additions. Don't forget to add the oracle user to vboxsf in order to make using de VirtualBox shares easier.

Java installation
I've used a 64 bit JVM installation; http://www.oracle.com/technetwork/java/javase/downloads/jdk-6u31-download-1501634.html and I've added the below lines to /etc/profile

export JAVA_HOME=/usr/java/jdk1.6.0_31
pathmunge $JAVA_HOME/bin

Hosts file
I cloned the above base installation 2 times so I had my 3 servers. One would be used for the database and load balancer and the other two for the Weblogic server installations. 

Add your short hostname at the end of the /etc/hosts file if you use DHCP to get an IP address. 

Database

When using Oracle XE 11g database during a previous installation (11.1.1.6), I encountered several errors during the installation of the Oracle Enterprise Scheduler schema. I didn't save screenshots of this. To avoid future problems and to provide a more production like environment, I decided to use 11gR2 (11.2.0.3.0) Enterprise Edition database. This however requires more actions then installation of an XE database. 

In the below part I describe the actions I had to take after the initial installation to get the database to work quickly. I'm no expert on this topic!

I first installed the database using basic options (no RAC). The installer can generate scripts to update settings required for the database installation on your system. This will not solve all requirement issues, but makes the installation a lot easier.

/etc/profile
I've added the following lines to /etc/profile

export ORACLE_UNQNAME=orcl
export ORACLE_HOME=/u01/app/oracle/product/11.2.0/dbhome_1
export ORACLE_SID=orcl
pathmunge $ORACLE_HOME/bin

Hosts file
My /etc/hosts file looks like this;
127.0.0.1               localhost.localdomain localhost
192.168.1.160       misakidb

chkconfig --levels 345 sendmail off

Startup script
I changed /etc/oratab;
orcl:/u01/app/oracle/product/11.2.0/dbhome_1:Y

I used the following to create a base startup script for the database. XE installations provide this automatically but the Enterprise Edition doesn’t; http://www.oracle-base.com/articles/linux/AutomatingDatabaseStartupAndShutdownOnLinux.php.

For my environment I ended up with the following (I've added the starting of the Enterprise Manager to the script)

#!/bin/sh
# chkconfig: 345 99 10
# description: Oracle auto start-stop script.
#
# Set ORA_HOME to be equivalent to the $ORACLE_HOME
# from which you wish to execute dbstart and dbshut;
#
# Set ORA_OWNER to the user id of the owner of the
# Oracle database in ORA_HOME.

ORA_HOME=/u01/app/oracle/product/11.2.0/dbhome_1
ORA_OWNER=oracle

if [ ! -f $ORA_HOME/bin/dbstart ]
then
    echo "Oracle startup: cannot start"
    exit
fi

case "$1" in
    'start')
        # Start the Oracle databases:
        # The following command assumes that the oracle login
        # will not prompt the user for any values
        su - $ORA_OWNER -c "$ORA_HOME/bin/dbstart $ORA_HOME"
        su - $ORA_OWNER -c "$ORA_HOME/bin/emctl start dbconsole"
        touch /var/lock/subsys/dbora
        ;;
    'stop')
        # Stop the Oracle databases:
        # The following command assumes that the oracle login
        # will not prompt the user for any values
        su - $ORA_OWNER -c "$ORA_HOME/bin/emctl stop dbconsole"
        su - $ORA_OWNER -c "$ORA_HOME/bin/dbshut $ORA_HOME"
        rm -f /var/lock/subsys/dbora
        ;;
esac

Database configuration
I used http://www.oracle-base.com/articles/misc/OracleNetworkConfiguration.php to get a working tnsnames.ora, sqlnet.ora and listener.ora. I ended up with the following;

tnsnames.ora
ORCL.WORLD =
  (DESCRIPTION =
    (ADDRESS = (PROTOCOL = TCP)(HOST = misakidb)(PORT = 1521))
    (CONNECT_DATA =
      (SERVICE_NAME = ORCL.WORLD)
    )
  )

sqlnet.ora
NAMES.DIRECTORY_PATH= (TNSNAMES, ONAMES, HOSTNAME)
NAMES.DEFAULT_DOMAIN = WORLD
ADR_BASE = /u01/app/oracle

listener.ora
LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = misakidb)(PORT = 1521))
    )
  )

SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = ORCL.WORLD)
      (ORACLE_HOME = /u01/app/oracle/product/11.2.0/dbhome_1)
      (SID_NAME = ORCL)
    )
  )

ADR_BASE_LISTENER = /u01/app/oracle

Saturday, March 3, 2012

Loops in BPEL 1.1 and 2.0

Introduction

When programming in BPEL, an often used construction is the following;
- the input of a process contains a collection of elements
- the elements need to be processed one at a time

There are several solutions to implement this. A couple of these solutions will be discussed here.

SOA Suite 10g and SOA Suite 11g have some differences in usage of extension functions such as the one used for transformations. SOA Suite 11g (of course) provides several improvements over 10g. Also BPEL 1.1 and BPEL 2.0 standards provide different activities for handling loops.

An alternative for using loop constructions in BPEL when XML needs to be send to the database is by using XML object types. See http://javaoraclesoa.blogspot.nl/2013/03/using-plsql-object-types-to-get-nested.html for more information.

Example processes

The complete BPEL 1.1 and BPEL 2.0 example (including XSD and XSLT) can be downloaded here; http://dl.dropbox.com/u/6693935/blog/TestXSLT.zip

The example used will use the following input and output schema definitions;


<?xml version="1.0" encoding="UTF-8"?>
<schema attributeFormDefault="unqualified" elementFormDefault="qualified"
        targetNamespace="http://test.ms/itemcollections"
        xmlns="http://www.w3.org/2001/XMLSchema"
        xmlns:me="http://test.ms/itemcollections">

 <element name="item" type="me:itemType"/>
<complexType name="itemType">
  <sequence>
   <element name="name" type="string"/>
   <element name="value" type="string"/>
  </sequence>
 </complexType>

 <element name="itemsCollection" type="me:itemsCollectionType"/>
 <complexType name="itemsCollectionType">
  <sequence>
   <element ref="me:item" minOccurs="0" maxOccurs="unbounded"/>
  </sequence>
 </complexType>

<element name="itemCollectionArray" type="me:itemCollectionArrayType"/>
 <complexType name="itemCollectionArrayType">
  <sequence>
   <element ref="me:itemsCollection" minOccurs="0" maxOccurs="unbounded"/>
  </sequence>
 </complexType>

 <element name="simpleString" type="me:simpleStringType"/>
 <complexType name="simpleStringType">
  <sequence>
   <element name="value" type="string"/>
  </sequence>
 </complexType>

<element name="simpleNumber" type="me:simpleNumberType"/>
 <complexType name="simpleNumberType">
  <sequence>
   <element name="value" type="decimal"/>
  </sequence>
 </complexType>

</schema>

This schema has been created to illustrate how collections can be handled.

It is advisable to define your element and type definitions inside a separate XSD. This promotes re-use and maintainability. The XSD can for example be put in the MDS (as described in http://javaoraclesoa.blogspot.com/2012/02/using-mds.html). It is also very useful to define separate types for every element used. This makes defining variables containing only a part of the message easier.

The workspace contains a BPEL 1.1 and a BPEL 2.0 process which are both exposed as webservices. The BPEL 1.1 process uses a While activity together with Assign activities to process a collection. The BPEL 2.0 process uses the For Each activity and a transformation to achieve the same.

BPEL 1.1 (SOA Suite 10g + 11g)

BPEL 1.1 contains the While activity and the FlowN activity which can both be used to process collections. The While activity allows to loop over a set of activities until a certain condition is met. The FlowN activity provides the option for parallel execution and an index variable to indicate the branche which is processed. FlowN has been described in; http://javaoraclesoa.blogspot.com/2012/02/parallel-execution-and-variable-scoping.html. This part will focus on the While activity. The part describing how to get a parameter inside an XSLT transformation or use it inside an assign activity, can of course also be applied for FlowN.

The below picture shows my BPEL 1.1 sample process. It transforms the input collection on a per item basis to a local variable (which would allow processing of the individual item). Then it adds the result to the output variable.


The While condition

You can create a variable inside the scope of the while activity and increase this. The condition can be set to check whether the counter is smaller then or equal to the count of elements in the message that need processing.

Getting to your element; The element is an XSLT element

There are several ways to obtain the element you want to process. Depending on the circumstances, some options might not be available.

The easiest option is creating a variable of the type of one of the items of the collection. Then use the assign activity to assign the element.

The empty activity in the process symbolizes actions on the l_item variable. This can for example be a transformation followed by a call to a DbAdapter. The result can be transformed to a different format which can then be used to generate the output. The example is meant as a bare-bones illustration of the functionality.

Things to mind;
- it is advisable to create a scope inside the While activity and use local variables inside this scope
- in the Assign activity where you assign the item to be processed, cast the local counter variable to a number like in the following example (else the condition which selects which item to be processed, doesn't work correctly);

bpws:getVariableData('inputVariable','payload','/ns1:itemsCollection/ns1:item')[number(bpws:getVariableData('l_counter'))]

- Use the Assign append rule type and append an item to the collection for creating output.

            <assign name="AssignOutputItemProcessToOutputProcess">
              <bpelx:append>
                <bpelx:from expression="bpws:getVariableData('l_item')"/>
                <bpelx:to variable="outputVariable" part="payload"
                          query="/ns1:itemsCollection"/>
              </bpelx:append>
            </assign>

- use >= and not => in the While activity condition!

Getting to your element; The element is not an XSLT element type. BPEL 1.1 SOA Suite 10g.

BPEL 1.1 in 10g uses a different transformation function as BPEL 1.1 in 11g. The transformation activity is an Oracle extension and not part of the BPEL specification (actually it's implemented as an Assign which is part of the specification and made easier to use with a wizard). The below part is for using the BPEL 10g version.

Sometimes the element of the collection is not available as a type. This makes processing less straightforward, since a variable of the item can not readily be created in BPEL. A solution for this is using an XSLT transformation with a parameter. It is important to think about to what type you are going to transform to, since the type of the element is not available. You can create a custom element type for this., for example the name/value pair items (as specified in the introduction), can be used for that.

Using a parameter inside an XSLT transformation can for example be found on;  http://rigterink.blogspot.com/2009/09/passing-xml-as-parameter-to-xslt.html

The parameter can be assigned the counter value of the While loop and be passed to the XSLT transformation. This XSLT parameter can then be used to select the correct element of the source XML to be used.

Selecting the correct element from the source XML inside an XSLT transformation, will be illustrated in the BPEL 2.0 part.

BPEL 2.0

BPEL 2.0 is only available in SOA Suite 11g and not in 10g. It has several new features which make it more easy to deal with loops. Also the bpws:getVariableData does not need to be used anymore; the syntax for accessing variables has been simplified. A dot notation can be used, for example an assign activity;

          <assign name="AssignCounter">
            <copy>
              <from>$ForEach1Counter</from>
              <to>$l_counter/ns1:value</to>
            </copy>
          </assign>

For Each activity

The For Each activity can be compared with the While activity, however it is more specific.
- It provides a scope for a loop (in a While activity you have to create the scope yourself)
- It provides a start and endpoint for the counter variable and the counter variable can be created in the For Each activity wizard.
- It provides an option for an additional completion expression
- It provides the option of parallel execution of the individual branches (similar to the FlowN activity in BPEL 1.1)
- it provides a variable which can be used in Assign activities, however not in XSLT transformations without some additions!


The below picture shows the BPEL 2.0 process using the For Each activity. Noticable is that I have to use less activities to achieve the same as with a While activity in BPEL 1.1.



XSLT transformations allow more input variables

The function which is used in the transformation for SOA Suite 10g BPEL 1.1 is different from the transformation function used in SOA Suite 11g BPEL 1.1 and BPEL 2.0. In SOA Suite 11g it is easier to add multiple input parameters to a single transformation in the wizard and the input parameters do not need to confer to a specific schema.

The source types for the transformation all need to be simple or complex types. it is thus easiest to use a complex type. A complex types can be selected when using the wizard to define the type of BPEL variable and by choosing one from the 'Element' or 'Message type' category. you can see examples in the schema shown at the start of this post; simpleNumberType and simpleStringType. These types are complex type wrappers for the simple types string and decimal. They allow the simple types to be used as complex types inside transformations.

Using the counter variable of a For Each activity inside an XSLT transformation, can be done as followed;
- assign the For Each counter variable to a complextype (for example the simpleNumberType)
- use the variable inside a for-each XSLT construction with a selection on the passed simpleNumberType by using the position() function.

The transformation without namespaces used, is the following (see the example ZIP for the complete XSLT);

  <xsl:param name="l_counter"/>
  <xsl:template match="/">
    <xsl:for-each select="/ns1:itemsCollection/ns1:item[position()=$l_counter/ns1:simpleNumber/ns1:value]">
      <ns1:item>
        <ns1:name>
          <xsl:value-of select="ns1:name"/>
        </ns1:name>
        <ns1:value>
          <xsl:value-of select="ns1:value"/>
        </ns1:value>
      </ns1:item>
    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>


Note on Assigns

Changing the rule type for the assign is in SOA Suite 11g in BPEL 1.1 different then in BPEL 2.0.

BPEL 1.1
Select the rule type from the drop-down box

BPEL 2.0
Right click the From / To rule, Change rule type