Showing posts with label XSLT. Show all posts
Showing posts with label XSLT. Show all posts

Thursday, August 15, 2013

OSB 11g XSLT multiple input parameters and the conversion error of org.apache.xmlbeans.impl.store.Xobj$DocumentXobj

OSB 11g XSLT expression editor allows you to enter multiple input parameters. The developer guide http://docs.oracle.com/cd/E23943_01/dev.1111/e15866/toc.htm (page 4-84) even contain a precise definition of each field with example. However, it doesn't work. You may see errors similar to this:
java.lang.RuntimeException: Invalid conversion from 'node-set' to 'org.apache.xmlbeans.impl.store.Xobj$DocumentXobj'

I see many frustrated bloggers posting questions. After my own long frustrating hours, i concluded it's an OSB bug. Good news is i also figured out a solution.

At the core, the issue is OSB passes in the parameter to XLST as org.apache.xmlbeans.impl.store.Xobj$DocumentXobj object (with the exception if the parameter is a simple string). But XSLT is expecting a NodeList.

Here is a quick solution. Compile this class, jar it up and copy it to your domain/lib directory:
package org.example.xslt;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;
public class XObjUtil
{
  public NodeList getNodeList(Object o)
  { System.out.println("### getNodeList o class name="+o.getClass().getName());
org.w3c.dom.Document xo = (org.w3c.dom.Document)o;
Node n = (Node) xo;
NodeList nl = n.getChildNodes();
       return nl;
// lazy way:  return (((org.w3c.dom.Document)o)).getChildNodes();
 }
}

Here is my cheap script to compile, jar and copy the file:

javac -d . XObjUtil.java
del myxlstext.jar
jar cvf myxlstext.jar org
copy myxlstext.jar C:\Oracle\Middleware\user_projects\domains\osbserver\lib

On the OSB side, here is the XSLT what will parse the input parameter:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0"
xmlns:client="http://xmlns.oracle.com/JCA_ResourceAdapter/foo/BPELProcess1"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:XObj="http://www.oracle.com/XSL/Transform/java/org.example.xslt.XObjUtil"
exclude-result-prefixes="xsl client ">
<xsl:param name="car" />
<xsl:template match="/">
<client:process>
<xsl:variable name="x" select="XObj:new()" />
<xsl:variable name="obj" select="XObj:getNodeList($x, $car)" />
<client:input>
<xsl:value-of select="$obj/client:car1" />
</client:input>
<client:foo1>
<xsl:value-of select="$obj/client:car2" />
</client:foo1>
<client:foo2>
<xsl:value-of select="/client:barElem/client:bar2" />
</client:foo2>
</client:process>
</xsl:template>
</xsl:stylesheet>

My foo/bar/car sample is based on this cheap XSD
<?xml version="1.0" encoding="UTF-8" ?>
<schema attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://xmlns.oracle.com/JCA_ResourceAdapter/foo/BPELProcess1"
     xmlns:this="http://xmlns.oracle.com/JCA_ResourceAdapter/foo/BPELProcess1" xmlns="http://www.w3.org/2001/XMLSchema">
    <complexType name="fooType">
        <sequence>
            <element name="input" type="string"/>
            <element name="foo1" type="string"/>
            <element name="foo2" type="string"/>
        </sequence>
    </complexType>
    <complexType name="barType">
        <sequence>
            <element name="input" type="string"/>
            <element name="bar1" type="string"/>
            <element name="bar2" type="string"/>
        </sequence>
    </complexType>
    <complexType name="carType">
        <sequence>
            <element name="input" type="string"/>
            <element name="car1" type="string"/>
            <element name="car2" type="string"/>
        </sequence>
    </complexType>
    <element name="process" type="this:fooType"/>
    <element name="carElem" type="this:carType"/>
    <element name="barElem" type="this:barType"/>
    <element name="processResponse">
        <complexType>
            <sequence>
                <element name="result" type="string"/>
            </sequence>
        </complexType>
    </element>
</schema>

Here is a sample output from the OSB test console:

client:process xmlns:client="http://xmlns.oracle.com/JCA_ResourceAdapter/foo/BPELProcess1"xmlns:XObj="http://www.oracle.com/XSL/Transform/java/org.example.xslt.XObjUtil">
<client:input>car18</client:input>
<client:foo1>car29</client:foo1>
<client:foo2>bar23</client:foo2>
</client:process>

