Mostrando entradas con la etiqueta XPath. Mostrar todas las entradas
Mostrando entradas con la etiqueta XPath. Mostrar todas las entradas

martes, 11 de agosto de 2020

WSO2 EI: Como validar correctamente de forma manual

Hoy vamos a ver como hacer validaciones a los atributos o parámetros de entrada en una integración con WSO2. En apariencia puede ser algo sencillo, pero lo veremos con más atención puesto que puede ser algo que nos de más dolores de cabeza de lo que puede aparentar. 

Si buscamos un poco por Google encontraremos fácilmente que la opción más rápida y común es utilizar la XPath function boolean. Pero aquí veremos que no es así en todos los casos y que por tanto es mejorar contemplar más opciones. 

<filter xpath="boolean($ctx:user)">
   mediator+
</filter>

Dejemos claro también que si lo que queremos es validar query parameters y estamos usando una API, podremos hacerlo a través del atributo URL mappings pertinente. Y si lo que queremos es validar la request en una API podemos usar el mediator validate. 

Pero supongamos que por motivos externos no podemos y necesitamos validar uno a uno dichos atributos o parámetros. Supongamos por tanto que nos llega un mensaje JSON con un atributo denominado user. Y presupongamos también que obtenemos ese atributo y lo almacenamos en la propiedad de contexto user:

<property name="user" expression="json-eval($.user)" scope="default" type="STRING"/>

El primer caso práctico será probar que pasa cuando enviamos un valor o no asociado al atributo. Y como se comporta la función boolean.

1. Si enviamos {"user":""}. Funcionará correctamente y la expresión XPath devolverá false. Por lo que podremos realizar la validación correctamente. 

2. Si enviamos el payload vacío {}. También funcionará correctamente, puesto que la expresión XPath devolverá false. 

