Xpath dynamique – OSB 12c

Dans les versions antérieures à la 12c, il était possible d’intégrer une fonction d’évaluation dynamique de xpath, au travers de la méthode « evaluate » du jar de xalan par exemple.
Suite au passage sur le moteur « Oracle XDK XSLT » il est désormais plus compliqué d’implémenter cette fonctionnalité.
Voici donc un exemple d’implémentation de « custom xpath » fonction permettant de réaliser du « xpath dynamique ».
 
 

1 – Création d’un jar implémentant une méthode « evaluate ».

Cette méthode possède 2 paramètres en entrée, une expression Xpath d’une part et un objet XML sur lequel appliquer le Xpath. La classe « UniversalNamespaceResolver » permet de résoudre les namespaces déclarés dans la balise racine de l’objet XML.

 public static String evaluate(final String expression, org.apache.xmlbeans.XmlObject xmlObject) throws XPathFunctionException {
        String str="";
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        factory.setNamespaceAware(true);
        try {
            //parse initial xmlObject
            InputStream inputStream = xmlObject.newInputStream();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(inputStream);
            document.getDocumentElement().normalize();
            //initialize xpath object and get namespace context from root node.
            XPathFactory xpf = XPathFactory.newInstance();
            XPath path = xpf.newXPath();
            path.setNamespaceContext(new UniversalNamespaceResolver(document));
            //create a new document removing xml-fragment element
            DocumentBuilder builderWithoutFragment = factory.newDocumentBuilder();
            Document documentWithoutFragment = builderWithoutFragment.newDocument();
            Node firstChildNode = document.getDocumentElement().getChildNodes().item(0);
            Node importedNode = documentWithoutFragment.importNode(firstChildNode,true);
            documentWithoutFragment.appendChild(importedNode);
            str = path.evaluate(expression, documentWithoutFragment);
            return str;
        } catch (Exception exc) {
            throw new XPathFunctionException(exc.toString() +
                                             " - Failed to evaluate xpath expression in custom xpath function.");
        }
    }
public class UniversalNamespaceResolver implements NamespaceContext {
    // the delegate
        private Document sourceDocument;
        /**
         * This constructor stores the source document to search the namespaces in
         * it.
         *
         * @param document
         *            source document
         */
        public UniversalNamespaceResolver(Document document) {
            sourceDocument = document;
        }
        /**
         * The lookup for the namespace uris is delegated to the stored document.
         *
         * @param prefix
         *            to search for
         * @return uri
         */
        public String getNamespaceURI(String prefix) {
            if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
                return sourceDocument.lookupNamespaceURI(null);
            } else {
                return sourceDocument.lookupNamespaceURI(prefix);
            }
        }
        /**
         * This method is not needed in this context, but can be implemented in a
         * similar way.
         */
        public String getPrefix(String namespaceURI) {
            return sourceDocument.lookupPrefix(namespaceURI);
        }
        public Iterator getPrefixes(String namespaceURI) {
            // not implemented yet
            return null;
        }
}

2 – Modification des fichiers  « osb-built-in.properties » et « osb-built-in.xml » situés dans $ORACLE_HOME\osb\config\xpath-functions.

 
Ces fichiers permettent de déclarer la nouvelle fonction « evaluate » afin qu’elle soit reconnue dans l’osb.

%MY_FUNCTIONS%=My custom Bus Functions
%FUNC_EVALUATE_COMMENT%=evaluate xpath over a document E.g. evaluate(expression as xs:string,xml as xs:tree)
<xpf:category id="%MY_FUNCTIONS%">
        <xpf:function>
            <xpf:name>evaluate</xpf:name>
            <xpf:comment>%FUNC_EVALUATE_COMMENT%</xpf:comment>
            <xpf:namespaceURI>http://www.my-custom-xpath.com/xpath</xpf:namespaceURI>
            <xpf:className>com.monpackage.EvaluateXpath</xpf:className>
            <xpf:method>java.lang.String evaluate(java.lang.String, org.apache.xmlbeans.XmlObject)</xpf:method>
            <xpf:isDeterministic>false</xpf:isDeterministic>
            <xpf:scope>Pipeline</xpf:scope>
            <xpf:scope>SplitJoin</xpf:scope>
        </xpf:function>
    </xpf:category>

remarque : un redémarrage du noeud d’administration et OSB est nécessaire pour une prise en compte du jar et de la déclaration de la nouvelle fonction xpath.

3 – Exemple de xquery utilisant la fonction « evaluate ».

La fonction « addContext » permet de déclarer une liste de namespace qui seront ensuite « résolus » par la classe « UniversalNamespaceResolver » afin d’être utilisés dans les expressions xpath.
La fonction « evaluate » utilise la fonction custom « evaluate ».

declare function local:addContext($document as element()) as element()
{
   <context xmlns:con="http://www.bea.com/wli/sb/context"
            xmlns:tran="http://www.bea.com/wli/sb/transports"
            xmlns:http="http://www.bea.com/wli/sb/transports/http"
            xmlns:conf="http://www.bea.com/wli/sb/stages/transform/config"
            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
            xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
            xmlns:jca="http://www.bea.com/wli/sb/transports/jca">{$document}</context>
};
declare function local:evaluate($expression as xs:string,
                                $document as element())
                                as element() {
        <evaluateResult>{xpatVN:evaluate($expression,local:addContext($document))}</evaluateResult>
};