Accessing Domain Value Maps in OSB using Custom XPath

As all know, current version of OSB doesn’t support DVM out of the box. So if you want to do lookups for a large amount of data then doing it in a Xquery or doing database lookups becomes bit complicated to handle. Instead, we can do similar functionality to BPEL’s dvm and dvm lookups in OSB. Basically, we put the dvm xml file in an OSB server classpath and then write a custom xpath function which can lookup data from these files. The custom xpath function uses XMLBeans api to parse the dvm xml and return the requested column of data.

The xpath method we are going to implement has the following signature :

String lookupDVM(String dvmName,String keyColumnName,String keyValue,String targetColumnName,String defaultValue)

dvmName : Name of the dvm file to query
keyColumnName : Name of the column in the dvm containg the key
keyValue : lookup key
targetColumn: Column in the dvm whose value to be retrieved
defaultValue : default value to return from dvm if no matches are found.

sample State.dvm


<?xml version="1.0" encoding="UTF-8"?>
<dvm name="State" xmlns="http://xmlns.oracle.com/dvm">

	<description>Maintain LOV Mappings for State </description>

	<columns>
		<column name="Code"/>
		<column name="Value"/>		
	</columns>

	<rows>
		<row>
			<cell>NSW</cell>
			<cell>New South Wales</cell>			
		</row>
		<row>
			<cell>VIC</cell>
			<cell>Victoria</cell>			
		</row>
		<row>
			<cell>TAS</cell>
			<cell>Tasmania</cell>			
		</row>
		<row>
			<cell>WA</cell>
			<cell>West Australia</cell>			
		</row>
		<row>
			<cell>QLD</cell>
			<cell>Queensland</cell>			
		</row>
		<row>
			<cell>SA</cell>
			<cell>South Australia</cell>			
		</row>		
	</rows>
	
</dvm>

Place this dvm file in server classpath. I placed this in $DOMAIN_HOME for the testing.

Java Code for the custom xpath function

import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;

import java.io.IOException;
import java.util.HashMap;
public class OSB_DVM {
	private static HashMap  dvmCollection = new HashMap();
	/* Driver Method */
	public static void main(String [] args)throws Exception
	{
		System.out.println(lookupDVM("State","Code","NSW","Value","default"));
	}
	/* Lookup DVM from DVMCollection */
	public static XmlObject getDVM(String dvmName)throws Exception
	{
		XmlObject dvm=(XmlObject)dvmCollection.get(dvmName);
		if (dvm==null)
		{
			dvm=loadDVM(dvmName);
			if (dvm!=null && validateDVM(dvm,dvmName))
				dvmCollection.put(dvmName, dvm);
			else throw new Exception ("Cannot find DVM "+dvmName+" on disk");
		}

		return dvm;
	}
	/* Load DVM from disk */
	public static XmlObject loadDVM(String dvmName)
	{
	 String xmlFilePath = dvmName+".dvm";
	 XmlObject dvm=null;
	 try
	 {
	 dvm=XmlObject.Factory.parse(Thread.currentThread().getContextClassLoader().getResource(xmlFilePath));
	 }
	 catch(IOException ex)
	 {

	 ex.printStackTrace();
	 }
	 catch(XmlException ex)
	 {
	 ex.printStackTrace();
	 }
	 return dvm;
	}

	public static boolean validateDVM(XmlObject dvm, String dvmName) throws Exception
	{
		String path="declare namespace xq='http://xmlns.oracle.com/dvm';" +
	    "./xq:dvm/xq:columns/xq:column";
		XmlObject [] columns = dvm.selectPath(path);
		if (columns.length == 0)
			throw new Exception("Invalid DVM -"+dvmName+" column defintions Not found");
		path="declare namespace xq='http://xmlns.oracle.com/dvm';" +
	    "./@name";
		for (int i=0;i<columns.length;i++)
		{

			XmlObject [] columnNames=columns[i].selectPath(path);

			if (columnNames.length !=1)
			throw new Exception("Invalid DVM - "+dvmName+" Zero or Multiple name Attributes found for column definitions");
		}
		return true;
	}

	public static String lookupDVM(String dvmName,String keyColumnName,String keyValue,String targetColumnName,String defaultValue)throws Exception
	{
		XmlObject dvm = getDVM(dvmName);
		String path="declare namespace xq='http://xmlns.oracle.com/dvm';" +
	    "./xq:dvm/xq:columns/xq:column";

		XmlObject [] columns = dvm.selectPath(path);

		int keyColumnIndex=-1;
		int targetColumnIndex=-1;
		path="declare namespace xq='http://xmlns.oracle.com/dvm';" +
	    "./@name";
		for (int i=0;i<columns.length;i++)
		{
			String columnName = columns[i].selectPath(path)[0].newCursor().getTextValue();
			if (columnName.equals(keyColumnName))
					keyColumnIndex=i+1;
			else if (columnName.equals(targetColumnName))
				    targetColumnIndex=i+1;

		}
		if (keyColumnIndex == -1 || targetColumnIndex ==-1 )
			throw new Exception("Invalid Column Names "+keyColumnName+" "+targetColumnName);
		String returnValue=null;
		path="declare namespace xq='http://xmlns.oracle.com/dvm';" +
	    "./xq:dvm/xq:rows/xq:row[xq:cell["+keyColumnIndex+"]='"+keyValue+"']/xq:cell["+targetColumnIndex+"]";

		XmlObject [] cells = dvm.selectPath(path);
		if ( cells.length == 0)
			returnValue=defaultValue;
		else
		returnValue = cells[0].newCursor().getTextValue();

		return returnValue;

	}

}