One interesting fact is this sample shows you a cheap way of extending XSLT without a full blown Xpath function project, see here http://yuanmengblog.blogspot.com/2013/04/break-comma-separated-string-into-array.html

BTW, very importantly, SOA and OSB uses different XSLT framework. OSB uses more org.w3 XML, SOA use "Oracle" own XML stuff. When SOA invokes XSLT, it passes in the parameter as type oracle.xml.parser.v2.XMLDocumentFragment, if ever want to play with that, the conversion is this:
 public NodeList getNodeList(oracle.xml.parser.v2.XMLDocumentFragment o)
 {
org.w3c.dom.NodeList nl = ((org.w3c.dom.Node)o).getChildNodes();
return  nl;
 }

Two other things you can explore:

1. instead of tossing the jar file into domain/lib directory, try if you can merely upload the jar to the proxy folder in OSB, treats it like a java callout jar. If that works, then you don't need to reboot OSB when change your jar file.

2. implement this as an Xpath function, then your JDev XSLT editor can pick it up. Of course, your XSLT syntax will be modified slightly as well. Have fun!

Thursday, May 30, 2013

The "cool" way to do base64 decode with OSB 11.1.1.6

Two years ago, i did a post on how to use proxy java callout to base64 decode with OSB 11.1.1.3 http://yuanmengblog.blogspot.com/2011/04/base64-decoder-for-osb.html.

Now i have 11.1.1.6. Obviously, oracle has moved those jar files around, so you will have to find out where the xerces jar file is if you want to do the same thing in 11.1.1.6.

Well, a colleague brought to my attention something I thought was very cool: using XSLT java call to do the base64 decode. Then you don't need to do the proxy java callout, therefore, saving the trouble of uploading some custom jar file. That sounded very cool, so i decided to give it a shot.

For that we need to resolve two issues. 1. what's the Oracle xslt processor's syntax for java call (it appears to me the XSLT java call syntax varies among different xslt processors). 2. What is the new base64 decoder class in 11.1.1.6. The xslt code below answers both of these questions:

<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:b64="http://www.oracle.com/XSL/Transform/java/weblogic.apache.xerces.impl.dv.util.Base64"
  >
  <xsl:template match="/">
  <foo>
        <xsl:value-of select="b64:decode('d2xzdXNlcjp3ZWxjb21lMQ==')"/>
    </foo>
  </xsl:template>
</xsl:stylesheet>

plug it into your proxy assignment activity, then you will get an output of
<foo xmlns:b64="http://www.oracle.com/XSL/Transform/java/weblogic.apache.xerces.impl.dv.util.Base64">wlsuser:welcome1</foo>

Keep in mind, directly referencing an undocumented/unsupported oracle class "weblogic.apache.xerces.impl.dv.util.Base64" is a tricky business, proceed at your own risk. currently, this class is inside "com.bea.core.apache_1.3.0.1.jar" under "C:\Oracle\Middleware\Oracle_OSB1\modules" for my 11.1.1.6 install. If oracle decides to change that, then you need to update your xslt as well.

testing on the command line (in my environment):
set classpath=C:\Oracle\Middleware\oracle_common\modules\oracle.xdk_11.1.0\xmlparserv2.jar;c:\Oracle\Middleware\Oracle_OSB1\modules\com.bea.core.apache_1.3.0.1.jar;

first jar is for oraxsl, 2nd jar is for base64 decoding:

java oracle.xml.parser.v2.oraxsl   input.xml     test.xslt

Thursday, April 25, 2013

Tokenize string into node list with XSLT 1.0 and custom XPath function

For XSLT 2.0, use built-in tokenzie function.

For XSLT 1.0, I'll show two ways below.

At the end of the post, i also demonstrate a way to test XSLT from command line directly, it can prove invaluable if you need to debug custom xpath function, like i am doing.

== Part I - use template ==

For xlst 1.0, here is a sample. It shows a recursive template. You can put the template in the same file, but I split the files into two and used import to make the main xsl more clean.

main.xsl

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:car="http://foo/bar">
  <xsl:import href="serialNumber.xsl"/>

 <xsl:template match="/">
                  <xsl:choose>
                      <xsl:when test="contains(/inputStr, ',')=false">
                          <car:Serial>
                            <xsl:value-of select="/inputStr"/>
                          </car:Serial>
                      </xsl:when>
                      <xsl:otherwise>
                          <xsl:call-template name="serial">
                            <xsl:with-param name="commaStr" select="/inputStr"/>
                          </xsl:call-template>
                      </xsl:otherwise>
                  </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

