zondag 19 mei 2013

How to deal with services that don't support concurrency? Offer requests one at a time.

When developing service orchestrations using Oracle SOA Suite, an often encountered problem is dealing with unreliable services. This can be services which cannot handle multiple simultaneous requests (don't support concurrency) or don't have an 100% availability (usually due to nightly batches or scheduled maintenance). One way to work with these services is having a good error handling or retry mechanism in place. For example, I've previously described a fault handling mechanism based on using Advanced Queues (AQ); http://javaoraclesoa.blogspot.nl/2012/06/exception-handling-fault-management-and.html. Using this mechanism, you can maintain the order of processing for messages and retry faulted messages. It would however be better if we can avoid faults. In case a service does not support concurrency (because of for example platform limitations or statefulness), messages will have to be offered one at a time.

If the service has a quick response time, you can make a process picking up messages from a AQ, synchronous and thus have only one running process at a time. This has been described at; http://mazanatti.info/index.php?/archives/81-SOA-Suite-11g-truly-singleton-with-AQ-Adapter.html. It's a recommended read.

In this blog post I'll describe a mechanism which can be used if a synchronous solution would not suffice, for example in long running processes. The purpose of this blog post is to illustrate a mechanism and it's components. It should not be used as-is in a production environment but tweaked to business requirements.

Implementation

I'll describe a database based mechanism which consists of several components;
- A database table holding process 'state'. In this example, CREATED, ENQUEUED, RUNNING, DONE
- A DBMS_SCHEDULER job which polls for changes. In my experience this is more stable then using the DbAdapter to do the same.
- A priority AQ to offer messages to BPEL in a specific order and allow loose coupling/flexibility/error handling mechanisms. In my experience this is very reliable.
- A BPEL process consuming AQ messages and calling the service which doesn't support concurrency. There should be only one running instance of this process at a time.


I've created a process state table which holds the process states and provides state history. I've also created a view on this table which only displays the current state. There is a column in the table PROC_NAME. This corresponds to the subscriber used in the BPEL process.

A database job polls for records every minute with state CREATED. If found and no other processes are in state ENQUEUED or RUNNING, a new message is enqueued. I've split the states ENQUEUED and RUNNING to be able to identify which messages have been picked up by the BPEL process and which haven't. There should only be one process in state RUNNING at a time.

I've created a simple HelloWorld BPEL process. This process polls for messages on the AQ. It picks up the message and informs the database that it has picked up a message (set the state to RUNNING). Next I've stubbed calling a service with a wait of one minute. After the period is over, the state is set to DONE. The process looks as followed;



At the end of this post you can download the code. To run the example however, the database needs to have a user TESTUSER with the correct grants to alllow queueing/dequeueing (see supplied script). Also in Weblogic server, there needs to be a JDBC datasource configured and a connection factory (eis/AQ/testuser) defined in the AqAdapter. You can find an example for configuring the DbAdapter at http://kiransaravi.blogspot.nl/2012/08/configuring-dbadapter-its-datasource.html. Configuration for the AqAdapter is very similar.

Running the example

First you need to create the table, trigger, AQ, package, DBMS_SCHEDULER job. This can be done by executing the supplied script.

To start testing the mechanism you can execute the following;

begin
insert into ms_process(proc_name,proc_comment) values('HELLOWORLD','Added new record for test 1');
insert into ms_process(proc_name,proc_comment) values('HELLOWORLD','Added new record for test 2');
insert into ms_process(proc_name,proc_comment) values('HELLOWORLD','Added new record for test 3');
commit;
end;

This will insert 3 records in the process table. These messages will be picked up in order. For implementations in larger applications I recommend using the PROC_SEQ field in the process table to obtain  required information for processing.

After a couple of minutes, you can see the following in the process state table;


As you can see, the messages were created at approximately the same time. The messages are picked up in order of insertion (based on ProcessId). Also as can be seen from the table, when a process is running (the period between state RUNNING and DONE), no other processes are running; there is no overlap in time.

After processing, the process view indicates the latest process state for every process. All processes are done.


In the Enterprise Manager, three processes have been executed and completed.


AQ in a clustered environment

In a clustered environment you have to mind that in an 11.2 database, AQ messages can be picked up twice from the same queue under load. Since this would break the mechanism, I suggest taking the below described workaround.

Bug: 13729601
Added: 20-February-2012
Platform: All
The dequeuer returns the same message in multiple threads in high concurrency environments when Oracle database 11.2 is used. This means that some messages are dequeued more than once. For example, in Oracle SOA Suite, if Service 1 suddenly raises a large number of business events that are subscribed to by Service 2, duplicate instances of Service 2 triggered by the same event may be seen in an intermittent fashion. The same behavior is not observed with a 10.2.0.5 database or in an 11.2 database with event10852 level 16384 set to disable the 11.2 dequeue optimizations.

Workaround: Perform the following steps:

    Log in to the 11.2 database:
    CONNECT /AS SYSDBA

    Specify the following SQL command in SQL*Plus to disable the 11.2 dequeue optimizations:
    SQL> alter system set event='10852 trace name context forever,
    level 16384'scope=spfile;

Considerations

The mechanism described can be used to avoid parallel execution of processes. Even when the processes are long running and synchronous execution is not an option.

Polling

The mechanism contains polling components; the DBMS_SCHEDULER job and the AqAdapter. This has two major drawbacks;
- it will cause load even when the system is idle
- it allows a period between finishing of a process and starting of the next process

You could consider starting the BPEL process actively from the database (thus avoiding polling) by using for example UTL_DBWS (see for example http://orasoa.blogspot.nl/2006/11/calling-bpel-process-with-utldbws.html). This however requires that the URL of the BPEL process is known in the database and that the ACL (Access Control List) is configured correctly. Also error handling should be reconsidered. The overhead of polling is minor. If a delay of 1 minute + default AqAdapter polling frequency is acceptable, a solution based on the described mechanism can be considered. Also, the DBMS_SCHEDULER job polling frequency can be reduced and the AqAdapter polling behavior can be tweaked to reduce the lost time between polls.

Chaining

Ending the process with a polling action -> initiation of the next message is not advisable since it raises several new questions;
- what to do if there are no messages waiting? having a polling mechanism together with this mechanism might break the 'only one process is running at the same time'-rule
- what to do in case of errors -> when the chain is broken

Retiring/activating

I've tried a mechanism which would retire a process at the start and then reactivate it after completion. This would disallow more then one process to be running at the same time. This appeared not to be a solid mechanism. Retiring and activating a process takes time in which new messages could be picked up. Also using the Oracle SOA API during process execution adversely effects performance.

Efficiently determining the current state

I've not tested this solution with large number of processes. I think in that case I should reconsider on how to keep a process history and get to the current state efficiently in a polling run. Most likely I'd use two tables. One for the current state which I would update and another separate table for the history which I would fill with PL/SQL triggers on the current state table.

Download

You can download the BPEL process here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldAQProcState.zip

The databasecode can be downloaded here (you might want to neatify it if for example you like CDM);
https://dl.dropboxusercontent.com/u/6693935/blog/processstate.txt

woensdag 1 mei 2013

Cleaning up unused namespaces in Oracle SOA 11g BPEL processes by using a Python script

Composites are often created and after creating, they are changed/expanded to implement functionality or bugfixes. When adding new partnerlinks and variables, removing them, importing new XSD, removing them, etc it often occurs that there are namespace definitions inside for example BPEL processes which are no longer relevant because they are not used anymore. This can adversely effect performance/memory usage and increases the chance of errors when XSD's are changed, removed or added (such as inconsistent duplicate namespace definitions).

I took this issue as a nice opportunity to learn myself a bit of Python. Python is a popular scripting language (http://www.tiobe.com/index.php/content/paperinfo/tpci/index.html) and used by several software vendors such as Oracle for Weblogic server; WLST (http://docs.oracle.com/cd/E14571_01/web.1111/e13813/quick_ref.htm) and ESRI (http://esripress.esri.com/display/index.cfm?fuseaction=display&websiteID=224&moduleID=0) for GIS related programming. There is an official Python tutorial available on http://docs.python.org/3.3/tutorial/ and I've also used http://www.vogella.com/articles/Python/article.html to learn some basics.

First impression of the Python language
- I like the usage of indentation compared to the use of brackets or end statements
- code completion is far from perfect with the PyDev plug-in for Eclipse when using Python 3.3 (I needed to Google a lot for API documentation)
- even without background in Python, you can quickly get something working after reading some tutorials (although I have some experiences with other scripting languages like PERL, PHP, JavaScript. I usually use PERL for my regular scripting needs).

Implementation

Purpose

I wanted to create a script which would cleanup unused namespaces in XML files. I started with a BPEL file as an example (the same example as used in; http://javaoraclesoa.blogspot.nl/2013/04/soa-suite-ps6-11117-service-loose.html. It can be downloaded here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldTokens.zip). The BPEL file had the following contents;

<?xml version = "1.0" encoding = "UTF-8" ?>
<!--
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  Oracle JDeveloper BPEL Designer
 
  Created: Thu Apr 11 12:23:10 CEST 2013
  Author:  Maarten
  Type: BPEL 2.0 Process
  Purpose: Synchronous BPEL Process
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-->
<process name="CallHelloWorld"
               targetNamespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
               xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
               xmlns:client="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
               xmlns:ora="http://schemas.oracle.com/xpath/extension"
               xmlns:bpelx="http://schemas.oracle.com/bpel/extension"
         xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
         xmlns:ns1="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld">

    <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld" location="CallHelloWorld.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/>
    <!--
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        PARTNERLINKS                                                     
        List of services participating in this BPEL process              
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    -->
  <partnerLinks>
    <!--
      The 'client' role represents the requester of this service. It is
      used for callback. The location and correlation information associated
      with the client role are automatically set using WS-Addressing.
    -->
    <partnerLink name="callhelloworld_client" partnerLinkType="client:CallHelloWorld" myRole="CallHelloWorldProvider"/>
    <partnerLink name="HelloWorld" partnerLinkType="ns1:HelloWorld"
                 partnerRole="HelloWorldProvider"/>
  </partnerLinks>

  <!--
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
      VARIABLES                                                       
      List of messages and XML documents used within this BPEL process
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  -->
  <variables>
    <!-- Reference to the message passed as input during initiation -->
    <variable name="inputVariable" messageType="client:CallHelloWorldRequestMessage"/>

    <!-- Reference to the message that will be returned to the requester-->
    <variable name="outputVariable" messageType="client:CallHelloWorldResponseMessage"/>
    <variable name="InvokeHelloWorld_process_InputVariable"
              messageType="ns1:HelloWorldRequestMessage"/>
    <variable name="InvokeHelloWorld_process_OutputVariable"
              messageType="ns1:HelloWorldResponseMessage"/>
  </variables>

  <!--
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     ORCHESTRATION LOGIC                                              
     Set of activities coordinating the flow of messages across the   
     services integrated within this business process                 
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  -->
  <sequence name="main">

    <!-- Receive input from requestor. (Note: This maps to operation defined in CallHelloWorld.wsdl) -->
    <receive name="receiveInput" partnerLink="callhelloworld_client" portType="client:CallHelloWorld" operation="process" variable="inputVariable" createInstance="yes"/>
    <assign name="Assign1">
      <copy>
        <from>$inputVariable.payload/client:input</from>
        <to>$InvokeHelloWorld_process_InputVariable.payload/ns1:input</to>
      </copy>
    </assign>
    <invoke name="InvokeHelloWorld"
            partnerLink="HelloWorld" portType="ns1:HelloWorld"
            operation="process"
            inputVariable="InvokeHelloWorld_process_InputVariable"
            outputVariable="InvokeHelloWorld_process_OutputVariable"
            bpelx:invokeAsDetail="no"/>
    <assign name="Assign2">
      <copy>
        <from>$InvokeHelloWorld_process_OutputVariable.payload/ns1:result</from>
        <to>$outputVariable.payload/client:result</to>
      </copy>
    </assign>
    <!-- Generate reply to synchronous request -->
    <reply name="replyOutput" partnerLink="callhelloworld_client" portType="client:CallHelloWorld" operation="process" variable="outputVariable"/>
  </sequence>
</process>


The ora namespace was not used in this process but it is specified in the process tag.

lxml

Based on the following I started with lxml; http://lxml.de/api/lxml.etree-module.html#cleanup_namespaces since it had a function to easily clean unused namespaces and on StackOverflow.com there were many posts on lxml. To install lxml for Windows, I had to first download and install it from http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml

cleanup_namespaces

My first try was the following;

import lxml.etree as et
filename_in='D:\\dev\\HelloWorld\\CallHelloWorld\\CallHelloWorld.bpel'
filename_out='D:\\dev\\HelloWorld\\CallHelloWorld\\CallHelloWorld.bpel.out'
tree = et.parse(filename_in)
et.cleanup_namespaces(tree)
tree.write(filename_out)

In the output I noticed that although my process definition had been cleaned up from

<process name="CallHelloWorld"
               targetNamespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
               xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
               xmlns:client="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
               xmlns:ora="http://schemas.oracle.com/xpath/extension"
               xmlns:bpelx="http://schemas.oracle.com/bpel/extension"
         xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable"
         xmlns:ns1="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld">

To

<process xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable" xmlns:bpelx="http://schemas.oracle.com/bpel/extension" name="CallHelloWorld" targetNamespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld">

Several namespaces were removed which were in use such as the ns1 namespace which was used in a partnerlink definition as part of an attribute value;

<partnerLink name="HelloWorld" partnerLinkType="ns1:HelloWorld" partnerRole="HelloWorldProvider"/>

My conclusion was that using prebuild functions like the above would most likely not help solve this problem. I did not find a way to limit the functionality to specific namespaces.

Determining used namespaces

I tried a different approach; determine namespaces used in the root element and try to find them on different locations in the BPEL file. Then rewriting the root element.

import lxml.etree as et
import copy
filename_in='D:\\dev\\HelloWorld\\CallHelloWorld\\CallHelloWorld.bpel'
filename_out='D:\\dev\\HelloWorld\\CallHelloWorld\\CallHelloWorld.bpel.out'
tree = et.parse(filename_in)
root=tree.getroot()
nsmap=root.nsmap
nsmapnew= copy.deepcopy(nsmap)

#print (nsmap.values())
namespaces=set(nsmap.values())
print ("Namespaces found: "+str(len(nsmap)))
for nsitem in nsmap:
    found=0
    nscount=0;
    if nsitem != None:
        print ("Processing prefix: "+nsitem+" Namespace: "+nsmap.get(nsitem))
        #processing all elements
        walkAll = tree.getiterator()
        for elt in walkAll:
            #check element
            eltns=elt.xpath('namespace-uri(/*)')
            if eltns==nsmap.get(nsitem):
                found=1
                #print("Found namespace as element namespace")
                break
            if str(elt.text).find(nsitem+":") != -1:
                found=1
                #print("Found prefix as part of element text")
                break
            #check attributes
            for attribute in elt.attrib:
                if attribute.startswith("{"+nsmap.get(nsitem)+"}"):
                    found=1
                    #print ("Found namespace as attribute name namespace")
                    break
                if str(elt.attrib[attribute]).find(nsitem+":") != -1:
                    found=1
                    #print ("Found prefix as part of attribute value")
                    break
    else:
        #default namespace not removing
        found=1
    if found==0:
        print("Not found")
        del nsmapnew[nsitem]
print ("Namespaces remaining: "+str(len(nsmapnew)))

new_root = et.Element(root.tag, attrib=root.attrib,nsmap=nsmapnew)
new_root[:] = root[:]

#to add the top level comment
try:
    firstcomment=root.getprevious()
    new_root.addprevious(firstcomment)
except:
    None

tree = et.ElementTree(new_root)

tree.write(filename_out, xml_declaration=True, encoding='utf-8',pretty_print=True) 


This rewrote my XML like I wanted it to. Even if namespaces were used in XPATH expressions in the BPEL file, they were being recognized as being used. One drawback however was the namespace prefix which was added for all the subelements even though these elements were in the default namespace. I considered using a transformation to fix this. This would however remove the comments from the file so I decided not to. The script output was as followed;

Namespaces found: 6
Processing prefix: ns1 Namespace: http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld
Processing prefix: ora Namespace: http://schemas.oracle.com/xpath/extension
Not found
Processing prefix: client Namespace: http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld
Processing prefix: bpel Namespace: http://docs.oasis-open.org/wsbpel/2.0/process/executable
Processing prefix: bpelx Namespace: http://schemas.oracle.com/bpel/extension
Namespaces remaining: 5


The created output file was as followed;

<?xml version='1.0' encoding='UTF-8'?>
<!--
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  Oracle JDeveloper BPEL Designer
 
  Created: Thu Apr 11 12:23:10 CEST 2013
  Author:  Maarten
  Type: BPEL 2.0 Process
  Purpose: Synchronous BPEL Process
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
-->
<bpel:process xmlns:ns1="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld" xmlns:client="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld" xmlns:bpel="http://docs.oasis-open.org/wsbpel/2.0/process/executable" xmlns:bpelx="http://schemas.oracle.com/bpel/extension" xmlns="http://docs.oasis-open.org/wsbpel/2.0/process/executable" name="CallHelloWorld" targetNamespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"><bpel:import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld" location="CallHelloWorld.wsdl" importType="http://schemas.xmlsoap.org/wsdl/"/>
    <!--
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
        PARTNERLINKS                                                     
        List of services participating in this BPEL process              
      ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    -->
  <bpel:partnerLinks>
    <!--
      The 'client' role represents the requester of this service. It is
      used for callback. The location and correlation information associated
      with the client role are automatically set using WS-Addressing.
    -->
    <bpel:partnerLink name="callhelloworld_client" partnerLinkType="client:CallHelloWorld" myRole="CallHelloWorldProvider"/>
    <bpel:partnerLink name="HelloWorld" partnerLinkType="ns1:HelloWorld" partnerRole="HelloWorldProvider"/>
  </bpel:partnerLinks>

  <!--
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
      VARIABLES                                                       
      List of messages and XML documents used within this BPEL process
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  -->
  <bpel:variables>
    <!-- Reference to the message passed as input during initiation -->
    <bpel:variable name="inputVariable" messageType="client:CallHelloWorldRequestMessage"/>

    <!-- Reference to the message that will be returned to the requester-->
    <bpel:variable name="outputVariable" messageType="client:CallHelloWorldResponseMessage"/>
    <bpel:variable name="InvokeHelloWorld_process_InputVariable" messageType="ns1:HelloWorldRequestMessage"/>
    <bpel:variable name="InvokeHelloWorld_process_OutputVariable" messageType="ns1:HelloWorldResponseMessage"/>
  </bpel:variables>

  <!--
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     ORCHESTRATION LOGIC                                              
     Set of activities coordinating the flow of messages across the   
     services integrated within this business process                 
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  -->
  <bpel:sequence name="main">

    <!-- Receive input from requestor. (Note: This maps to operation defined in CallHelloWorld.wsdl) -->
    <bpel:receive name="receiveInput" partnerLink="callhelloworld_client" portType="client:CallHelloWorld" operation="process" variable="inputVariable" createInstance="yes"/>
    <bpel:assign name="Assign1">
      <bpel:copy>
        <bpel:from>$inputVariable.payload/client:input</bpel:from>
        <bpel:to>$InvokeHelloWorld_process_InputVariable.payload/ns1:input</bpel:to>
      </bpel:copy>
    </bpel:assign>
    <bpel:invoke name="InvokeHelloWorld" partnerLink="HelloWorld" portType="ns1:HelloWorld" operation="process" inputVariable="InvokeHelloWorld_process_InputVariable" outputVariable="InvokeHelloWorld_process_OutputVariable" bpelx:invokeAsDetail="no"/>
    <bpel:assign name="Assign2">
      <bpel:copy>
        <bpel:from>$InvokeHelloWorld_process_OutputVariable.payload/ns1:result</bpel:from>
        <bpel:to>$outputVariable.payload/client:result</bpel:to>
      </bpel:copy>
    </bpel:assign>
    <!-- Generate reply to synchronous request -->
    <bpel:reply name="replyOutput" partnerLink="callhelloworld_client" portType="client:CallHelloWorld" operation="process" variable="outputVariable"/>
  </bpel:sequence>
</bpel:process>


The ora namespace had been removed from the process attribute. The file had also become smaller. The ora namespace is however a default Oracle namespace and to make sure I didn't break anything, I tried to compile and deploy the altered process. This was successful. I also tested it with more complex processes. The resulting BPEL file was still fully functional.

Possible followups could be;

- expand the script and allow recursively processing of multiple files and filetypes
- determine used and unused namespaces for every element, not just the root element
- link the results of unused namespaces within a project to XSD's which could also be removed from a project if they are not used by any file in the project
- if for example problems start to occur with XPATH expressions after removing default Oracle namespaces; exclude specific namespaces from cleaning
- remove the namespace prefix for the default namespace

woensdag 24 april 2013

Debugging Java webservices

In this post I'll describe two methods which can be used to help developers debug Java webservices (JAX-WS). First I will describe how a debugger can be used from Netbeans. Next I'll describe how SOAP-UI can be used to perform a load test and check the response using an XPATH expression.

Implementation

Setup

To illustrate debugging
I've used a simple webservice to illustrate the above;

package ms.testapp.services;

import java.util.Calendar;
import javax.jws.WebService;
import javax.jws.WebMethod;
import javax.jws.WebParam;

/**
 *
 * @author Maarten
 */
@WebService(serviceName = "HelloWorldService")
public class HelloWorldService {

    /**
     * This is a sample web service operation
     */
    @WebMethod(operationName = "hello")
    public String hello(@WebParam(name = "name") String txt) throws Exception {
        Calendar myCal = Calendar.getInstance();
        if ((myCal.getTimeInMillis() % 10) == 0) {
            throw new Exception("Not right now !");
        }
        if (txt.equals("Maarten")) {
            throw new Exception("Maarten is not allowed !");
        }
        return "Hello " + txt + " !";
    }
}


This webservice will throw exceptions in case the supplied input is Maarten and during every 10th millisecond . This allows me to illustrate both the debugger and SOAP UI usage. After I've deployed the application, the URL of the WSDL of the webservice in my case is;
http://[server]:[port]/HelloWorldApp/HelloWorldService?wsdl

You can download the sample WAR file here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldApp.war

Using the debugger in Netbeans 

Netbeans has a build-in remote debugger. To use this debugger, first debugging needs to be activated on the application server by adding a JVM parameter (and restarting the server). This can be done in the Weblogic console by going to the Servers item in the 'Domain Structure'. Click on the relevant server and go to the 'Server Start' tab. Here you can add under 'Arguments';

-Xrunjdwp:server=y,suspend=n,transport=dt_socket,address=12998

After the server has been restarted, you can set a breakpoint in Netbeans by left clicking the relevant line number;


Next you can attach a debugger in Netbeans.



Now you can test your project in SOAP UI. First use any name. The Netbeans debugger is not triggered. Next use Maarten. The breakpoint is triggered.


SOAP UI will wait for a response (within a timeout). The debugger is triggered and you can see the contents of variables by hovering over them with the mouse. When you press the green play button, the program will continue and you will get the response in SOAP UI.



SOAP UI Xpath expression testing the response.

If you have defined a project in SOAP UI based on the WSDL of the service,you can create a TestCase.

Add an assertion;


Property Content, XPath Match. Define your namespaces and your XPath expression. Click the 'Select from current' button to test your query.


Now create a new load test and put some load on the service;


As you can see, there are a lot of errors. This is as expected since every 10ms, requests will fail with an exception.

Conclusion

Often it is not enough to put log statements in your code to solve problems. Using SOAP UI to do a load test and check the response can help determine the behavior of a service under load. Also using a debugger to track exactly what happens on the server can provide additional information on the cause of errors.

Do not forget that on a production environment, the server should (of course) not be in debug mode and log messages should be sufficient for the people responsible for application maintenance to solve problems.

donderdag 11 april 2013

SOA Suite PS6 (11.1.1.7); Service loose coupling and tokens

Oracle SOA Suite Patchset 6 (11.1.1.7) has been released (https://blogs.oracle.com/SOA/entry/new_release_of_oracle_soa). Several new features are available such as the implementation of configuration tokens. This could be an interesting new feature, since it could potentially allow configuration plans to become obsolete, so I decided to try it out in order to determine it's usefulness.

Using tokens

Edwin Biemond has described in his post on http://biemond.blogspot.nl/2013/04/token-configurations-in-oracle-soa.html how tokens can be used in configurations. He describes the following limitation; "Important to know this only works on the location attribute of the binding.ws element of the composite.xml file.". So if you use references in other locations to for example concrete WSDL's , you can't replace them by using tokens.

Service Loose Coupling

One of the general principles of Service Orientation is Service Loose Coupling (http://serviceorientation.com/serviceorientation/service_loose_coupling); "This principle advocates the creation of a specific type of relationship within and outside of service boundaries, with a constant emphasis on reducing ("loosening") dependencies between the service contract, its implementation, and its service consumers.". On the following blog, a method is described to reduce the dependency between Oracle SOA composite services by placing abstract WSDL's in the MDS; https://blogs.oracle.com/aia/entry/aia_11g_best_practices_for_dec. You can find a clear description of what an abstract WSDL is and how you can create one at; http://sathyam-soa.blogspot.nl/2012/10/abstract-wsdl-vs-concrete-wsdl.html. In the AIA 11g best practice blog post it is mentioned that in the composite.xml file there is a reference to an abstract and a concrete WSDL. The concrete WSDL is referenced in the binding.ws location attribute, which is exactly the attribute which can be replaced with tokens!

An example of a reference to an abstract WSDL is;
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl

An example of a reference to a concrete WSDL is;
http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL

Implementation

I created a HelloWorld process and a CallHelloWorld process. The CallHelloWorld process is a proxy to the HelloWorld process.

When creating the process, the following composite.xml was generated for the CallHelloWorld process (indicated in bold the references to the HelloWorld process);

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Generated by Oracle SOA Modeler version 11.1.1.7.0 at [4/11/13 12:22 PM]. -->
<composite name="CallHelloWorld"
           revision="1.0"
           label="2013-04-11_12-22-56_153"
           mode="active"
           state="on"
           xmlns="http://xmlns.oracle.com/sca/1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
           xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy"
           xmlns:ui="http://xmlns.oracle.com/soa/designer/">
  <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
          location="CallHelloWorld.wsdl" importType="wsdl"/>
  <import namespace="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld"
          location="
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl"
          importType="wsdl"/>

  <service name="callhelloworld_client_ep"
           ui:wsdlLocation="CallHelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.interface(CallHelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.endpoint(callhelloworld_client_ep/CallHelloWorld_pt)"/>
  </service>
  <property name="productVersion" type="xs:string" many="false">11.1.1.7.0</property>
  <component name="CallHelloWorld" version="2.0">
    <implementation.bpel src="CallHelloWorld.bpel"/>
    <property name="bpel.config.transaction" type="xs:string" many="false">required</property>
    <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string"
              many="false">async.persist</property>
  </component>
  <reference name="HelloWorld"
             ui:wsdlLocation="
http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.interface(HelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">

      <property name="weblogic.wsee.wsat.transaction.flowOption"
                type="xs:string" many="false">WSDLDriven</property>
    </binding.ws>
  </reference>
  <wire>
    <source.uri>callhelloworld_client_ep</source.uri>
    <target.uri>CallHelloWorld/callhelloworld_client</target.uri>
  </wire>
  <wire>
    <source.uri>CallHelloWorld/HelloWorld</source.uri>
    <target.uri>HelloWorld</target.uri>
  </wire>
</composite>


This composite.xml refers to a concrete WSDL; http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL. It also refers to the abstract WSDL at http://localhost:8001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl.These WSDL's however cannot be accessed if the HelloWorld process has not been deployed. For compilation and loading of the process in the SOA runtime, only the abstract WSDL's are required. Therefore, putting the abstract WSDL in the MDS resolves some dependency issues which are for example observed during server start (since there is no guarantee to the order in which services are started).

Putting the abstract WSDL in the MDS

MDS usage in Oracle SOA Suite 11g could in my opinion be improved. You can deploy to the MDS by using scripts; http://biemond.blogspot.nl/2009/11/soa-suite-11g-mds-deploy-and-removal.html or you can create a ZIP-file and upload it. See http://www.oracle.com/technetwork/articles/soa/fonnegra-storing-sca-metadata-1715004.html for an overview of what Oracle provides to interact with the MDS. When developing from JDeveloper though I recommend using a local file based MDS from a version controlled workarea. There are several arguments in support of this but since it is not the focus of this post, I'll not go into details here. For simplicity, I've created a ZIPfile with the following structure;

/apps/hello/wsdl/HelloWorld.wsdl
/apps/hello/xsd/HelloWorld.xsd

And uploaded it using the MDS Configuration page (Import)


Updating the composite.xml from CallHelloWorld
 
In order to use the files put in the MDS, the composite.xml from the CallHelloWorld process needs to be updated. Indicated in bold are the parts I've changed.

<?xml version="1.0" encoding="UTF-8" ?>
<!-- Generated by Oracle SOA Modeler version 11.1.1.7.0 at [4/11/13 12:22 PM]. -->
<composite name="CallHelloWorld"
           revision="1.0"
           label="2013-04-11_12-22-56_153"
           mode="active"
           state="on"
           xmlns="http://xmlns.oracle.com/sca/1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
           xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy"
           xmlns:ui="http://xmlns.oracle.com/soa/designer/">
  <import namespace="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld"
          location="CallHelloWorld.wsdl" importType="wsdl"/>
  <import namespace="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld"
          location="oramds:/apps/hello/wsdl/HelloWorld.wsdl"
          importType="wsdl"/>

  <service name="callhelloworld_client_ep"
           ui:wsdlLocation="CallHelloWorld.wsdl">
    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.interface(CallHelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/CallHelloWorld/CallHelloWorld#wsdl.endpoint(callhelloworld_client_ep/CallHelloWorld_pt)"/>
  </service>
  <property name="productVersion" type="xs:string" many="false">11.1.1.7.0</property>
  <component name="CallHelloWorld" version="2.0">
    <implementation.bpel src="CallHelloWorld.bpel"/>
    <property name="bpel.config.transaction" type="xs:string" many="false">required</property>
    <property name="bpel.config.oneWayDeliveryPolicy" type="xs:string"
              many="false">async.persist</property>
  </component>
  <reference name="HelloWorld"
             ui:wsdlLocation="oramds:/apps/hello/wsdl/HelloWorld.wsdl">

    <interface.wsdl interface="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.interface(HelloWorld)"/>
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">
      <property name="weblogic.wsee.wsat.transaction.flowOption"
                type="xs:string" many="false">WSDLDriven</property>
    </binding.ws>
  </reference>
  <wire>
    <source.uri>callhelloworld_client_ep</source.uri>
    <target.uri>CallHelloWorld/callhelloworld_client</target.uri>
  </wire>
  <wire>
    <source.uri>CallHelloWorld/HelloWorld</source.uri>
    <target.uri>HelloWorld</target.uri>
  </wire>
</composite>



Configuring the MDS connection (JDeveloper)

For the compilation of the process, make sure you have a local file based MDS configured; In the Application Resources tab under Applications there is a Connections folder. Here you can create the MDS connection.


Make sure the connection is correctly present in the adf-config.xml of your application. My MDS was in /dev/HelloWorld/mds. My adf-config.xml was as followed (in bold what I needed to add to make the oramds references work correctly);

<?xml version="1.0" encoding="windows-1252" ?>
<adf-config xmlns="http://xmlns.oracle.com/adf/config"
            xmlns:config="http://xmlns.oracle.com/bc4j/configuration"
            xmlns:adf="http://xmlns.oracle.com/adf/config/properties"
            xmlns:sec="http://xmlns.oracle.com/adf/security/config">
  <adf-adfm-config xmlns="http://xmlns.oracle.com/adfm/config">
    <defaults useBindVarsForViewCriteriaLiterals="true"/>
    <startup>
      <amconfig-overrides>
        <config:Database jbo.locking.mode="optimistic"/>
      </amconfig-overrides>
    </startup>
  </adf-adfm-config>
  <adf:adf-properties-child xmlns="http://xmlns.oracle.com/adf/config/properties">
    <adf-property name="adfAppUID" value="HelloWorld.ms.testapp.soa.utils"/>
  </adf:adf-properties-child>
  <sec:adf-security-child xmlns="http://xmlns.oracle.com/adf/security/config">
    <CredentialStoreContext credentialStoreClass="oracle.adf.share.security.providers.jps.CSFCredentialStore"
                            credentialStoreLocation="../../src/META-INF/jps-config.xml"/>
  </sec:adf-security-child>
  <adf-mds-config xmlns="http://xmlns.oracle.com/adf/mds/config">
    <mds-config xmlns="http://xmlns.oracle.com/mds/config">
      <persistence-config>
        <metadata-namespaces>
          <namespace path="/soa/shared" metadata-store-usage="mstore-usage_1"/>
          <namespace path="/apps" metadata-store-usage="mstore-usage_3"/>
        </metadata-namespaces>
        <metadata-store-usages>
          <metadata-store-usage id="mstore-usage_1">
            <metadata-store class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
              <property name="metadata-path"
                        value="${oracle.home}/integration"/>
              <property name="partition-name" value="seed"/>
            </metadata-store>
          </metadata-store-usage>
          <metadata-store-usage id="mstore-usage_3">
                  <metadata-store class-name="oracle.mds.persistence.stores.file.FileMetadataStore">
                     <property value="D:\dev\HelloWorld"
                      name="metadata-path"/>
                     <property value="mds" name="partition-name"/>
                  </metadata-store>
               </metadata-store-usage>

        </metadata-store-usages>
      </persistence-config>
    </mds-config>
  </adf-mds-config>
</adf-config>


Next I undeployed HelloWorld and compiled/deployed CallHelloWorld to confirm they were loosely coupled. I got the following warning; Warning(28,72): Failed to Find Binding "Service1":"{http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld}HelloWorld_pt" in WSDL Manager

This is correct since the binding is still;
    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                soapVersion="1.1">


I could create the JAR file and deploy it. Even without the HelloWorld process.

Tokens

As you might have noticed, the binding is the only concrete coupling between the endpoint of HelloWorld and the CallHelloWorld. Here tokens come in. We can use a token to specify the endpoint and thus become completely independant of configuration plans for the specification of the endpoint to use!

    <binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="http://localhost:8001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                soapVersion="1.1">


How to use tokens has been described on http://docs.oracle.com/cd/E28280_01/dev.1111/e10224/sca_bindingcomps.htm#CIHFJFJC. I will not go into detail here. The steps are as followed;

Use from the Composite Editor the Binding URL tokenizer

The binding.ws entry is replaced in my example by;

<binding.ws port="http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)"
                location="${helloworld_protocol}://${helloworld_host}:${helloworld_port}/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL"
                supports="" soapVersion="1.1">


Update the tokens on the server


On the server, there is now a file mdm-url-resolver.xml present. In my example this file could be found in; /u01/app/Middleware/user_projects/domains/base_domain/config/fmwconfig/mdm-url-resolver.xml. This file is an example token file;

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<entry key="helloworld_host">localhost</entry>
<entry key="helloworld_protocol">http</entry>
<entry key="helloworld_port">8001</entry>
</properties>


You can place the token file in the MDS so it is easily available to other developers. I deployed the HelloWorld process and called the CallHelloWorld service with the following result;


After restarting the (Admin)Server, it worked.

Predefined token ${serverURL} 

There is one predefined token; the ${serverURL} token. This token uses the soa-infra server URL as it's value. When I changed my binding.ws location entry to ${serverURL}/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL and tested the service, it worked.This means no token configuration on the server is required if you want to call processes on the same server.

You can download the sample code here; https://dl.dropboxusercontent.com/u/6693935/blog/HelloWorldTokens.zip. It does not require server configuration since the serverURL token is used. Keep in mind though that you will need an 11.1.1.7 (PS6) SOA Suite installation for this to work.

Conclusion

Tight coupling between services by using references to WSDL's and/or XSD's which are part of another process, should be avoided. This can be done by putting abstract WSDL's in the MDS.

When the above has been implemented, the loose coupling between processes can be extended by using tokens in the composite.xml file to set the correct binding.ws location attribute. When changing tokens/token values, the server needs to be restarted though. Since only one property can be replaced, it's use is limited to the concrete WSDL used to obtain the binding in the composite.xml file. When calling processes on the same server, the serverURL token can be used which is always available and does not require specific definition of tokens on the server.

With the above in place; loose coupling to WSDL's/services and tokens, one can argue that configuration plans become obsolete. When however variables/properties in the composite.xml are used, you still might need a mechanism to make these values differ per environment and configuration plans provide a (deploy time) solution to that. By using the MBean browser, these properties can be updated on runtime (see http://beatechnologies.wordpress.com/2011/11/04/persisting-component-preferences-in-oracle-soa-suite-11g/). This would not be needed if global variables could be used. I'm however not aware of an out of the box (GUI supported, little coding) implementation for deploytime and runtime global variables in Oracle SOA Suite 11g. Of course a database can be used, Server MBeans or plain old property files, but you have to build your own implementation.

woensdag 27 maart 2013

Monitoring Oracle SOA Suite 11g composites using HTTP polling

This post is about efficient vendor neutral monitoring of Oracle SOA composites by providing a mechanism which allows HTTP polling for the state/mode on a per composite basis. First I'll describe what could be the reason to monitor composite process state/mode. Then I'll provide code which can be used to allow HTTP polling of the process state/mode in order to link it to various monitoring tools or custom dashboards.

What is composite state?

There are two properties related to the behavior of a composite in relation to accepting requests and processing already running instances; composite mode and composite state. The mode can be active or retired and the state can be on or off. If the mode is retired, running instances will finish but no new instances can be created. Active means the service is ready to accept requests. If the state is off, the composite is not loaded (shutdown) and as such will not process running requests or pickup new requests. If the state is on, the composite is loaded. The mode and state can be manually changed from Enterprise Manager Fusion Middleware Control. They can also be changed by accessing the API for example as part of an exception handling mechanism.

In the below image you can see the state/mode of all the composites in a partition from Fusion Middleware Control;

In the below image you can see the buttons in Fusion Middleware control to retire/shutdown a composite. Once this is done, buttons for activating/starting the composite become visible.


Why can composite state be important to monitor?

Starting the server

When an Oracle SOA Suite server starts, resources can be pulled in and loaded as part of a composite. Such can be the case when external webservices are called and remote XSD's and WSDL's are used (references). If resources can not be loaded this way, the loading of the entire server might stall. In the Weblogic console however, the server will have the state RUNNING. The SOA Server however will not allow much actions from the webinterface since it is still starting. How to check the SOA serverstate with a servlet is illustrated on http://javaoraclesoa.blogspot.nl/2012/11/soa-suite-cluster-deployments-and.html. If the server is starting, usually the composite which is being started will have state off. Composites which have not been started yet will have state unknown. If the server hangs during start, this can help identifying the problem. Sometimes a manual undeploy is required of a faulty process. This is described on; http://shrikworld.blogspot.nl/2011/04/how-to-undeploy-composite-manually.html. Monitoring the state/mode of individual composites might provide an indication of start-up issues in addition to monitoring the entire SOA Server state.

Exception handling

When using certain methods of exception handling in Oracle SOA Suite 11g, processes can get the retired state in order to stop processing messages after a fault situation. For example, when using the method described on; http://javaoraclesoa.blogspot.nl/2012/06/exception-handling-fault-management-and.html. Usually an error event is triggered linked to sending an e-mail to the person responsible for solving the problem. This person however can take some time before taking action or checking/responding to his mail. In the mean time, the number of requests which need to be processed can increase, requiring more time after activating the composite again before the system is capable of processing new requests. Sometimes this mechanism might be sufficient but a little redundancy in informing people doesn't hurt.

Common monitoring tools

Organizations (especially the larger ones) often use monitoring tooling to determine the state of their server park. If a critical system fails, these monitoring tools immediately inform the person responsible for maintaining server stability, even at night or in weekends. This avoids the problem which can arise when the mail indicating a problem is overlooked. Also usually these monitoring tools have dashboards at which more people look, so the chance the problem is solved quickly increases.

Integrating the raising of the error event from a composite with specific monitoring tooling causes a direct dependency (tight coupling) between the monitoring tooling and the composite error handling mechanism. Also it will often require different disciplines/departments to achieve. Because of this, it is often not advisable to do.

The monitoring tools are not always from the same software vendor as the system which need to be monitored and thus specific components and states/modes are usually not fully supported. Certain common vendor neutral monitoring mechanisms are often used to allow monitoring of diverse systems. Usually these tools allow HTTP polling mechanisms. For example HP SiteScope (http://www8.hp.com/us/en/software-solutions/software.html?compURI=1174244#.UVKxsleyJh4).

Monitoring composite state by using HTTP polling

To monitor composite state via a HTTP polling mechanism, a HttpServlet can be deployed on the Oracle SOA server. The servlet provided in this post accepts an HTTP POST or GET request with the following parameters; name, partition, revision.

It then selects the composite and returns it's state in the response. It's code is based on the previously mentioned sample on; http://javaoraclesoa.blogspot.nl/2012/11/soa-suite-cluster-deployments-and.html. There the code is also explained.

You can test the process after deployment like (where of course hostname needs to be replaced and you should refer to a  composite which is present in your environment);
http://soabpm-vm:7001/DetermineBPELProcessStatus/determinebpelprocessstatus?name=HelloWorld&revision=1.0&partition=default

The output in my case is State: on, Mode: active

To test it's function, you can retire the composite and confirm the servlet returns; State: on, Mode: retired. You can also check the presence of a composite. If you provide a name/revision/partition which is not a valid composite, the State and Mode field will remain empty.

You can download the code here; https://dl.dropbox.com/u/6693935/blog/DetermineCompositeStatus.zip

zaterdag 16 maart 2013

Generating Oracle SOA configuration plans; XML manipulation in Ant using XMLTask

Configuration plans can be used for making Oracle SOA deployments specific to an environment. Writing deployment plans can be cumbersome. Especially replacing endpoints of every called service for a newly generated process for every environment is repetitive work and error prone. Also updating the configuration plan when changes occur is often forgotten. A solution for this can be to use a generic configuration plan such as described on; http://javaoraclesoa.blogspot.nl/2013/01/generic-configuration-plan-for-soa.html. This generic plan however is very generic and it will replace endpoints in all files in the project. This might not be what you want. In this post I describe another solution. Generating a configuration plan using the Oracle supplied Ant scripts and then using an Ant script to rewrite it to become specific based on a simple configuration file. This also illustrates how XML manipulations can be done by using xmltask (http://www.oopsconsultancy.com/software/xmltask/) in Ant.

The below explanation of how I've created this might seem complicated. If you just want the tool, you can download a complete working example (which requires little configuration) here; https://dl.dropbox.com/u/6693935/blog/GenConfigPlan.zip. All you have to do to get this working is replace the ORACLE_HOME variable, create a build.properties file for your environments and you're ready to go.

Implementation

build.properties

I've used a build.properties as follows;

envs=dev,prd
replacesets=first,second
first.dev.url=http://192.168.1.1:7001
first.prd.url=http://192.168.1.2:7001
second.dev.url=http://192.168.2.1:8080
second.prd.url=http://192.168.2.2:8080


This properties file specifies both my environments, development (dev) and production (prd). In my environment I have two sets of endpoints to be replaced. My first set (called conveniently 'first') and my second set (second). 'first' can represent for example the Oracle SOA server URL to be replaced and second can be an external service which has references which differ across the environments and thus for which URL's also need to be replaced.

Batch script to start Ant

I've used the following batch script to start the Ant script;

REM Location of Middleware home
set ORACLE_HOME=D:\Oracle\Middleware11116
set ANT_HOME=%ORACLE_HOME%\jdeveloper\ant
set PATH=%ANT_HOME%\bin;%PATH%
set JAVA_HOME=%ORACLE_HOME%\jdk160_24
set CURRENT_FOLDER=%CD%
ant -f genConfigPlan.xml -Dbasedir=%ORACLE_HOME%\jdeveloper\bin -DcompositeDir=%1


The parameter compositeDir specifies the path below which the composite.xml exists and where the new configuration plans need to be created

The batch file can for example be called like;

genConfigPlan.bat D:\dev\HelloWorld\HelloWorldCaller

Where HelloWorldCaller directory is the location of the project which contains the composite.xml. This example is also used in the 'Results' section

Ant script

I've created an Ant build file. This calls the ant-sca-compile script (http://docs.oracle.com/cd/E14571_01/integration.1111/e10224/sca_lifecycle.htm) to generate a default configuration plan. Then I use properties from my configuration file and do replacements for every environment in the generated configuration plan.

Below is the complete Ant script I've used. I will explain the most important parts.

<?xml version="1.0" encoding="iso-8859-1"?>
<project name="soaGenConfigPlan" default="build">
    <property environment="env"/>
   
    <property file="${env.CURRENT_FOLDER}/build.properties"/>
    <taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask">
        <classpath>
            <pathelement path="${env.CURRENT_FOLDER}/lib/xmltask.jar"/>
            <pathelement path="${env.CURRENT_FOLDER}/lib/xalan.jar"/>
        </classpath>
    </taskdef>

    <taskdef resource="net/sf/antcontrib/antcontrib.properties">
        <classpath>
            <pathelement location="${env.ORACLE_HOME}/modules/net.sf.antcontrib_1.1.0.0_1-0b2/lib/ant-contrib.jar"/>
        </classpath>
    </taskdef>

    <import file="${basedir}/ant-sca-compile.xml"/>

    <target name="genenvconfigplan">
       
        <echo>Build environment: ${buildenv}</echo>
        <property name="configplan_out" value="${compositename}_cfgplan_${buildenv}.xml"/>
        <echo>Target configplan name: ${configplan_out}</echo>
        <copy file="${compositeDir}/plantemplate.xml" tofile="${compositeDir}/${configplan_out}"/>
        <foreach list="${replacesets}"  target="replaceset" param="replaceset" inheritall="true" inheritrefs="false" delimiter=","/>
    </target>
   
    <target name="replaceset">
        <echo message="Processing replacementset: ${replaceset}"/>
        <propertycopy name="devurl" from="${replaceset}.dev.url"/>
        <propertycopy name="replacewithurl" from="${replaceset}.${buildenv}.url"/>
        <echo message="dev URL: ${devurl}"/>
        <echo message="${buildenv} URL: ${replacewithurl}"/>
        <xmltask source="${compositeDir}/${configplan_out}" destbuffer="myMsg"/>
       
        <xmltask sourcebuffer="myMsg" destBuffer="myMsg">
           
            <insert path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']/*[local-name() = 'import']/*[local-name() = 'searchReplace' and last()]" position="after"><![CDATA[<searchReplace xmlns="http://schemas.oracle.com/soa/configplan"><search>${devurl}</search><replace>${replacewithurl}</replace></searchReplace>]]></insert>
            <remove path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']/*[local-name() = 'import']/*[local-name() = 'searchReplace' and string-length(*[local-name() = 'search']/text())=0]"/>
            <insert path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'wsdlAndSchema']/*[local-name() = 'searchReplace' and last()]" position="after"><![CDATA[<searchReplace xmlns="http://schemas.oracle.com/soa/configplan"><search>${devurl}</search><replace>${replacewithurl}</replace></searchReplace>]]></insert>
            <remove path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'wsdlAndSchema']/*[local-name() = 'searchReplace' and string-length(*[local-name() = 'search']/text())=0]"/>
            <copy path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']" buffer="myMsgTmp"/>
            <call path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']/*[local-name() = 'reference']">
              <param name="name" path="@name"/>
              <actions>
                <echo>Found a reference: @{name}</echo>
                <xmltask sourcebuffer="myMsgTmp" destbuffer="myMsgTmp">
                <regexp path="/*[local-name() = 'composite']/*[local-name() = 'reference' and @name='@{name}']/*[local-name() = 'binding' and @type='ws']/*[local-name() = 'attribute' and @name='location']/*[local-name() = 'replace']/text()" pattern="(.*)${devurl}(.*)" replace="$1${replacewithurl}$2"/>
                <regexp path="/*[local-name() = 'composite']/*[local-name() = 'reference' and @name='@{name}']/*[local-name() = 'binding' and @type='ws']/*[local-name() = 'property' and @name='endpointURI']/*[local-name() = 'replace']/text()" pattern="(.*)${devurl}(.*)" replace="$1${replacewithurl}$2"/>
                </xmltask>
              </actions>
            </call>
            <replace path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']" withBuffer="myMsgTmp"/>
        </xmltask>
        <xmltask sourcebuffer='myMsg' dest="${compositeDir}/${configplan_out}"/>
    </target>

    <target name="build">
    <echo>current folder ${env.CURRENT_FOLDER}</echo>

    <property file="${env.CURRENT_FOLDER}/build.properties"/>
    <input message="Please enter composite directory:" addproperty="compositeDir"/>
    <antcall target="generateplan">
        <param name="scac.input" value="${compositeDir}/composite.xml"/>
        <param name="scac.plan" value="${compositeDir}/plantemplate.xml"/>
    </antcall>
    <xmltask source="${compositeDir}/composite.xml">
        <copy path="/*[local-name() = 'composite']/@name" property="compositename" attrValue="true"/>
    </xmltask>
    <echo message="Composite name: ${compositename}"/>
   
    <foreach list="${envs}" param="buildenv" target="genenvconfigplan" inheritall="true" inheritrefs="true" delimiter=","/>
    <delete file="${compositeDir}/plantemplate.xml"/>

    </target> 
</project>


The default target is 'build'. First it generates a default configuration plan (plantemplate.xml in the below sample) based on the composite.xml from a supplied location;

    <antcall target="generateplan">
        <param name="scac.input" value="${compositeDir}/composite.xml"/>
        <param name="scac.plan" value="${compositeDir}/plantemplate.xml"/>
    </antcall>


Then for every environment (from the properties file build.properties). It calls the target 'genenvconfigplan'. This target generates a configuration plan specific to an environment. This target calls for every replaceset from the build.properties the target 'replaceset'. This allows multiple sets of URL's to be replaced. This target performs the actual replacement by using the xmltask.

xmltask

The top part of the Ant build file is for expanding the classpath in order to include the xmltask Java libraries (see: http://stackoverflow.com/questions/11633308/xmltask-in-java-1-7). Also the Ant task is made known to the script.

    <taskdef name="xmltask" classname="com.oopsconsultancy.xmltask.ant.XmlTask">
        <classpath>
            <pathelement path="${env.CURRENT_FOLDER}/lib/xmltask.jar"/>
            <pathelement path="${env.CURRENT_FOLDER}/lib/xalan.jar"/>
        </classpath>
    </taskdef>


First it inserts a new entry at /SOAConfigPlan/wsdlAndSchema/searchReplace and removes the old empty one. Then it does the same for /SOAConfigPlan/wsdlAndSchema/searchReplace.

           <insert path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']/*[local-name() = 'import']/*[local-name() = 'searchReplace' and last()]" position="after"><![CDATA[<searchReplace xmlns="http://schemas.oracle.com/soa/configplan"><search>${devurl}</search><replace>${replacewithurl}</replace></searchReplace>]]></insert>
            <remove path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']/*[local-name() = 'import']/*[local-name() = 'searchReplace' and string-length(*[local-name() = 'search']/text())=0]"/>

 
Because of namespace issues I use the local-name() XPATH function to get to the correct path. See for example; http://stackoverflow.com/questions/9381512/xmlpath-from-ant-using-xmltasks-cant-match-if-xml-file-has-elements-in-differen. When I use the 'insert' action and specify a namespace which is a default namespace in the target, the namespace reference get's removed in the resulting XML which is nice.

Replacing the location attribute and endpointURI property in the reference part of the configuration plan was harder. I needed a loop construction here so I could process every reference entry individually. The method I found to achieve this was by using the xmltask 'call' action. Properties in Ant can be set only once, which makes working with them somewhat difficult. xmltask provides an alternative; buffers. Here I encountered some difficulties to process parts of the message. When using xmltask call action, the buffer used inside a called Ant target will be out of scope in the parent process. When using an embedded <actions/> section, the buffer won't be out of scope! So I used the embedded <actions/> section to achieve successful replacement. I've also used a parameter (@{name} in the below sniplet) to be able to select the correct reference node to do the replacement in.

             <call path="/*[local-name() = 'SOAConfigPlan']/*[local-name() = 'composite']/*[local-name() = 'reference']">
              <param name="name" path="@name"/>
              <actions>
                <echo>Found a reference: @{name}</echo>
                <xmltask sourcebuffer="myMsgTmp" destbuffer="myMsgTmp">
                <regexp path="/*[local-name() = 'composite']/*[local-name() = 'reference' and @name='@{name}']/*[local-name() = 'binding' and @type='ws']/*[local-name() = 'attribute' and @name='location']/*[local-name() = 'replace']/text()" pattern="(.*)${devurl}(.*)" replace="$1${replacewithurl}$2"/>
                <regexp path="/*[local-name() = 'composite']/*[local-name() = 'reference' and @name='@{name}']/*[local-name() = 'binding' and @type='ws']/*[local-name() = 'property' and @name='endpointURI']/*[local-name() = 'replace']/text()" pattern="(.*)${devurl}(.*)" replace="$1${replacewithurl}$2"/>
                </xmltask>
              </actions>
            </call>


As can be seen in the above sample I used the regular expression call to do the actual replacement. This way URL's like http://192.168.2.1:8080/webapp/services/mysuperservice would also get replaced properly.

The complete code can be downloaded here; https://dl.dropbox.com/u/6693935/blog/GenConfigPlan.zip

Result

In the below sniplets I've removed the generated comments for brevity. I've created a synchronous hello world process (HelloWorld) and  a synchronous process to call this hello world process (HelloWorldCaller). The default generated configuration plan for HelloWorldCaller is as follows;

<?xml version="1.0" encoding="UTF-8"?>
<SOAConfigPlan xmlns:jca="http://platform.integration.oracle/blocks/adapter/fw/metadata" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy" xmlns:edl="http://schemas.oracle.com/events/edl" xmlns="http://schemas.oracle.com/soa/configplan">
   <composite name="HelloWorldCaller">
      <import>
         <searchReplace>
            <search/>
            <replace/>
         </searchReplace>
      </import>
      <service name="helloworldcaller_client_ep">
         <binding type="ws">
            <attribute name="port">
               <replace>http://xmlns.oracle.com/HelloWorld/HelloWorldCaller/HelloWorldCaller#wsdl.endpoint(helloworldcaller_client_ep/HelloWorldCaller_pt)</replace>
            </attribute>
         </binding>
      </service>
      <component name="HelloWorldCaller">
         <property name="bpel.config.transaction">
            <replace>required</replace>
         </property>
      </component>
      <reference name="Service1">
         <binding type="ws">
            <attribute name="port">
               <replace>http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)</replace>
            </attribute>
            <attribute name="location">
               <replace>http://
192.168.1.1:7001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL</replace>
            </attribute>
            <property name="weblogic.wsee.wsat.transaction.flowOption">
               <replace>WSDLDriven</replace>
            </property>
         </binding>
      </reference>
   </composite>
   <wsdlAndSchema name="HelloWorld.wsdl|HelloWorldCaller.wsdl|xsd/HelloWorld.xsd|xsd/HelloWorldCaller.xsd">
      <searchReplace>
         <search/>
         <replace/>
      </searchReplace>
   </wsdlAndSchema>
</SOAConfigPlan>


This plan is not directly usuable since nothing is replaced. To avoid manually creating a configuration plan for my production environment, I've used the Ant script explained in this post with the mentioned build.properties.

In the below script output you can see all replacement sets are used (first and second) for all environments (dev and prd) and all references (1 in this case).

----------------------------------------------------
Buildfile: genConfigPlan.xml

build:
     [echo] current folder D:\dev\GenConfigPlan
    [input] skipping input as property compositeDir has already been set.

generateplan:
    [input] skipping input as property scac.input has already been set.
    [input] skipping input as property scac.plan has already been set.
[generateplan] Loading Composite file d:\dev\HelloWorld\HelloWorldCaller\composite.xml
[generateplan] Composite loaded
[generateplan] Done generation of soa config plan.
[generateplan] Write soa config plan to file d:\dev\HelloWorld\HelloWorldCaller/plantemplate.xml
[generateplan] Generate plan successful
     [echo] Composite name: HelloWorldCaller

genenvconfigplan:
     [echo] Build environment: dev
     [echo] Target configplan name: HelloWorldCaller_cfgplan_dev.xml
     [copy] Copying 1 file to d:\dev\HelloWorld\HelloWorldCaller

replaceset:
     [echo] Processing replacementset: first
     [echo] dev URL: http://192.168.1.1:7001
     [echo] dev URL: http://192.168.1.1:7001
     [echo] Found a reference: Service1

replaceset:
     [echo] Processing replacementset: second
     [echo] dev URL: http://192.168.2.1:8080
     [echo] dev URL: http://192.168.2.1:8080
     [echo] Found a reference: Service1

genenvconfigplan:
     [echo] Build environment: prd
     [echo] Target configplan name: HelloWorldCaller_cfgplan_prd.xml
     [copy] Copying 1 file to d:\dev\HelloWorld\HelloWorldCaller

replaceset:
     [echo] Processing replacementset: first
     [echo] dev URL: http://192.168.1.1:7001
     [echo] prd URL: http://192.168.1.2:7001
     [echo] Found a reference: Service1

replaceset:
     [echo] Processing replacementset: second
     [echo] dev URL: http://192.168.2.1:8080
     [echo] prd URL: http://192.168.2.2:8080
     [echo] Found a reference: Service1
   [delete] Deleting: d:\dev\HelloWorld\HelloWorldCaller\plantemplate.xml

BUILD SUCCESSFUL
Total time: 2 seconds


After I ran the Ant script I get two configuration plans. One for the development environment and one for the production environment. Below is the one for the production environment. In bold indicated which portions are added/changed by the script.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<SOAConfigPlan xmlns="http://schemas.oracle.com/soa/configplan" xmlns:edl="http://schemas.oracle.com/events/edl" xmlns:jca="http://platform.integration.oracle/blocks/adapter/fw/metadata" xmlns:orawsp="http://schemas.oracle.com/ws/2006/01/policy" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
   <composite name="HelloWorldCaller">
      <import>
         <searchReplace>
<search>http://192.168.1.1:7001</search>
<replace>http://192.168.1.2:7001</replace>
</searchReplace>
<searchReplace>
<search>http://192.168.2.1:8080</search>
<replace>http://192.168.2.2:8080</replace>
</searchReplace>

      </import>
      <service name="helloworldcaller_client_ep">
         <binding type="ws">
            <attribute name="port">
               <replace>http://xmlns.oracle.com/HelloWorld/HelloWorldCaller/HelloWorldCaller#wsdl.endpoint(helloworldcaller_client_ep/HelloWorldCaller_pt)</replace>
            </attribute>
         </binding>
      </service>
      <component name="HelloWorldCaller">
         <property name="bpel.config.transaction">
            <replace>required</replace>
         </property>
      </component>
      <reference name="Service1">
         <binding type="ws">
            <attribute name="port">
               <replace>http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)</replace>
            </attribute>
            <attribute name="location">
               <replace>http://192.168.1.2:7001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL</replace>
            </attribute>
            <property name="weblogic.wsee.wsat.transaction.flowOption">
               <replace>WSDLDriven</replace>
            </property>
         </binding>
      </reference>
   </composite>
   <wsdlAndSchema name="HelloWorld.wsdl|HelloWorldCaller.wsdl|xsd/HelloWorld.xsd|xsd/HelloWorldCaller.xsd">
      <searchReplace>
<search>http://192.168.1.1:7001</search>
<replace>http://192.168.1.2:7001</replace>
</searchReplace>
<searchReplace>
<search>http://192.168.2.1:8080</search>
<replace>http://192.168.2.2:8080</replace>
</searchReplace>

   </wsdlAndSchema>
</SOAConfigPlan>


When testing the generated configuration plan (JDeveloper, right click the configuration plan, Validate Config Plan), the following is shown. This validates the generated configuration plan and shows it functions as expected.

Modified Composite [ HelloWorldCaller ]
    Import Loations
        No change in old and new value HelloWorldCaller.wsdl
        Old [ http://192.168.1.1:7001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl ]
        New [ http://192.168.1.2:7001/soa-infra/services/default/HelloWorld/HelloWorld.wsdl ]

    Component
      Component  [ HelloWorldCaller ]
        Property [ bpel.config.transaction ]
        No change in old and new value required
    Service
      Service  [ helloworldcaller_client_ep ]
        Service Bindings
          Binding  [ ws ]
    Attribute name=port
        No change in old and new value http://xmlns.oracle.com/HelloWorld/HelloWorldCaller/HelloWorldCaller#wsdl.endpoint(helloworldcaller_client_ep/HelloWorldCaller_pt)
    Reference
      Reference  [ Service1 ]
        Reference Bindings
          Binding  [ ws ]
        Property [ weblogic.wsee.wsat.transaction.flowOption ]
        No change in old and new value WSDLDriven
    Attribute name=port
        No change in old and new value http://xmlns.oracle.com/HelloWorld/HelloWorld/HelloWorld#wsdl.endpoint(helloworld_client_ep/HelloWorld_pt)
    Attribute name=location
        Old [ http://192.168.1.1:7001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL ]
        New [ http://192.168.1.2:7001/soa-infra/services/default/HelloWorld/helloworld_client_ep?WSDL ]

---End Match for composite [ HelloWorldCaller ] in config plan---
Checking for replacement in wsdl and schema files