¿Cómo crear una expresión XPATH custom, para Oracle SOA SUITE 11g?

Esta es una aportación de un gran amigo, él es Andrés Ramírez (Perú). Gracias, mano, por tu gran aportación.


=====
Siendo que BPEL es un lenguaje basado 99% en XML, es muy común tener la necesidad de validar si el contenido de un XML en particular, se encuentra alineado al XSD (Schema) que lo define. Para esto Oracle provee una actividad llamada VALIDATE:
clip_image002

En este artículo se explora el uso de esta actividad y sus beneficios bajo su implementación en BPEL.


Si esta variable detecta que el documento XML no coincide con las restricciones del XSD asociado, generará una excepción del sistema (SystemFault) de tipo “invalidVariable”.
NOTA: Encuentro una limitante con esta funcionalidad y es que solo me va a mostrar el primer elemento XML que generó el error de validación de la siguiente manera:
Invalid data: The value for variable "<variable_bpel>", part "payload" does not match the schema definition for this part Invalid text 'aaaa' in element: '<elemento_xml_con_problemas>'.
Es decir, que si mi documento tiene “n” fallas, para saber que partes están erróneas, tendré que ejecutar “n” veces el proceso BPEL hasta corregir todo (asumiendo que el cliente no valide su request antes de enviarlo).
Ahora, lo más probable es que si encontramos este error, querremos responderle al cliente en que consistió el error (o errores) con el XML que envió a fin que pueda corregirlos, y para esto, lo lógico sería utilizar un CATCH asociado al SystemFault - invalidVariable:
clip_image004
clip_image006
Y es precisamente aquí donde tenemos un problema. Lo normal sería definir un “Fault Variable” para recibir el contenido del error (código, resumen y detalle del mismo) para reenviarlo al usuario. Sin embargo, al definir la variable, la excepción no llega a ser capturada por este Catch y se va automáticamente al CatchAll
clip_image008
Para lograr que sea accionado este Catch, debemos dejar el campo “Fault Variable” en blanco, pero hacerlo implica que para obtener la excepción debemos usar la función: ora:getFaultAsString().
clip_image010
Esto podría ser suficiente, salvo el pequeño problema (pienso que es un BUG en realidad, aunque según leí en un foro, Oracle dice que es una funcionalidad aun no soportada y que de momento funciona como es esperado, es decir, NO FUNCIONA) que cuando obtiene el detalle de la excepción, en el camino se pierde la descripción de que elemento XML generó el problema de validación contra el XSD.
clip_image012
En ese sentido, me vi en la necesidad de crear una función XPath “custom” para realizar la validación del XML utilizando el API Java para XML. A groso modo, los pasos a seguir son los siguientes:
I. Pasos en JDeveloper:

1. Crear un proyecto Java donde implementaremos una Clase con la funcionalidad “custom”
2. Agregar el XML de definición de la nueva funcionalidad XPath
3. Empaquetar la aplicación en un JAR
4. Registrar el JAR en el JDeveloper
5. Utilizar la nueva función XPath en un composite
II. Pasos en SOA Server:

6. Registrar el JAR en el classpath del servidor SOA
7. Probar el composite que hace uso de la función XPath creada
Descripción de los pasos:
1. Crear un proyecto Java donde implementaremos una Clase con la funcionalidad “custom”
a. Crear un proyecto Java y agregar la librería “SOA Runtime” pues esta contiene la interfaz que debemos implementar
clip_image014
b. Crear la clase que realizará la validación del documento XML contra el XSD
package ------- .utils.xml;