3. ¿Pero qué pasa si enviamos el payload {"user:null}. La expresión XPath también debería devolver false, puesto que el atributo es nulo. Pero he aquí que en contra de lo en aparente, el resultado de la función será true. 

Si intentamos cambiar la validación de boolean($ctx:user) por string($ctx:user) != ''. No tendremos ningún avance puesto que funcionará exactamente igual. 

Para poder corregir esta casuística deberemos realizar la siguiente validación:

<filter xpath="boolean($ctx:user) and string($ctx:user) != 'null'">
   mediator+
</filter>

En principio con estas dos funciones tendríamos las distintas casuísticas. Para terminar un par de detalles. 

1. Cuando queremos almacenar el valor de un atributo o parámetro de la request podemos indicar que tipo de objeto es: String, Integer, etc. Hay que tener cuidado con esto siempre que no se elija String, porque si el el campo es vacío o nulo puede fallar la lógica al intentar convertirlo. 

2. Puede ser un poco engorroso la validación manual si tenemos muchos atributos. Para ello podemos crearnos nuestro propio template, que nos ayude a evitar duplicar código. Aquí os pongo un simple ejemplo:

<template name="validate">
    <parameter name="field"/>
    <parameter name="text"/>
    <sequence>
        <filter xpath="boolean($func:field) and string($func:field) != 'null'">
            <then/>
            <else>
                <payloadFactory media-type="json">
                    <format>{"code":400, "exception":"message not valid","detail":"$1"}</format>
                    <args>
                        <arg evaluator="xml" expression="$func:text" literal="false"/>
                    </args>
                </payloadFactory>
                <property name="HTTP_SC" scope="axis2" type="STRING" value="400"/>
                <property name="messageType" scope="axis2" type="STRING" value="application/json"/>
                <respond/>
            </else>
        </filter>
    </sequence>
</template>

Y podemos invocarlo de la siguiente manera:

<property name="user" expression="json-eval($.user)" scope="default" type="STRING"/>
<call-template target="validate">
    <with-param name="field" value="{$ctx:user}"/>
    <with-param name="text" value="user doesnt exist"/>
</call-template>

sábado, 14 de diciembre de 2019

PMD: Como hacer un custom rule

Hoy vamos a ver un poco de PMD, una magnífica herramienta que nos permite medir la calidad de nuestro código.  Es en general una herramienta imprescindible en nuestro día a día y muy completa si hablamos de código Java. Pero no tanto para otros lenguajes, aunque de soporte para ellos. Por tanto hoy hablaremos de cómo crear una regla, pero para XML. 

Para empezar debemos tener un concepto claro: Abstract Syntax Tree, AST. Este es el árbol de elementos de los que se compone nuestro código. Es decir, a través de él, podremos identificar cada uno de los elementos de nuestro código y por tanto asociar una regla que nos indique si esta bien creado el código o no. Para ayudarnos en esta tarea, tenemos la herramienta PMD Rule Designer, con la cual tras pasarle un trozo de código, nos devolverá su AST asociado.


Una vez tenemos claro cual es la regla que queremos implementar y sobre qué elemento del código queremos implementarla, pasaremos a escribirla. La implementación de la regla la podemos realizarla en código Java o a través de XPath. Pero su definición,  deberemos crearla siempre en XML.

Esta definición de la regla, se debe incluir en un fichero que contenga un conjunto de reglas o ruleset. Este ruleset puede ya existir (por ejemplo el básico para XML) o crearlo nosotros desde cero.

<?xml version="1.0"?>
<ruleset name="XML Own Convention"
 xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0 https://pmd.sourceforge.io/ruleset_2_0_0.xsd">
 <description>Rules to detect if our XML is how we want.</description>
</ruleset>

Ahora nos quedaría crear la regla o rule. En ella debemos indicar diferentes atributos informativos de la regla como pueden ser el nombre, mensaje asociado cuando se muestre al usuario que la regla está siendo violada o un ejemplo de cómo podemos evitar la violación de la misma.

Si quisiéramos generarla con Java, escribiríamos una clase que heredase de net.sourceforge.pmd.lang.java.rule.AbstractJavaRule. Y que recibiese el elemento sobre el cual queremos realizar el análisis. Esta clase debería ser indicada en la definición de la regla a través del atributo class.

<rule name="WhileLoopsMustUseBracesRule"
  message="Avoid using 'while' statements without curly braces"
  class="WhileLoopsMustUseBracesRule">
 // description, priority or example properties
</rule>

En el caso de querer hacerla a traves de XPath. En el atributo class debemos indicar net.sourceforge.pmd.lang.rule.XPathRule y posteriormente crear una propiedad denominada xpath que incluya la regla que queremos validar. Para nuestro ejemplo, queremos verificar por ejemplo que una secuencia de WSO2, termine siempre con el sufijo '_seq'. Para ello implementaremos una regla XPath que realice exactamente lo contrario, que busque si dado un elemento sequence, su atributo name no termine en '_seq'.

<rule name="sequenceBadNamed" language="xml" 
 message="Incorrect sequence name."
 class="net.sourceforge.pmd.lang.rule.XPathRule">
 <description>A sequence was wrongly named, it must end with _seq</description>
 <priority>3</priority>
 <properties>
  <property name="xpath">
   <value>
    <![CDATA[/sequence[not(ends-with(@name, '_seq'))]]]>
   </value>
  </property>
 </properties>
 <example>
  <![CDATA[A sequence must be called: $Service_$SeqName_seq]]>
 </example>
</rule>

Como se puede ver el uso de XPath te puede permitir realizar reglas muy potentes y de forma sencilla, sin la necesidad de crear más clases.

Por último nos queda configurar el plugin de maven para el uso de validaciones con XML y nuestro conjunto de reglas. Para ello debemos:
  • Añadir como dependencia la librería específica de PMD para dicho lenguaje, en nuestro caso pmd-xml.
  • Configurar correctamente el apartado configuración.
    • Indicar que son validaciones para XML.
    • Indicar nuestro conjunto de reglas.
    • Indicar que los ficheros XML sean analizados. 

<plugin>
 <groupId>org.apache.maven.plugins</groupId>
 <artifactId>maven-pmd-plugin</artifactId>
 <version>3.12.0</version>
 <dependencies>
  <dependency>
   <groupId>net.sourceforge.pmd</groupId>
   <artifactId>pmd-xml</artifactId>
   <version>6.3.0</version>
  </dependency>
 </dependencies>
 <configuration>
  <language>xml</language>
  <rulesets>
   <ruleset>${project.basedir}/src/test/resources/ownConvention.xml</ruleset>
  </rulesets>
  <includes>
   <include>**/*.xml</include>
  </includes>
 </configuration>
</plugin>

lunes, 5 de agosto de 2019

WSO2 Mediator Tutorial: Como funciona el iterate

Hoy vamos a ver el funcionamiento y un ejemplo practico del mediator Iterate de WSO2. A traves de este mediador podremos dividir el mensaje en distintas partes, ejecutar una logica asociada a cada uno de ellas y luego enviarlas.  

Ademas tambien puedes elegir si seguir con el flujo normal despues del mediador o no. Y si simplemente queremos dividir el mensaje pero no asociar una logica de envio a cada una de sus partes, es recomendable utilizar el mediador ForEach. 

A continuacion vamos a indicar los atributos que componen a dicho mediador:
  • expression: Campo obligatorio. Expresion XPath (JsonPath a partir del EI 6.5) a traves de la cual indicaremos sobre que elemento deseamos iterar y particionar el elemento.
  • attachPath: Expresion XPath que nos permite indicar la ubicacion dentro del mensaje en la cual queremos 'pegar' el elemento sobre el que iteramos. 
  • sequential: Booleano que nos permite indicar si queremos que se realice de forma sequencial o paralela. 
  • continueParent: Booleano que nos permite indicar si queremos continuar con el flujo habitual despues del iterate. 
  • preservePayload: Booleano que nos permite indicar si queremos usar el mensaje como plantilla del mensaje resultante de las particiones a realizar por el mediador. 
Si usamos la opcion preservePayload, debemos tener en cuenta que el el mensaje a usar como plantilla, no contendra el elemento por el cual queremos iterar. Si queremos que lo contenga, deberemos usar la opcion attachPath. 

Dentro del mediador indicaremos como queremos realizar la logica a aplicar a cada mensaje. Estas son las distintas opciones:
  • soapAction: Atributo que nos permite indicar dicho atributo en caso de ser necesario
  • to: Atributo que nos permite indicar una direccion a donde enviar el mensaje
  • sequence: Nos permite indicar una secuencia donde incluir la logica a realizar sobre cada una de las iteraciones. Permitiendonos escoger una secuencia ya definida, escribirla directamente o no seleccionar ninguna y por tanto no realizar ninguna logica.
  • endpoint: Nos permite indicar el endpoint hacia el cual enviaremos el mensaje. Pudiendo configurarlo de forma habitual en WSO2, como por ejemplo se hace para los proxy services. 
Sobre estas opciones indicar que al menos hay que poner o una secuencia que realice el envio o un endpoint. Y que ademas tampoco tiene mucho sentido utilizar a la vez el atributo to y el elemento endpoint, puesto que solo se tendra en consideracion el elemento endpoint. 

A continuacion indicamos un ejemplo en el cual recibiremos el siguiente mensaje:

<books>
<book><id>1</id></book>
<book><id>2</id></book>
<book><id>3</id></book>
</books>

Y configuraremos nuestro mediador de la siguiente forma:
  • Iteraremos sobre el nodo book. A traves del atributo expression.
  • Usaremos el mensaje acutal como plantilla de los siguientes mensajes. A traves de preservePayload. 
  • Incluiremos el contenido sobre el que iteramos dentro del nodo raiz. A traves de attachPath.
  • Evitaremos que se siga ejecutando el flujo despues del mediador. Uso por defecto de continueParent.
  • Enviaremos cada nuevo trozo de mensaje generado a nuestro propio proxy de eco. A traves del atributo to y evitando definir ningun endpoint a nivel del iterate o del send. 
<?xml version="1.0" encoding="UTF-8"?>
<sequence name="exampleIterateMediator" xmlns="http://ws.apache.org/ns/synapse">
    <iterate attachPath="$body/node()" expression="//book" preservePayload="true">
        <target to="http://localhost:8280/services/myOwnEchoProxy">
            <sequence>
                <log level="custom">
                    <property name="msg" value="inside iterate"/>
                    <property expression="$body" name="full body"/>
                    <property expression="//book" name="message splitted"/>
                </log>
                <send/>
            </sequence>
        </target>
    </iterate>
</sequence>

sábado, 5 de enero de 2019

WSO2: Diferencia entre PayloadFactory y Enrich Mediator


Ultimamente estoy trabajando con WSO2 y me había estado resistendo a crear posts de algo tan específico. Pero creo que poco a poco vamos a hacer ejemplos básicos de esta técnologia de integración. 

Para empezar vamos a realizar un post sobre dos de los mediators más comunes que podemos utilizar dentro de una sequencia del Enterprise Integrator. Como son 'PayloadFactory' y 'Enrich'.  El primero de ellos nos permitirá modificar el cuerpo de la respuesta y crear uno nuevo a nuestra conveniencia. El segundo nos permitirá modificar el cuerpo de la respuesta pero solo añadiendole nuevos valores. 

Para el ejemplo partiremos en ambos casos de una API de prueba. Donde en la sequencia de entrada llamaremos a un wiremock que nos devolverá la respuesta mostrada a continuación. Y será esta respuesta la que va a ser modificada por los dos mediators:

{"bookstore": [
      {"name": "the starts my destination", "author": "Alfred Bester"},
      {"name": "Ender's Game", "author": "Orson Scott Card"}
]}

Con el 'PayloadFactory' mediator, vamos a indicar cual queremos que sea nuestra nueva respuesta. Nos permite incluso crear una nueva respuesta completamente estática, aunque no tenga mucho sentido. Para darle dinamismo tenemos los 'arguments', estos argumentos los indicaremos dentro del cuerpo de la respuesta como '$x', siendo 'x' el ordinal del mismo en la lista global de argumentos. Pueden ser obtenidos de 4 formas: a través de XPath o JSONPath sobre el cuerpo de la actual respuesta, mediante valores literales o con propiedades ya almacenadas en cualquiera de los ambitos dentro del EI. A continuación veremos un ejemplo con JSONPath, literales y valores del contexto:

<payloadFactory media-type="json">
        <format>
{"books":[{"book":[{"name":"$1","author":"$3"}]},{"book":[{"name":"$2","author":"$4"}]}],"total":"$5","description":"$6"}
            </format>
        <args>
            <arg evaluator="json" expression="$.bookstore[0].name" literal="true"/>
            <arg evaluator="json" expression="$.bookstore[1].name" literal="false"/>
            <arg evaluator="json" expression="$.bookstore[0].author" literal="false"/>
            <arg evaluator="json" expression="$.bookstore[1].author" literal="false"/>
            <arg literal="false" value="2"/>
            <arg evaluator="xml" expression="$ctx:description" literal="false"/>
        </args>
    </payloadFactory>

Generando la siguiente salida:

{"books":[
  {"book":[{"name":"the starts my destination","author":"Alfred Bester"}]},
  {"book":[{"name":"Ender's Game","author":"Orson Scott Card"}]}],
  "total":"2","description":"list of books"}

Ahora veremos como funciona el 'Enrich' mediator. Con el basicamente le indicaremos una fuente y un destino, pudiendo realizar 3 tipos de acciones diferentes: añadir como hijo, añadir como hermano o reemplazar. La configuración para la fuente y el destino son similares, se pueden elegir que sean de 4 tipos: personalizada en base a un XPath, envelope del mensaje original, el cuerpo del mensaje original o una propiedad de un ambito. Aunque existe una matrix de compatibilidades para los distintos tipos de fuentes y destinos, por lo que no vale cualquier combinación. Aquí vemos un ejemplo donde añadimos un hijo, un hermano y reemplazamos un valor.

<enrich>
    <source clone="false" type="inline">
        <total>2</total>
    </source>
    <target action="child" type="body"/>
</enrich>
<enrich>
    <source clone="false" type="inline">
        <description>list of books</description>
    </source>
    <target action="sibling" type="custom" xpath="//bookstore"/>
</enrich>
<property name="shortName" scope="default" type="STRING" value="Orson S. Card"/>
<enrich>
    <source clone="false" property="shortName" type="property"/>    <target action="replace" type="custom"
            xmlns:ns="http://org.apache.synapse/xsd" xpath="//bookstore[2]/author"/>
</enrich>

La salida en este caso sería la siguiente:

{"bookstore": { "name": "Ender's Game", "author": "Orson S. Card" }
,"description": "list of books", "total": 2}

Por ulitmo, con enrich una utilidad común es guardar la respuesta original, aunque también lo podemos hacer con el 'Property' mediator. Y luego restablecer dicha respuesta original. Muy util para cuando tienes que hacer llamadas intermedias en tu sequencia.

<property name="REQUEST_PAYLOAD" expression="$body" />
<!-- more code -->
<enrich>
    <source clone="true" xpath="$ctx:REQUEST_PAYLOAD" />    <target type="body" />
 </enrich>