serialNumber.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:car="http://foo/bar">

  <xsl:template name="serial" >
  <xsl:param name="commaStr"/>
    <xsl:if test="normalize-space($commaStr) != ''">
        <xsl:choose>
            <xsl:when test="contains($commaStr, ',')">
                 <car:Serial>
                    <xsl:value-of select="substring-before($commaStr, ',')"/>    
                  </car:Serial>
                  <xsl:call-template name="serial">
                    <xsl:with-param name="commaStr" select="substring-after($commaStr,',')"/>
                  </xsl:call-template>        
            </xsl:when>
            <xsl:otherwise>
                 <car:Serial>
                    <xsl:value-of select="$commaStr"/>    
                  </car:Serial>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

Disregard the specifics of namespace, here are some sample input and output:

<inputStr>Aaa</inputStr>
results in
<Serial>Aaa</Serial>

<inputStr>Aaa,bbb,ccc</inputStr>
result in
<Serial>Aaa</Serial><Serial>bbb</Serial><Serial>ccc</Serial>

<inputStr>Aaa,bbb,ccc,</inputStr>
will yield the same result.

== part II - use custom java XPath function ==

I developed the project based this link https://blogs.oracle.com/bwb/resource/custom_xpath_functions/Creating_Custom_XPath_functions_with_JDeveloper_FullPost.html, and https://blogs.oracle.com/reynolds/entry/building_your_own_path

My intention is to create a generic tokenizer like Java StringTokenizer. I made it work, but it's mixed result.

I only made it work with XSLT. I still need to figure out how to make it work with BPEL xpath. I suspect there is something not quite right when I pass in a single node to the xpath function whereas the signature expects a List. That's an experiment I need do later. When I do, I'll update this post.

Anyway, here is the java class and the descriptor (just a quick dirty impl for demo with two input parameters):

StringTokenizer.java

package com.foo.util.StringTokenizer;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import javax.xml.parsers.DocumentBuilderFactory;
import oracle.fabric.common.xml.xpath.IXPathContext;
import oracle.fabric.common.xml.xpath.IXPathFunction;
import oracle.fabric.common.xml.xpath.XPathFunctionException;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.w3c.dom.Node;

public class Tokenizer {
    public static Object tokenizeString(String str, String token) {      
        Element node =  null;
        String xmlStr = "";
        try {
            StringTokenizer st = new StringTokenizer(str, token);
            while (st.hasMoreTokens()) {
                String nt = st.nextToken().trim();
                     System.out.println(nt);
                xmlStr +="<node>"+nt + "</node>";
            }                
            if (xmlStr != null) {
                xmlStr ="<root>" + xmlStr + "</root>";
            }    
            node = DocumentBuilderFactory
                    .newInstance()
                    .newDocumentBuilder()
                    .parse(new ByteArrayInputStream(xmlStr.getBytes()))
                    .getDocumentElement();
     
        }
        catch (Exception e) {
            System.out.println("***ex="+e.getMessage());
        }
        return nList;
    }
    public static void main(String args[]) throws Exception{        
          NodeList nList = (NodeList) Tokenizer.tokenizeString("aaa,bb,cc", ",");
         
          if (nList.getLength() > 0) {
             for (int i = 0; i < nList.getLength(); i++) {
                  Node tNode = nList.item(i);
                  String tc = tNode.getTextContent();
                 System.out.println("***"+tc);              
             }
          }
}
}

descriptor:
<?xml version="1.0" encoding="UTF-8"?>
<soa-xpath-functions
  xmlns="http://xmlns.oracle.com/soa/config/xpath"
  xmlns:tn="http://www.oracle.com/XSL/Transform/java/com.cci.util.StringTokenizer.Tokenizer">
  <function name="tn:tokenize">
    <className>com.cci.util.StringTokenizer.Tokenizer</className>
   <return type="node-set"/>
    <params>
      <param name="str" type="string"/>
     <param name="token" type="string"/>
    </params>
    <desc/>
    <detail>
       <![CDATA[This function breaks up a comma separated string return a tokenized list of nodes.]]>
    </detail>
   </function>  