clip_image015
import java.io.ByteArrayInputStream; import java.io.InputStream;
import java.net.URL;
import java.util.List;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource; import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory; import javax.xml.validation.Validator;
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.Document; import org.w3c.dom.Element;
public class SchemaValidation implements IXPathFunction { public SchemaValidation() {
}
public Object call(IXPathContext iXPathContext, List list) throws XPathFunctionException { return validateXml(iXPathContext, list);
}
public static Object validateXml(IXPathContext iXPathContext, List list) throws XPathFunctionException { // Init
Document doc = null; DocumentBuilderFactory docFactory = null; DocumentBuilder docBuilder = null; Element rootElement = null;
// Instanciar DOM factory y crear estructura base del XML de respuesta try {
docFactory = DocumentBuilderFactory.newInstance(); docBuilder = docFactory.newDocumentBuilder();
doc = docBuilder.newDocument();
rootElement = doc.createElement("XMLSchemaValidation"); doc.appendChild(rootElement);
} catch (Exception e) { e.printStackTrace();
}
try {
//System.out.println("Paso 1");
// 1. Instanciar factory para W3C XML Schema language
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
//System.out.println("Paso 2");
// 2. Compilar XSD e instanciar Validator -> parametro (0) = xsdLocation URL xsdUrl = new URL(list.get(0).toString());
Schema schema = factory.newSchema(xsdUrl); Validator validator = schema.newValidator();
//System.out.println("Paso 3");
// 3. Parsear XML -> parametro (1) = xmlString
InputStream xmlInputStream = new ByteArrayInputStream(list.get(1).toString().getBytes("UTF-8")); Source source = new StreamSource(xmlInputStream);
//System.out.println("Paso 4"); // 4. Validar XML contra XSD
ValidateErrorHandler validateErrorHandler = new ValidateErrorHandler(); validator.setErrorHandler(validateErrorHandler); validator.validate(source);
//System.out.println("Paso 5");
// 5. Contsruir XML en base al resultado de la validacion if (validateErrorHandler.getErrorMsg() != null) {
// Code element
Element code = doc.createElement("Code"); code.appendChild(doc.createTextNode("1")); rootElement.appendChild(code);
// Summary element
Element summary = doc.createElement("Summary"); summary.appendChild(doc.createTextNode("XML Schema validation error")); rootElement.appendChild(summary);
// Detail element
Element faultDetails = doc.createElement("FaultDetails"); rootElement.appendChild(faultDetails);
String[] splitError;
splitError = validateErrorHandler.getErrorMsg().split(validateErrorHandler.getDELIMITER()); for(int i =0; i < splitError.length ; i++) {
// ErrorMessage elements
Element faultMessage = doc.createElement("FaultMessage"); faultMessage.appendChild(doc.createTextNode(splitError[i])); faultDetails.appendChild(faultMessage);
}
} else {
// Code element
Element code = doc.createElement("Code"); code.appendChild(doc.createTextNode("0")); rootElement.appendChild(code);
// Summary element
Element summary = doc.createElement("Summary"); summary.appendChild(doc.createTextNode("XML is valid")); rootElement.appendChild(summary);
// Detail element
Element faultDetails = doc.createElement("FaultDetails"); faultDetails.appendChild(doc.createTextNode("")); rootElement.appendChild(faultDetails);
}
}
catch (Exception e) { // Code element
Element code = doc.createElement("Code"); code.appendChild(doc.createTextNode("1")); rootElement.appendChild(code);
// Summary element
Element summary = doc.createElement("Summary"); summary.appendChild(doc.createTextNode("XML Schema validation unknown error")); rootElement.appendChild(summary);
// Detail element
Element faultDetails = doc.createElement("FaultDetails"); rootElement.appendChild(faultDetails);
// ErrorMessage elements
Element faultMessage = doc.createElement("FaultMessage"); faultMessage.appendChild(doc.createTextNode(e.getMessage())); faultDetails.appendChild(faultMessage);
return doc;
}
return doc;
}
}
Como consideración adicional, se ha creado una clase llamada ValidateErrorHandler la cual nos permitirá controlar los errores generados por la validación en lugar de ir al catch al primer error que encuentre. Así podremos tener la capacidad de concatenar todos los errores que se detecten en la validación del XML.
Clase ValidateErrorHandler.java:
package ------- .utils.xml;