Since this code uses xpath based on predicates, you will need extra jars in your project build path if you are testing in OEPE. This is true even if you enable Apache XMLBeans facet for the project. The following jars will be required for testing in OEPE

  • xbean_xpath : From XML Beans project
  • saxon9 : From Saxon Project
  • saxon9-dom : From Saxon project
  • Compile this code and create a jar file called osb_dvm.jar.
    Place the jar in OSB_HOME/config/xpath-functions. Also create the osb_dvm.properties and osb_dvm.xml required for the custom xpath file in this same directory.

    osb_dvm.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <xpf:xpathFunctions xmlns:xpf="http://www.bea.com/wli/sb/xpath/config">
        <xpf:category id="%OSB_FUNCTIONS%">
            <xpf:function>
                <xpf:name>lookupDVM</xpf:name>
                <xpf:comment>looks up a DVM in filesystem and returns transformed value</xpf:comment>
                <xpf:namespaceURI>http://www.bea.com/xquery/xquery-functions</xpf:namespaceURI>
                <xpf:className>OSB_DVM</xpf:className>
                <xpf:method>java.lang.String lookupDVM(java.lang.String,java.lang.String,java.lang.String,java.lang.String,java.lang.String)</xpf:method>
                <xpf:isDeterministic>false</xpf:isDeterministic>
                <xpf:scope>Pipeline</xpf:scope>
                <xpf:scope>SplitJoin</xpf:scope>
            </xpf:function>       
            
        </xpf:category>
    </xpf:xpathFunctions>
              
    
    

    osb_dvm.properties

    %OSB_FUNCTIONS%=Service Bus Functions
    

    Restart the server.
    I created a dummy proxy service with an ASSIGN action to test the custom xpath function.

    Now add new dvm called Country.dvm to DOMAIN_HOME

    <?xml version="1.0" encoding="UTF-8"?>
    
    <dvm name="State" xmlns="http://xmlns.oracle.com/dvm">
    
    	<description>Maintain LOV Mappings for Countries </description>
    
    	<columns>
    		<column name="Code"/>
    		<column name="Value"/>		
    	</columns>
    
    	<rows>
    		<row>
    			<cell>AU</cell>
    			<cell>Australia</cell>			
    		</row>
    		
    	</rows>
    	
    </dvm>
    

    Now test using fn-bea:lookupDVM(“Country”,”Value”,”Australia”,”Code”,”default”);
    You can see result AU being returned.

    Thus this is an extensible framework whereyou can create any new dvm’s ( and in the same oracle format, so you can use JDeveloper to create or reuse from a soa application), and make OSB to perform lookups by just placing these dvm files in the classpath. No code change or server restart will be required.
    For performance reason I am caching the dvm in memory once loaded, so if a loaded dvm changes then you might need a restart. To prevent restart you can have another method , which loads the dvm from filesystem and updates the cache

    /* Refreshes changed dvm from file system and updates cache                                 */
    	public static boolean refreshDVM(String dvmName) throws Exception
    	{
    		XmlObject dvm = loadDVM(dvmName);
    		dvmCollection.put(dvmName,dvm);
    		return true;
    		
    	}
    

    Add refreshDVM also to the list of exposed xpath functions in osb-dvm.xml Then you can use a utility proxy service which accepts the dvm name in the request and perform a refreshDVM() call for that dvm. This will load the new version into the cache.

    About atheek

    I am a Tech consultant working in Middleware/Integration area.
    This entry was posted in Domain Value Maps, OSB and tagged , , , . Bookmark the permalink.

    4 Responses to Accessing Domain Value Maps in OSB using Custom XPath

    1. Ramya says:

      HI.. I am trying to use the same concept in my OSB project, but i am getting error in lookupDVM function where Xpath is used for node selection. the error is something like Trying XBeans path engine… Trying XQRL… Trying Saxon… FAILED on declare namespace xq=’http://xmlns.oracle.com/dvm’;./xq:dvm/xq:rows/xq:row[xq:cell[1]=’ALEAUD_SUCESSCODE’]/xq:cell[2]. what is the right combination of the jars to be used?

    2. Naveen says:

      Dynamically Creating DVM file

      I have a requirement to read Pipe delimited test file and add those entries in DVM file.
      Later access the dvm file through dvm:lookup in the same SOA project.

      Is it possible to create DVM file from SOA Project (programmatically) and access it from the same SOA project to do lookup?
      I have tried using Spring context to create the DVM file through Java program. After deploying the SOA project, it creates the file under weblogic root folder which is not accessible by SOA project.
      Any one tried this scenario? or any Java Program to push DVM file to SOA-MDS (database)?

    3. Siaw Hie Tji says:

      I have a memory leak issue using this DVM code. Somehow, the size of the value inside the dvmCollection HashMap is increasing slowly from time to time. Still, haven’t found any clues yet 😦

    Leave a reply to Naveen Cancel reply