</soa-xpath-functions>

snippet of test.xsl
...
                xmlns:tn="http://www.oracle.com/XSL/Transform/java/com.foo.util.StringTokenizer.Tokenizer"
...
 <xsl:variable name="sNodes"                      select="tn:tokenizeString(/foo_ESB/DATA/ESB_ORDER/SERIAL_NUMBER_RECEIVED, ',')"/>

<xsl:for-each select="$sNodes">
   <xsl:value-of select="'<serial>'"/>
    <xsl:value-of select="."/>
   <xsl:value-of select="'</serial>"/>          
</xsl:for-each>

testing from the command line

One problem of testing inside JDev XSL mapper is that you can't see your java debug output. You can test your impl from the command line directly. It provides additional error messages if something is wrong.

Here is how I do it in my environment. 

1. set classpath=C:\Oracle\Middleware\oracle_common\modules\oracle.xdk_11.1.0\xmlparserv2.jar;c:\aproj\src\StringTokenizer\deploy\stringTokenizer.jar;C:\Oracle\Middleware\jdeveloper\soa\modules\oracle.soa.fabric_11.1.1\fabric-runtime.jar

2. test with actual input source, and xslt file:
    java oracle.xml.parser.v2.oraxsl    input.xml    test.xsl

3. test with Java only (no xlst):
      java com.foo.util.StringTokenizer.Tokenizer

Tuesday, August 7, 2012

XSLT accessing DVM

While I'm at it, let me record it here. This is again a "re-learn" that cost me many hours.

I need to access DVM in my XSLT inside my BPEL process. I have created my DVM file (myfile.dvm) inside the project, so "myfile.dvm" sits in the project folder. The correct way to access the file in xslt is:

 <xsl:value-of select='dvm:lookupValue("myfile.dvm","column1-title", "value1","column2-title","default-for-column2")'/>

Here are the two things that cost me several hours to "re-learn":

1. JDEV UI has that tool you can test XSLT locally, it works great most of the time, but it DOES NOT work with DVM. So don't waste your time testing DVM in jdev locally. You have to deploy it to the server to run your test!

2. I thought my XSLT file sits in "xsl" subfolder, so I tried to access it as "../myfile.dvm" in my XSLT code. Well, that turned out to be wrong. After searching through my old code, I found out that you should access the file as "./myfile.dvm". That's it.


Thursday, June 7, 2012

The Mess of XLST 1.0 / 2.0, JDEV and OSB

I ran into some mess with XSLT 1.0 / 2.0, JDEV and OSB. Oh, boy, I just have to blog it.

Let me sum up the lesson I learned before I dive into the details:
  • If you crafted your XSLT in JDEV, you tested it, and  it worked fine in JDEV.  Then you port it to OSB, the same XSLT code may blow up!
I have to admit that I didn't just do a "normal" XSLT and ported it OSB. I did something "smart" with XSLT. At first, I was proud of what I did. Then I spent hours to realize there is such a thing as being "too smart".

In JDEV,  the default version of XSLT is "1.0". You can change it to "2.0", so you can take advantage of some of the 2.0 features. I did that in the past. It worked well.

Here is what I did this time:

<xsl:stylesheet version="2.0"
                xmlns:xp20="http://www.oracle.com/XSL/Transform/java/oracle.tip.pc.services.functions.Xpath20"
...
  <xsl:template match="/">

  <!-- this only works with 2.0 -->
    <xsl:variable name="v">
      <var name="v1">legal</var>
      <var name="v2">preferred</var>
    </xsl:variable>
     <ns2:employeeElem>
      <ns2:id>
        <xsl:value-of select="/wd:Report_Data/wd:Report_Entry/wd:Employee_ID"/>
      </ns2:id>

      <xsl:for-each select="$v/var">
        <xsl:choose>
          <xsl:when test=".='legal'">
            <ns2:name>
              <ns2:firstName>        
                <xsl:value-of select="/wd:Report_Data/wd:Report_Entry/wd:Legal_First_Name"/>
              </ns2:firstName>
            </ns2:name>
          </xsl:when>
         <xsl:when test=".='preferred'">
            <ns2:name>
              <ns2:firstName>
                <xsl:value-of select="/wd:Report_Data/wd:Report_Entry/wd:Preferred_First_Name"/>
              </ns2:firstName>
            </ns2:name>
          </xsl:when>

        </xsl:choose>
      </xsl:for-each>
    </ns2:employeeElem>
  </xsl:template>