clip_image015[1]
import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException;
public class ValidateErrorHandler implements ErrorHandler {
private String errorMsg = null;
private static final String DELIMITER = "&";
public void warning(SAXParseException exception) throws SAXException { System.out.println("\nWARNING"); System.out.println(exception.getMessage());
if (errorMsg != null)
errorMsg = errorMsg + DELIMITER + exception.getMessage();
else
errorMsg = exception.getMessage();
}
public void error(SAXParseException exception) throws SAXException { System.out.println("\nERROR"); System.out.println(exception.getMessage());
if (errorMsg != null)
errorMsg = errorMsg + DELIMITER + exception.getMessage();
else
errorMsg = exception.getMessage();
}
public void fatalError(SAXParseException exception) throws SAXException { System.out.println("\nFATAL ERROR"); System.out.println(exception.getMessage());
if (errorMsg != null)
errorMsg = errorMsg + DELIMITER + exception.getMessage();
else
errorMsg = exception.getMessage();
}
public String getErrorMsg() { return errorMsg;
}
public static String getDELIMITER() { return DELIMITER;
}
}
Uso de la clase:
//System.out.println("Paso 4"); // 4. Validar XML contra XSD
ValidateErrorHandler validateErrorHandler = new ValidateErrorHandler(); validator.setErrorHandler(validateErrorHandler);
2. Agregar el XML de definición de la nueva funcionalidad XPath
a. Crear el XML donde se tendrá la definición del nuevo XPath con el nombre: ext-soa-xpath-functions-config.xml en la ruta: /src/META-INF/ext-soa-xpath-functions-config.xml (esto permitirá que la función esté disponible para cualquier componente del SOA Suite)
clip_image016clip_image016[1]
<?xml version="1.0" encoding="UTF-8"?>
<soa-xpath-functions xmlns="http://xmlns.oracle.com/soa/config/xpath" xmlns:custom="http://www.oracle.com/XSL/Transform/java/------.utils.xml.SchemaValidation">
clip_image017
<function name="custom:validateXml"> <className>------.utils.xml.SchemaValidation</className> <return type="node-set"/>
clip_image017[1]
<params>
<param name="xsdLocation" type="string" wizardEnabled="true" minOccurs="1" maxOccurs="1"/> <param name="xmlAsString" type="string" wizardEnabled="true" minOccurs="1" maxOccurs="1"/>
</params>
<desc>Retorna el resultado de la validacion del XML contra el XSD indicado</desc>
<detail>Retorna el resultado de la validacion del documento XML (enviado como "String") contra el
XSD (URL del XSD como "String"). La respuesta es de tipo node-set siguiente la siguiente estructura "XMLSchemaValidation" definida en el schema: CustomXpathFunctions.xsd
</detail> </function>
</soa-xpath-functions>
Considerar que:
· El namespace debe tener la estructura:
“http://www.oracle.com/XSL/Transform/java/<ruta_completa_de_la_clase>
· El prefijo (en este caso “custom” puede tener cualquier nombre)
· En el elemento “function” debemos colocar el método donde se ha implementado la funcionalidad utilizando el prefijo definido en el paso anterior (custom)
· En el return type, definimos que la respuesta va a venir en la forma de un XML
· En los parámetros especificamos que se van a tener 2 parámetros de tipo string
Como resultado tendremos la siguiente estructura:
clip_image019
3. Empaquetar la aplicación en un JAR (schemavalidationcustomxpath.jar)
4. Registrar el JAR en el JDeveloper
Tools à Preferences à SOA à Add à schemavalidationcustomxpath.jar
Luego reiniciar el JDeveloper
5. Utilizar la nueva función XPath en un composite
a. Antes de utilizar la función, es necesario definir el XSD asociado al tipo de respuesta de nuestro custom xpath (CustomXpathFunctions.xsd):
clip_image016[2]clip_image016[3]
<?xml version="1.0" encoding="windows-1252" ?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="XMLSchemaValidation"> <xsd:complexType>
<xsd:sequence>
<xsd:element name="Code" type="xsd:string"/> <xsd:element name="Summary" type="xsd:string"/>
<xsd:element name="FaultDetails" maxOccurs="1" minOccurs="0"> <xsd:complexType>
<xsd:sequence>
<xsd:element name="FaultMessage" maxOccurs="unbounded" minOccurs="0" type="xsd:string"/>
</xsd:sequence> </xsd:complexType>
</xsd:element> </xsd:sequence>
</xsd:complexType> </xsd:element>
</xsd:schema>
b. Con el XSD definir, crearemos una variable BPEL para recibir la respuesta de la validación:
clip_image021
c. Para utilizar la nueva función XPath podemos usar una actividad Assign y asignar el valor a la nueva variable utilizando un expression: custom:validateXml('<url_del_xsd>',oraext:get-content-as-string(<xml_a_validar>))
Ejemplo:
custom:validateXml(' http://soabpm-vm:7001/soa-infra/services/default/<composite>/<bpel_process>?XSD=xsd/<bpel_process_xsd>.xs d',oraext:get-content-as-string($receive_--------------.payload))
6. Registrar el JAR en el classpath del servidor SOA
Para registrar el JAR será necesario copiarlo en la ruta: /<FMW_HOME> /Oracle_SOA1/soa/modules/oracle.soa.ext_11.1.1
Ejecutar en commando ANT para que la librería se registre dentro del classpath del JAR.
clip_image022clip_image024
[oracle@soa.ext_11.1.1]$ ../../../../modules/org.apache.ant_1.7.1/bin/ant Buildfile: build.xml
create-manifest-jar:
[echo] Creating oracle.soa.ext at
…/FMW/Oracle_SOA/soa/modules/oracle.soa.ext_11.1.1/oracle.soa.ext.jar…
BUILD SUCCESSFUL Total time: 1 second
NOTA: Recomiendo usar la última version de ANT
Finalmente reiniciar el servidor SOA!!
7. Probar el composite que hace uso de la función XPath creada Resultado OK:
Resultado con error:
clip_image026