</xsl:stylesheet>

I created an array variable, so I can control a for-each loop to run precisely twice. I call this "smart", because it is packed with two complicated tricks. We know that XSLT doesn't have index-incremented loop. If you change the size of this array, then you can actually emulate an index-incremented loop!

Another trick I am playing is to create the result with multiple <name> elements. If you don't use for-each, and hard code two <name> elements, then you can't edit your XSLT in design view anymore. It complains that you mapped the <name> multiple times. But if you use above for-each loop , you can still use design view!

Well, more traps here, if you manually change your XSLT version to 2.0, and you use design view to make any changes, whenever you save, it switches version back to 1.0. So you have to remember to manually change it to 2.0 again. 

Sounds messy enough?

Anyway, after I finished my 2.0 trick, and tested in JDEV, it worked beautifully. Then I ported it OSB, it blew up. It took me hours to find out that OSB doesn't like that "smart" 2,0 feature. 

Now I have to bite the bullet and manually edit the whoel XSLT script.


Thursday, September 15, 2011

dateTime duration with XSLT in SOA 11g

This tiny post reminds me the exact reason why I started this blog. I want to collect the things I learned using SOA.

It took me a long time to figure out this tidbit with XSLT before, but I just spent half a day to re-figure it out again. 

In my XSLT I need to calculate the duration based on two dateTime fields. So I used this expression:
    <xsl:variable name="dur">
      <xsl:value-of select="xsd:dateTime($endDt)-xsd:dateTime($stDt)"/>
    </xsl:variable>

However, with JDEV 11g, the default XSLT stylesheet version is "1.0". The above expression doesn't work with "1.0". I have to change the version to "2.0". That's all. With that, "dur" will have a value like "PT2H5M". Then you can maniplate it.

Now I have recorded it here, so I don't have spend another 5 hours to "re-discover" it next time.

Tuesday, September 13, 2011

Tweaking auto-generated XSLT may result in "Element not found" error

I have a JDEV generated XSLT like below.

<?xml version="1.0" encoding="UTF-8" ?>
<?oracle-xsl-mapper
  <!-- SPECIFICATION OF MAP SOURCES AND TARGETS, DO NOT MODIFY. -->
  <mapSources>
    <source type="WSDL">
      <schema location="../BPELProcess1.wsdl"/>
      <rootElement name="process" namespace="http://xmlns.oracle.com/test/testImdXform/BPELProcess1"/>
    </source>
  </mapSources>
  <mapTargets>
    <target type="XSD">
      <schema location="../xsd/D1_InitialLoadIMD.xsd"/>
      <rootElement name="D1-InitialLoadIMD" namespace="http://oracle.com/D1-InitialLoadIMD.xsd"/>
    </target>
  </mapTargets>
  <!-- GENERATED BY ORACLE XSL MAPPER 11.1.1.5.0(build 110418.1550.0174) AT [TUE SEP 13 15:55:59 MST 2011]. -->
?>
<xsl:stylesheet version="1.0"
...
xmlns:ns1="http://oracle.com/D1-InitialLoadIMD.xsd"
...>
  <xsl:template match="/">
    <ns1:D1-InitialLoadIMD>
      <ns1:version>
        <xsl:value-of select="/client:process/client:input"/>
      </ns1:version>
    </ns1:D1-InitialLoadIMD>
  </xsl:template>
</xsl:stylesheet>

Looking at the file, it is very tempting to replace "ns1" with default namespace, i.e. xmlns="http://oracle.com/D1-InitialLoadIMD.xsd", then get rid of "ns1" from the file.

The result may look clean.

<xsl:stylesheet version="1.0"
...
xmlns="http://oracle.com/D1-InitialLoadIMD.xsd"
...>
  <xsl:template match="/">
    <D1-InitialLoadIMD>
      <version>
        <xsl:value-of select="/client:process/client:input"/>
      </version>
    </D1-InitialLoadIMD>
  </xsl:template>
</xsl:stylesheet>

However, this is a fatal attraction. Even though the content should be technically equivalent, but I got an  "Line Number:(13) : Error: "D1-InitialLoadIMD" Element not Found in Target Schema".

It cost me a few hours to sort it out. The morale of the story, be careful when you mess up the namespace with the auto-generated XSLT.