lunes, 26 de enero de 2015

Smooks framework y WSO2 ESB. I

introduction1

El framework Smooks es considerado una herramienta poderosa para el procesamiento, la manipulación y la transformación de datos entre distintos formatos, ya sea XML o no XML. Y posee una documentación que a través de ejemplos nos permite asimilarlo muy facilmente.

El WSO2 ESB posee un mediador que nos permite hacer uso de este framework en los servicios proxy con lo cual podemos muy facilmente llevar datos de un formato a otro, como veremos en esta entrada.

Para ilustrar el uso de este framework primero mostraremos en esta entrada como desarrollar una transformación sencilla haciendo uso del Developer Studio, y en una segunda entrada veremos como mejorar un servicio proxy haciendo uso del mediador smooks.
Comenzaremos asumiendo que tenemos la necesidad de transformar de un formato XML generado por una “Aplicación A1” al formato que usa una “Aplicación A2”.

Formato de una orden para la “Aplicación A1”
<order id='001'>  
    <header>  
        <customer number="002">Jorge</customer>  
    </header>  
    <order-items>  
        <order-item id='1'>  
            <product>2</product>  
            <quantity>2</quantity>  
            <price>100</price>  
        </order-item>  
    </order-items>  
</order>

Formato de una orden para la “Aplicación A2”
<salesorder>  
   <details>  
     <orderid></orderid>  
     <customer>  
 <id></id>  
 <name></name>  
     </customer>  
   </details>  
   <itemList>  
     <item>  
 <id></id>  
 <productId></productId>  
 <quantity></quantity>  
 <price></price>  
     </item>  
   </itemList>  
</salesorder>  

Lo que se quiere hacer entonces es que ambos sistemas puedan integrarse entre sí con una afectación mínima en su utilización. Como ven aquí hay que usar el patrón de Transformación de mensajes.

Para esto hay varias soluciones:

  1. Modificar el primer sistema para que emita las órdenes en el formato del segundo sistema. Esto tiene la complicación de que estaríamos atando un sistema al otro lo que no es una buena práctica a seguir.
  2. Modificar el sistema 2 para que acepte las órdenes en el formado del sistema 1 y las modifique si es necesario. El problema con este enfoque es que si el sistema 1 cambia su forma de emitir las órdenes entonces el sistema 2 tendrá que ser modificado. Y todos sabemos que modificar un sistema significa sacarlo de producción, nuevas implementaciones, pruebas para comprobar que los cambios no afectaron otras partes del sistema, etc.
  3. Crear un mecanismo, como se expone en el patrón de transformación, que intercepte el mensaje del sistema 1 y lo modifique al formato del sistema 2. De esta manera ninguno de los dos sistemas se verían afectados por el cambio en el otro sistema. La complicación que tiene este enfoque es con el rendimiento y la potencial creación de cuellos de botella en el mecanismo de transformación, pero nada en la vida es perfecto.

En esta entrada nos vamos por el punto 3.

Como había dicho más arriba, Smooks es un framework de JBoss que pertenece a REDHAT Enterprise y este framework puede ser usado para:
  1. Transformaciones: XML/CSV/EDI/Java/JSON a XML/CSV/EDI/Java/JSON.
  2. Java Binding: convertir en un objeto Java cualquier información que provenga de una fuente de datos (CSV, EDI, XML, Java, JSON etc).
  3. Procesamiento de grandes mensajes: con este framework se pueden implementar los mensajes de Separación, Transformación y ruteo de fragmentos de mensajes a distintos destinos tales como JMS, Sistema de archivos, Bases de datos, etc.
  4. Enriquecimiento de mensajes: también con este framework podemos enriquecer los mensajes tal y como planteamos en el patrón Enriquecedor de Contenidos extrayendo la información necesaria de bases de datos o de otras fuentes de datos.
  5. Persistencia de mensajes usando ORM(Mapeo Objeto Relacional): permite el uso de frameworks de persistencia de entidades como Hibernate o cualquier framework compatible con JPA para acceder a la BD y ejecutar operaciones CRUD para leer o escribir.
  6. Combinar: Realiza operaciones ETL (Extract/Transform/Load)

La suite de WSO2 incluye este framework para ser usado dentro del ESB como un mediador y eso es lo que veremos a continuación.

Usando la versión 3.7.1 del WSO2 Developer Studio vamos a crear un entrada local que contendrá la funcionalidad para realizar la transformación requerida.

Paso 1: Crear un “Registry Resource Project”.

crear_proyecto_registry

Paso 2: Crear un “Registry Resource”.

Para hacer este paso damos clic derecho encima del proyecto recién creado, seleccionamos “New”  y luego “Registry Resource”.

crear_registry_resource

Manteniendo la opción seleccionada damos click en ”Next” y en la sección de Template seleccionamos “Smooks Configuration” tal y como se muestra a continuación.

smooks_resource

Damos clic en “Finish” y ya estamos listos para definir nuestra transformación.

Paso 3: desarrollar la transformación.

Ahora demos abrir el fichero “smooks_order_transformation.xml” dando doble click encima del mismo y luego damos click encima de la flecha azul. Veremos lo siguiente:

fichero_smooks

Ahora creamos una carpeta dentro del proyecto y añadimos los xml que usamos al inicio para definir los formatos de entrada y salida.

De esta manera en la sección de “Input Data” adicionamos el fichero input.xml con el contenido del mensaje de entrada, así que podremos ver su formato en pantalla.

input

Ahora podemos dar clic derecho encima de la flecha azul que nos marca la tarea de entrada y seleccionamos “Add Task” y luego “Apply Template”, ahí mantenemos el tipo de mensaje como XML y damos clic en “Next”. En la última pantalla seleccionamos el formato de salida y especificamos el XML de salida a utilizar.

salida

Damos click en finish y veremos lo siguiente:

T1

Lo que viene a continuación es desplegar los distintos elementos y conectar los que deben ser mapeados tal y como mostramos a continuación.

T2

Ahora lo que queda es probar, para ello podemos ir al fichero “smooks_order_transformation.xml” y damos click derecho, seleccionando la opción “Run As” y luego la opción 2. Ahí veremos el siguiente error:

error

 La causa es que no hemos incluido las librerías del framework en el proyecto. Para hacerlo basta ir a la librería de WSO2 y marcar todos los jar de la pestaña de Smooks y eso es todo. Volvemos a probar y vemos el resultado:

resultado

No tengan en cuenta el error mostrado al final pues no afecta el resultado ni su uso dentro del WSO2 ESB.

De esta manera concluímos esta primera parte dejando para la segunda el cómo usar la transformación de smooks dentro del WSO2 ESB.

Enlaces de interés:


 

viernes, 23 de enero de 2015

Exportando nuestras hojas de cálculo como Web Service (Parte I)

En esta entrada podremos ver el uso del DSS para exponer a través de un servicio web los datos que tengamos almacenados dentro de una hoja de cálculo Excel, creo que este escenario lo podemos encontrar hoy día en muchos lugares donde se tenga alguna información en formato digital.

Espero les pueda servir.
 


viernes, 16 de enero de 2015

Introducción al ESB de WSO2 a través de ejemplos prácticos. VI

Existe una empresa, a la cual llamaremos “Empresa A” que posee un sistema de órdenes de compra que genera diariamente un fichero con los datos de las órdenes a realizar.
Este fichero es procesado por el personal de la empresa el cual se encarga de notificar del recibo de la orden a los destinatarios de la misma y de insertar las órdenes en una base de datos.

La “Empresa A” ha decidido automatizar el proceso, como un primer paso en un desarrollo general de sus sistemas a manera de PoC, y está buscando una solución que le permita un desarrollo rápido y que pueda evolucionar en el tiempo para integrar esta funcionalidad de negocio con otros sistemas que se están diseñando. La idea es que si se logra un éxito en este primer paso se podrá continuar con los siguientes en poco tiempo.

Bajo este esquema, la “Empresa A” nos ha pedido realizar dicha prueba de concepto, casi el primer paso completo, y para ello nos han dado los siguientes requerimientos:

  1. El fichero generado por el sistema antiguo puede encontrarse en el filesystem del mismo servidor donde esté la solución o puede estar en un ftp, aun no se deciden. Así que la solución debe permitir cambiar rápidamente de ubicación sin mucha complicación y con 0 codificación, solo configuración.
  2. En caso de que el fichero se almacene en un ftp se debe proporcionar algún mecanismo de seguridad, mínimo usuario y contraseña. Aunque aun está por determinar si se usará sftp, ftps, o algún otro mecanismo.
  3. Cada orden será una línea en el fichero y contendrá el ID de la orden, el ID del cliente, la fecha de la orden y el monto de la misma. Esto para la PoC pues la orden contiene mucha más información.
  4. Cada orden debe ser guardada en BD para su uso por futuros sistemas, además de permitir llevar un registro de las órdenes emitidas.
  5. Los clientes deben ser notificados tan pronto el sistema inicia el procesamiento de la orden, incluyendo los datos de la misma.
  6. En BD se almacenan los datos de los clientes así que la solución debe ser capaz de consultarlos mientras se procesa la orden.
  7. La solución debe ser liviana, para que pueda correr en los servidores de la empresa, los cuales son de prestaciones bajas.
  8. La PoC debe desarrollarse en menos de una semana.

Aunque es un escenario ficticio para mostrar lo fácil que es implementar con WSO2, bien podría ser un requerimiento real.
Revisando estos requerimientos y teniendo en cuenta las características de la “Empresa A”  hemos escogido usar la suite de WSO2 para su desarrollo. Las causas son las siguientes:

  1. No existen servidores de muy buenas prestaciones en “Empresa A” eso hace que debamos buscar una tecnología que corra en 1GB de RAM sin muchos problemas y que no consuma demasiados recursos de CPU y espacio en disco.
  2. Se desea que la PoC sea desarrollada en menos de una semana. Así que eso significa que no se debe reinventar la rueda y que debemos evitar mucha codificación.
  3. WSO2 tiene una comunidad de desarrolladores en un aumento significativo en el área. Tiene documentación oficial y existen muchos blogs independientes con tutoriales y casos de éxito que avalan su factibilidad técnica. Esto siempre es importante tenerlo en cuenta a la hora de buscar soluciones ya implementadas o ayuda rápida ante un problema puntual.
  4. Los clientes desean que la solución sea escalable y adaptable a nuevos escenarios de integración con otros sistemas. Así que un enfoque de integración de sistemas tiene sentido, y el WSO2 ESB se presta bastante bien para este enfoque.
  5. Como la empresa es pequeña no puede disponer de un gran presupuesto para comprar técnología cara y como WSO2 es gratis y hace lo mismo o casi lo mismo que esas tecnologías caras pues tiene sentido probar. Además no son tontos, piden una PoC primero. Y si les funciona pues pueden contar con soporte oficial de WSO2 que tampoco es caro.

Así que manos a la obra y veamos que encontramos para solucionar el problema. San Google viene a nuestro rescate y ya tenemos en nuestras manos la PoC practicamente implementada. Es lo bueno de contar con una comunidad como la de WSO2.  Este enlace nos viene como anillo al dedo: http://wso2.org/library/articles/2012/01/integrating-different-systems-with-wso2-esb lo tiene todo o casi todo.

El escenario que implementan es el siguiente:
image

Analicemos los aspectos técnicos de la solución que propone WSO2 a nuestro problema:
  1. El ESB debe estar comprobando cada determinado periodo de tiempo si se pone un fichero con el listado de órdenes en el FTP o filesystem, según se configure. Además debe ser capaz de procesar el fichero y generar un XML entendible por el resto de la solución.
  2. Cada orden debe procesarse, así que se debe iterar por cada línea del fichero extrayendo la información y completándola con la información de los clientes, digamos que nos basta con tener su nombre y correo electrónico.
  3. Luego esta información debe ser enviada por correo al cliente y finalmente almacenada en una BD.

Para el punto 1 sabemos que el ESB de WSO2 cuenta con un transporte llamado VFS, o Virtual File System, que nos permite conectarnos a un filesystem local o remoto o a un ftp usando diferentes mecanismos.
Lamentablemente :-D el ESB no puede magicamente entender el fichero y llevarlo a un XML de utilidad, así que debemos implementar un componente que sea capaz de hacer esto, este componente se llama “CSVFile Builder” y debe implementar la interfaz org.apache.axis2.builder.Builder.

Luego vendrá un servicio proxy que recibirá el XML ya elaborado, iterará por cada orden y se encargará de obtener los datos de los usuarios, enviar el correo y finalmente guardar la información de la orden en una BD. Para el envío del correo necesitamos darle algún formato a los datos, así que implementaremos otro componente llamado “EmailMessage Formatter” y que implementa la interfaz org.apache.axis2.transport.MessageFormatter.
La información de las órdenes se guardará como se muestra en la siguiente línea:

001,001,2011-12-26T18:28:48.214+05:30,456.76

[codigo de la orden], [codigo id del usuario], [fecha de la orden], [monto de la orden]

La implementación del componente CSVFile Builder es la siguiente:

package sample.shop.builder;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.builder.Builder;
import org.apache.axis2.context.MessageContext;

import java.io.*;

import sample.shop.formatter.EmailMessageFormatter;

import javax.xml.stream.XMLStreamException;

public class CSVFileBuilder implements Builder {

    public OMElement processDocument(InputStream inputStream,
                                     String s,
                                     MessageContext messageContext) throws AxisFault {

        SOAPFactory soapFactory = OMAbstractFactory.getSOAP11Factory();
        SOAPEnvelope soapEnvelope = soapFactory.getDefaultEnvelope();

        OMNamespace omNamespace = soapFactory.createOMNamespace("http://wso2.org/sample/shop/order", "ns1");

        OMElement batchRequestElement =
                soapFactory.createOMElement("AddOrder_batch_req", omNamespace);

        OMElement addOrderElement = null;

        OMElement orderIDElement = null;
        OMElement customerIDElement = null;
        OMElement dateElement = null;
        OMElement priceElement = null;

        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        try {
            String readLine = bufferedReader.readLine();
            String[] values = null;
            while (readLine != null) {
                addOrderElement = soapFactory.createOMElement("AddOrder", omNamespace);
                values = readLine.split(",");

                //adding child elements.
                orderIDElement = soapFactory.createOMElement("orderID", omNamespace);
                orderIDElement.setText(values[0]);
                addOrderElement.addChild(orderIDElement);

                customerIDElement = soapFactory.createOMElement("customerID", omNamespace);
                customerIDElement.setText(values[1]);
                addOrderElement.addChild(customerIDElement);

                dateElement = soapFactory.createOMElement("date", omNamespace);
                dateElement.setText(values[2]);
                addOrderElement.addChild(dateElement);

                priceElement = soapFactory.createOMElement("price", omNamespace);
                priceElement.setText(values[3]);
                addOrderElement.addChild(priceElement);

                batchRequestElement.addChild(addOrderElement);
                readLine = bufferedReader.readLine();
            }

            soapEnvelope.getBody().addChild(batchRequestElement);
            return soapEnvelope;

        } catch (IOException e) {
            throw new AxisFault("Can not read the input stream", e);
        } finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
                System.out.println("Error in closing the reader");
            }
        }

    }
}


Este componente recibe la información del fichero en un InputStream, crea la estructura del mensaje que será procesado en el ESB y luego itera por cada línea rellenando el mensaje con los datos de las órdenes.


El mensaje generado entra al siguiente proxy:

<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse" name="OrderProcessor" transports="https http vfs" startOnLoad="true" trace="disable">
    <target>
        <inSequence>
            <log level="full"/>
            <property name="OUT_ONLY" value="true"/>
            <clone continueParent="true" sequential="true">
                <target>
                    <sequence>
                        <iterate xmlns:sn="http://wso2.org/sample/shop/order" id="orderIterator" expression="//sn:AddOrder_batch_req/sn:AddOrder" sequential="true">
                            <target>
                                <sequence>
                                    <log level="full"/>
                                    <dblookup>
                                        <connection>
                                            <pool>
                                                <password>your_password</password>
                                                <user>postgres</user>
                                                <url>jdbc:postgresql://localhost:5432/SHOP_DB</url>
                                                <driver>org.postgresql.Driver</driver>
                                            </pool>
                                        </connection>
                                        <statement>
                                            <sql>select "EMAIL_C","NAME_C" from "CUSTOMER_T" where "CUSTOMER_ID_C" = ?</sql>
                                            <parameter expression="//sn:AddOrder/sn:customerID" type="VARCHAR"/>
                                            <result name="email" column="EMAIL_C"/>
                                            <result name="name" column="NAME_C"/>
                                        </statement>
                                    </dblookup>
                                    <log level="custom">
                                        <property name="email" expression="get-property('email')"/>
                                        <property name="name" expression="get-property('name')"/>
                                    </log>
                                    <xslt key="orderTransformer">
                                        <property name="email" expression="get-property('email')"/>
                                        <property name="name" expression="get-property('name')"/>
                                    </xslt>
                                    <log level="full"/>
                                    <property name="messageType" value="text/csv" scope="axis2"/>

         <property name="Subject" expression="fn:concat('Customer Data: ', get-property('name'))" scope="transport"/>
                                    <header name="To" expression="fn:concat('mailto:', get-property('email'))"/>
                                    <send/>
                                </sequence>
                            </target>
                        </iterate>
                    </sequence>
                </target>
            </clone>
            <log level="full"/>
            <property name="messageType" value="application/soap+xml" scope="axis2"/>
            <send>
                <endpoint>
                    <address uri="http://localhost:9765/services/OrderService"/>
                </endpoint>
            </send>
            <property name="FORCE_SC_ACCEPTED" value="true" scope="axis2"/>
        </inSequence>
    </target>
    <parameter name="transport.vfs.ActionAfterProcess">MOVE</parameter>
    <parameter name="transport.PollInterval">5</parameter>
 <parameter name="transport.vfs.FileURI">vfs:ftp://user:tupass@tuserver/projects/WSO2/data</parameter>
    <parameter name="transport.vfs.MoveAfterProcess">vfs:ftp://user:tupass@tuserver/projects/WSO2/data/processed</parameter>
    <parameter name="transport.vfs.MoveAfterFailure">vfs:ftp://user:tupass@tuserver/projects/WSO2/data/failure</parameter>
 <parameter name="transport.vfs.Locking">disable</parameter>
 <!-- En caso de que deseen usar el filesystem en vez 
      de un ftp deben descomentariar las 3 líneas 
   siguientes y comentar las 3 de las mismas 
   propiedades que apuntan a un ftp-->
 <!--parameter name="transport.vfs.MoveAfterProcess">E:\Work\WSO2\sample_8\sample\data\processed</parameter-->
    <!--parameter name="transport.vfs.FileURI">E:\Work\WSO2\sample_8\sample\data\</parameter-->
    <!--parameter name="transport.vfs.MoveAfterFailure">E:\Work\WSO2\sample_8\sample\data\failure</parameter-->
    <parameter name="transport.vfs.FileNamePattern">.*.txt</parameter>
    <parameter name="transport.vfs.ContentType">text/csv</parameter>
    <parameter name="transport.vfs.ActionAfterFailure">MOVE</parameter>
</proxy>


Analicemos este proxy:


Por la secuencia de entrada recibe un mensaje y usando un mediador log, lo imprime en consola.

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Body>
  <ns1:AddOrder_batch_req xmlns:ns1="http://wso2.org/sample/shop/order">
   <ns1:AddOrder>
    <ns1:orderID>001</ns1:orderID>
    <ns1:customerID>001</ns1:customerID>
    <ns1:date>2011-12-26T18:28:48.214+05:30</ns1:date>
    <ns1:price>456.76</ns1:price>
   </ns1:AddOrder>
   <ns1:AddOrder>
    <ns1:orderID>002</ns1:orderID>
    <ns1:customerID>003</ns1:customerID>
    <ns1:date>2011-12-27T18:28:48.214+05:30</ns1:date>
    <ns1:price>451.76</ns1:price>
   </ns1:AddOrder>
   <ns1:AddOrder>
    <ns1:orderID>003</ns1:orderID>
    <ns1:customerID>003</ns1:customerID>
    <ns1:date>2011-12-27T18:28:48.214+05:30</ns1:date>
    <ns1:price>451.76</ns1:price>
   </ns1:AddOrder>
  </ns1:AddOrder_batch_req>
 </soapenv:Body>
</soapenv:Envelope>


Luego clona el mensaje y lo manda a un mediador iterate, que se encarga usando xpath de obtener cada una de las órdenes contenidas en el mensaje.

Un mensaje con una orden tiene la siguiente estructura:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Body>
  <ns1:AddOrder xmlns:ns1="http://wso2.org/sample/shop/order">
   <ns1:orderID>001</ns1:orderID>
   <ns1:customerID>001</ns1:customerID>
   <ns1:date>2011-12-26T18:28:48.214+05:30</ns1:date>
   <ns1:price>456.76</ns1:price>
  </ns1:AddOrder>
 </soapenv:Body>
</soapenv:Envelope>


  Para cada orden realiza las siguientes acciones:

  • Usa el mediador dblookup para realizar una consulta a la BD y usando el valor del customerID obtener para ese cliente su nombre y correo electrónico.
  • Usa el mediador log para imprimer el nombre y el correo en la consola.
  • Usa el mediador xslt para transformar el mensaje pasándole como parámetros el nombre y el correo y agregandolos a la estructura del mensaje. Esto se hace en la transformación orderTransformer que pueden ver a continuación: 
<?xml version="1.0" encoding="UTF-8"?>
<localEntry xmlns="http://ws.apache.org/ns/synapse" key="orderTransformer">
    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ns1="http://wso2.org/sample/shop/order" version="1.0">
        <xsl:output method="xml" omit-xml-declaration="yes"/>
        <xsl:param name="email"/>
        <xsl:param name="name"/>
        <xsl:template match="/">
            <xsl:apply-templates select="//ns1:AddOrder"/>
        </xsl:template>
        <xsl:template match="ns1:AddOrder">
            <ns1:AddOrder>
                <ns1:orderID>
                    <xsl:value-of select="//ns1:orderID/text()"/>
                </ns1:orderID>
                <ns1:customerID>
                    <xsl:value-of select="//ns1:customerID/text()"/>
                </ns1:customerID>
                <ns1:date>
                    <xsl:value-of select="//ns1:date/text()"/>
                </ns1:date>
                <ns1:price>
                    <xsl:value-of select="//ns1:price/text()"/>
                </ns1:price>
                <ns1:email>
                    <xsl:value-of select="$email"/>
                </ns1:email>
                <ns1:name>
                    <xsl:value-of select="$name"/>
                </ns1:name>
            </ns1:AddOrder>
        </xsl:template>
    </xsl:stylesheet>
</localEntry>

El resultado de esta transformación se ve en el siguiente mensaje:

<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Body>
  <ns1:AddOrder xmlns:ns1="http://wso2.org/sample/shop/order">
   <ns1:orderID>001</ns1:orderID>
   <ns1:customerID>001</ns1:customerID>
   <ns1:date>2011-12-26T18:28:48.214+05:30</ns1:date>
   <ns1:price>456.76</ns1:price>
   <ns1:email>isildurmac@gmail.com</ns1:email>
   <ns1:name>IsildurMac</ns1:name>
  </ns1:AddOrder>
 </soapenv:Body>
</soapenv:Envelope>

  • Imprime el nuevo mensaje en la consola.
  • A continuación se setea una property para definir el messageType que debe ser de ese tipo para que se pueda disparar el componente para el envío del correo.
  • Se define el header to, incluyendo la dirección de correo del destinatario.
  • Finalmente se llama al mediador send. Aquí gracias al property que define el messageType se llama a un componente encargado de crear el mensaje de correo que será enviado al cliente. Veremos su código ahora:
package sample.shop.formatter;

import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMOutputFormat;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.transport.MessageFormatter;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.io.OutputStream;
import java.io.ByteArrayOutputStream;
import java.net.URL;

public class EmailMessageFormatter implements MessageFormatter {

    public byte[] getBytes(MessageContext messageContext,
                           OMOutputFormat omOutputFormat) throws AxisFault {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        writeTo(messageContext, byteArrayOutputStream);
        return byteArrayOutputStream.toByteArray();
    }

    public void writeTo(MessageContext messageContext,
                        OMOutputFormat omOutputFormat,
                        OutputStream outputStream, boolean b) throws AxisFault {
        writeTo(messageContext, outputStream);

    }

    private void writeTo(MessageContext messageContext, OutputStream outputStream) throws AxisFault {
        SOAPEnvelope soapEnvelope = messageContext.getEnvelope();
        OMElement addOrderElement = soapEnvelope.getBody().getFirstElement();

        String orderID = addOrderElement.getFirstChildWithName(
                new QName("http://wso2.org/sample/shop/order", "orderID")).getText();
        String email = addOrderElement.getFirstChildWithName(
                new QName("http://wso2.org/sample/shop/order", "email")).getText();
        String name = addOrderElement.getFirstChildWithName(
                new QName("http://wso2.org/sample/shop/order", "name")).getText();
        String date = addOrderElement.getFirstChildWithName(
                new QName("http://wso2.org/sample/shop/order", "date")).getText();
        String price = addOrderElement.getFirstChildWithName(
                new QName("http://wso2.org/sample/shop/order", "price")).getText();

        try {
            outputStream.write("Order ID : ".getBytes());
            outputStream.write(orderID.getBytes());
            outputStream.write("\n".getBytes());

            outputStream.write("Email : ".getBytes());
            outputStream.write(email.getBytes());
            outputStream.write("\n".getBytes());

            outputStream.write("Name : ".getBytes());
            outputStream.write(name.getBytes());
            outputStream.write("\n".getBytes());

            outputStream.write("Date : ".getBytes());
            outputStream.write(date.getBytes());
            outputStream.write("\n".getBytes());

            outputStream.write("Price : ".getBytes());
            outputStream.write(price.getBytes());
            outputStream.write("\n".getBytes());


        } catch (IOException e) {
            throw new AxisFault("Can not write to the output stream");
        }

    }

    public String getContentType(MessageContext messageContext,
                                 OMOutputFormat omOutputFormat,
                                 String s) {
        return "text/csv";
    }

    public URL getTargetAddress(MessageContext messageContext,
                                OMOutputFormat omOutputFormat,
                                URL url) throws AxisFault {
        return url;
    }

    public String formatSOAPAction(MessageContext messageContext,
                                   OMOutputFormat omOutputFormat,
                                   String s) {
        return s;
    }
}

  • Aquí se obtiene del messageContext el mensaje SOAP y de sus nodos se extraen los valores necesarios para incluirlos en el correo a enviar. Luego se escriben fuera del código el contenido del OutputStream  se manda como un adjunto por correo. El envío por correo se logra mediante una configuración en el fichero axis2.xml del ESB.
<transportSender name="mailto" class="org.apache.axis2.transport.mail.MailTransportSender">
    <parameter name="mail.smtp.host">tuserverdecorreo</parameter>
    <parameter name="mail.smtp.port">puerto</parameter>
    <parameter name="mail.smtp.starttls.enable">false</parameter>
    <parameter name="mail.smtp.auth">false</parameter>
    <parameter name="mail.smtp.user">tuusuario</parameter>
    <parameter name="mail.smtp.password">*****</parameter>
    <parameter name="mail.smtp.from">tucorreo@gmail.com</parameter>
</transportSender> 
  • Ahora vendría para finalizar la implementación el guardar las órdenes en BD. Pero esto se hace fuera del mediador clone, el cual se usó para mantener el mensaje original y poder trabajar luego con este mensaje.
  • Una vez que salimos del mediador clone imprimimos nuevamente el mensaje.
  • Volvemos a cambiar el messageType al formato de mensaje SOAP.
  • Y enviamos este mensaje con todas las órdenes a un servicio de acceso a datos  que mostramos su configuración a continuación. Vena en particular la propiedad enableBatchRequests="true" porque esta es la que permite que podamos enviarle todas las órdenes y estas sean procesadas por el servicio.
<data name="OrderService" enableBatchRequests="true" serviceNamespace="http://wso2.org/sample/shop/order">
   <config id="shopdatasource">
      <property name="org.wso2.ws.dataservice.driver">org.postgresql.Driver</property>
      <property name="org.wso2.ws.dataservice.protocol">jdbc:postgresql://localhost:5432/SHOP_DB</property>
      <property name="username">postgres</property>
      <property name="password">tupass</property>
   </config>
   <query id="addOrderQuery" useConfig="shopdatasource">
      <sql>insert into "ORDER_T" values (:orderID, :customerID, :date,:price);</sql>
      <param name="orderID" sqlType="STRING" />
      <param name="customerID" sqlType="STRING" />
      <param name="date" sqlType="TIMESTAMP" />
      <param name="price" sqlType="DOUBLE" />
   </query>
   <operation name="AddOrder">
      <call-query href="addOrderQuery">
         <with-param name="orderID" query-param="orderID" />
         <with-param name="customerID" query-param="customerID" />
         <with-param name="date" query-param="date" />
         <with-param name="price" query-param="price" />
      </call-query>
   </operation>
</data>
  
  • Para aquellos que son detallistas deben haber notado que el mensaje creado a partir del fichero tenía como elemento raíz uno de nombre “AddOrder_batch_req” y esto no es casual, es para poder enlazar sin problema este mensaje con el servicio de acceso a datos invocando a la operación de procesamiento en batch.


Así termina el servicio proxy y los invito a probarlo, descargando los ficheros necesarios desde el enlace provisto en el blog original que puse al inicio o directamente desde aquí http://wso2.org/files/sample_8.zip.

Algunos cambios hechos a las configuraciones originales:
  1. Se usaba antes el filesystem para tener el fichero. En lo que muestro en esta entrada uso un ftp.
  2.  El SGBD era MySQL, acá usé PostgreSQL.

miércoles, 17 de diciembre de 2014

Componente Carbon UI para gestionar la clusterización (III) Final

Pues como pudieron ver en la entrada anterior, se mostró la visualización del plugin desarrollado para gestionar de manera gráfica la configuración necesaria para clusterizar las herramientas de la Suite de WSO2, hasta el momento se encuentra funcional solo que se está implementando las respectivas validaciones para no permitir que se introduzcan errores en los parámetros.

Ahora les daré una breve descripción de como es que se desarrollan los componentes visuales (Carbon UI) para la Suite de WSO2.Y cualquier apoyo que nos quieran brindar o duda que tengan nos pueden dejar sus comentarios.

Para el desarrollo de un Carbon UI Bundle Project es necesario que nos descarguemos el WSO2 Developer Studio, que no es más que un plugin desarrollado por WSO2 y que está montado en Eclipse, en este link se puede descargar el plugin por separado separado o un Eclipse en este caso la versión Kepler que ya trae instalado el plugins Developer Studio lo cual es lo que recomiendo para evitar algun problema de configuración que tengamos.

Para entender como se desarrolla un carbon UI Bundle pueden acceder a la documentación online del Developer Studio.

Para comprender como es que funciona el core de Carbon y como se puede personalizar les sugiero que sigan estos enlaces en los que se da una explicación bien detallada de como es que funciona y como está compuesto:
Una vez descargada la herramienta, podemos ejecutarla y visualizamos el Dashboard del Developer Studio como la imagen que verán a continuación:

Paso 1

 Damos click en la opción Carbon UI Bundle Project para poder crear un UI Bundle y seleccionamos la primera opción porque vamos a desarrollar uno desde cero: 

Paso 2
Después de darle siguiente en la captura anterior, nos encontraríamos con la siguiente pantalla, en la que definimos cual va a ser el nombre de nuestro proyecto, si deseamos crear una clase Activator que va a ser ejecutada una vez que se cargue nuestro bundle y se definen otros atributos necesarios para el mismo, además de definir el path donde se van a desplegar nuestras vistas:

Paso 3
Una vez que hayamos completado el paso anterior se especifica en quñe grupo y qué id va a tener nuestro bundle en maven:

Paso 4
Le damos Finish y nos quedaría un proyecto en nuestro workspace con una estructura similar a esta:

Paso 5
Como verán tenemos creada nuesta clase Activator en la que podemos definir que hacer en el momento en que se cargue nuestro bundle esto se define en el metodo:

    public void start(BundleContext bundleContext) throws Exception {
        Activator.context = bundleContext;
        //TODO Aquí podemos poner nuestra implementación.
    }

También tenemos creada nuesta carpeta clustermanager dentro de la carpeta web en la que estarán contenidas todas nuestras vistas, css, imágenes y js. 

En la carpeta META-INF encontrarán el fichero MANIFEST.MF y component.xml, en el primero se especifica todas las características relacionadas con el bundle y en el segundo es donde se le configura la región en la que va a estar situado el plugins en el menú de las herramientas así como cuales son las páginas de inicio del mismo yel recurso donde va a estar la internacionalización quedando este ejemplo como se muestra a continuación:

<?xml version="1.0" encoding="UTF-8" ?>
<component xmlns="http://products.wso2.org/carbon">
<menus>
        <menu>
            <id>clustermanager_menu</id>
            <i18n-key>clustermanager.managername</i18n-key>
            <i18n-bundle>com.wso2.clustermanager.mgt.ui.i18n.Resources</i18n-bundle>
            <parent-menu>configure_menu</parent-menu>
            <link>#</link>
            <region>region1</region>
            <order>20</order>
            <style-class>manage</style-class>
            <icon>../clustermanager/images/applications.gif</icon>
            <require-permission>/permission/protected/manage</require-permission>
        </menu>
        <menu>
            <id>clustermanager_configuration</id>
            <i18n-key>clustermanager.configuration</i18n-key>
            <i18n-bundle>com.wso2.clustermanager.mgt.ui.i18n.Resources</i18n-bundle>
            <parent-menu>clustermanager_menu</parent-menu>
            <link>../clustermanager/index.jsp</link>
            <region>region1</region>
            <order>14</order>
            <style-class>manage</style-class>
            <icon>../clustermanager/images/list.gif</icon>
            <require-permission>/permission/protected/manage</require-permission> 
        </menu>
    </menus>
</component>

La tendencia según la implementación que se ha visto y según lo que se propone por los desarrolladores de las herramientas es que en el plugin esté bien separada la interfaz de la lógica de negocio y que la comunicación entre la misma sea por lo general mediante el consumo de Servicios Web, pero en nuestro caso como no existe mucha más lógica que allá que leer y escribir un fichero XML por lo tanto no se hizo necesario esta separación, no obstante si usted cuando lo esté leyendo cree que no está totalmente correcto puede dejar su impresión y siempre serán bien recibidos sus comentarios :-).

Bueno vayamos al grano del asunto.

En nuestra página index.jsp que será la que se visualizará una vez que se haya dado click en el menú de la herramienta es donde se escribe todo el código html para lograr visualizar la información que se desea recoger y mostrar de la configuración que se encuentra en la sección clustering del fichero axis2.xml, para ello es necesario que respetemos las clases CSS que usan las herramientas para lograr mantener el estilo de la vista.

Se creó una clase nombrada Axis2HandlerXML.java que es la encargada de escribir y leer del fichero axis2.xml mediante el uso del DOM y SAX que es bastante sencilla debido al negocio que tiene que resolver.

La información que se recoge en la vista es enviada a un JSP que hace función de Servlet la que interactúa directamente con la clase Axis2HandlerXML.java para almacenar toda la información recogida de la vista y escribirla directamente en el fichero axis2.xml dentro de la sección de clustering, automáticamente se actualiza la vista.

Para evitar que cualquier usuario pueda realizar un cambio en la configuración de los servidores se utiliza el tag <require-permission/> en el fichero component.xml dentro del directorio META-INF, en nuestro caso estos son los permisos que usamos: <require-permission>/permission/protected/manage</require-permission>

Una vez que hayamos realizado todos los cambios en la configuración debemos reiniciar el servidor para que los mismos se hagan efectivos y lograr disfrutar de las ventajas y mejoras que nos brinda la clusterización.

Espero que les haya interesado el desarrollo de este plugins, como ven se puediera automatizar el proceso de configuración de muchas funcionalidades de las herramientas para evitar que introduzcamos errores en los mismos y para facilitar el proceso que en no pocas ocasiones se torna algo engorroso.

Nos vemos pronto ;-)



lunes, 6 de octubre de 2014

En la entrada anterior vimos cómo implementar usando el ESB de WSO2 un problema planteado en StackOverFlow. En esta entrada veremos con más nivel de detalle la solución propuesta y terminaremos la implementación pues nos faltó realizar la transformación para que el mensaje de salida cumpliera con el WSDL del servicio proxy desarrollado.

El WSDL del servicio proxy visto a través del editor gráfico del Eclipse luce como sigue (nos centraremos en los mensajes de entrada y salida):

Mensaje de entrada
mensaje_entrada

Mensaje de salida:
mensaje_salida

Como se puede apreciar la salida debe ser un listado de direcciones y cada dirección debe tener los elementos del complexType addressType.

La parte que nos interesa del servicio proxy que se implementó es la del flujo de salida, que es donde se reciben los mensajes y estos son agregados para luego pasar a una transformación.
De la entrada anterior a esta se hicieron pequeños cambios en la secuencia así que la mostramos nuevamente.


<outSequence>
 <property name="root" scope="default">
 <root:rootelement xmlns:root="http://ws.wso2.org/dataservice"/>
 </property>
 <aggregate id="iterate1">
 <completeCondition>
    <messageCount min="-1" max="-1"/>
 </completeCondition>
 <onComplete xmlns:s12="http://www.w3.org/2003/05/soap-envelope"
    xmlns:add="http://ws.wso2.org/dataservice"
    xmlns:s11="http://schemas.xmlsoap.org/soap/envelope/"
    expression="//add:Addresses/add:Address"
    enclosingElementProperty="root">
    <log level="full"/>
 </onComplete>
 </aggregate>
 <xslt key="conf:/xslt/Tranformacion_DS_to_ESB.xsl"/>
 <send/>
</outSequence>


Lo primero diferente que se puede apreciar es que hemos creado una property de nombre root que está asociada a un namespace y que posee un elemento rootelement. Esta property la necesitaremos pues cuando los mensajes son agregados el resultado del mediador aggregate no posee un nodo raíz, así que se lo ponemos usando esta propiedad y el parámetro enclosingElementProperty="root" que pueden ver en el mediador aggregate. Ambos nos permiten generar un XML válido para que sea procesado sin problemas por la transformación. Para más información pueden ver esta pregunta en stackoverflow.


La transformación entonces sería la siguiente:


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions" xmlns:a="http://ws.wso2.org/dataservice" xmlns:b="http://www.example.org/Address/" exclude-result-prefixes="a fn xs">
 <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
 <xsl:template match="/">
  <b:getAddressResponse>
   <xsl:for-each select="//a:rootelement/a:Address">
     <address>
      <address>
       <xsl:value-of select="a:address"/>
      </address>
      <partybranchid>
       <xsl:value-of select="a:partybranchid"/>
      </partybranchid>
      <clientid>
       <xsl:value-of select="a:clientid"/>
      </clientid>
     </address>
   </xsl:for-each>
  </b:getAddressResponse>
 </xsl:template>
</xsl:stylesheet>


Como pueden observar a la hora de obtener los nodos ya tenemos en cuenta que existirá un elemento raíz “rooelement” y basicamente lo que hacemos es iterar por cada nodo Address y generar nodos address los cuales estarán contenidos dentro de un nodo raíz getAddressResponse.


Ya con esta transformación se logra que el mensaje de salida cumpla con el WSDL del servicio proxy, lo cual pueden comprobar usando el SOAPUI y validando contra el Schema del servicio.


Espero les sea de utilidad.

jueves, 25 de septiembre de 2014

Introducción al ESB de WSO2 a través de ejemplos prácticos.V

En esta entrada quiero mostrarles una pequeña implementación motivada a partir de una pregunta lanzada en StackOverFlow.
Basicamente se quiere obtener a partir de la consulta de un servicio de datos un listado de IDs y con ese listado se desea iterar sobre el mismo y por cada elemento lanzar una llamada a otro servicio de acceso a datos, y las respuestas obtenidas unirlas para devolver una única respuesta al cliente.
Veamos como se hace.

El servicio proxy queda como sigue:
<?xml version="1.0" encoding="UTF-8"?>
<proxy xmlns="http://ws.apache.org/ns/synapse"
       name="AddressProxy"
       transports="https,http"
       statistics="disable"
       trace="disable"
       startOnLoad="true">
   <target>
      <inSequence>
         <property xmlns:p="http://www.example.org/Address/"
                   name="clientid"
                   expression="//p:getAddress/clientid"
                   scope="default"
                   type="STRING"/>
         <sequence key="conf:/sequencesStackOverFlow/getmpartybranch"/>
      </inSequence>
      <outSequence>
         <aggregate>
            <completeCondition>
               <messageCount min="-1" max="2"/>
            </completeCondition>
            <onComplete xmlns:add="http://ws.wso2.org/dataservice"
                        expression="//add:Addresses/add:Address">
               <send/>
            </onComplete>
         </aggregate>
      </outSequence>
   </target>
   <publishWSDL key="conf:/wsdls/Address.wsdl"/>
   <description/>
</proxy>



Como pueden ver en la secuencia de entrada primero guardo en una property el valor del clientid que es la entrada del servicio proxy, y luego hago una llamada a la secuencia getmpartybranch, que está en el registro del ESB y que veremos a continuación.


<sequence xmlns="http://ws.apache.org/ns/synapse">
   <payloadFactory media-type="xml">
      <format>
         <dat:getmpartybranch xmlns:dat="http://ws.wso2.org/dataservice">            
            <dat:clientid>$1</dat:clientid>         
         </dat:getmpartybranch>
      </format>
      <args>
         <arg xmlns:ns="http://org.apache.synapse/xsd" expression="get-property('clientid')" evaluator="xml"></arg>
      </args>
   </payloadFactory>
   <send receive="conf:/sequencesStackOverFlow/iterOvermpartybranch">
      <endpoint>
         <address uri="http://127.0.0.1:9765/services/mpartybranch"></address>
      </endpoint>
   </send>
</sequence>



En esta secuencia creo un nuevo mensaje para consultar el servicio de acceso a datos que me dará dado el clientid el listado sobre el cual deberé iterar. Este resultado lo obtengo de invocar al servicio http://127.0.0.1:5555/services/mpartybranch y será enviado a la secuencia iterOvermpartybranch, que veremos a continuación.


<sequence xmlns="http://ws.apache.org/ns/synapse">
   <iterate xmlns:ns="http://org.apache.synapse/xsd" xmlns:ds="http://ws.wso2.org/dataservice" expression="//ds:DataCollection/ds:Datalist" id="iterate1" sequential="true">
      <target>
         <sequence>
            <property name="partybranchid" expression="//ds:partybranchid/text()" scope="default" type="STRING"></property>
            <property name="clientid" expression="//ds:clientid/text()" scope="default" type="STRING"></property>
            <log>
               <property name="PARTYID" expression="get-property('partybranchid')"></property>
               <property name="CLIENTID" expression="get-property('clientid')"></property>
            </log>
            <payloadFactory media-type="xml">
               <format>
                  <dat:getselect_addresses xmlns:dat="http://ws.wso2.org/dataservice">                     
                     <dat:objectid>$1</dat:objectid>                     
                     <dat:clientid>$2</dat:clientid>                  
                  </dat:getselect_addresses>
               </format>
               <args>
                  <arg expression="get-property('partybranchid')" evaluator="xml"></arg>
                  <arg expression="get-property('clientid')" evaluator="xml"></arg>
               </args>
            </payloadFactory>
            <log level="full"></log>
            <send>
               <endpoint>
                  <address uri="http://127.0.0.1:9765/services/getAddress" format="soap12"></address>
               </endpoint>
            </send>
         </sequence>
      </target>
   </iterate>
</sequence>



En  el mediador iteramos para llegar a cada nodo //ds:DataCollection/ds:Datalist y obtenemos las propiedades que vamos a utilizar, estas las imprimimos en consola para comprobar que efectivamente el servicio está funcionando. Algo que es muy útil cuando lo estamos desarrollando.


Luego creamos otro mensaje usando el mediador payload factory y lo enviamos al servicio de datos correspondiente. En base de datos tengo 2 tuplas en mi ejemplo, así que se realizaría 2 recorridos y se consultaría 2 veces al servicio http://127.0.0.1:9765/services/getAddress


La respuesta de esta secuencia entraría por la secuencia de salida del servicio proxy, la cual pueden ver en la definición del servicio. En la secuencia se usa el mediador aggregate para agregar las respuestas. Solo queda pendiente realizar una transformación para cumplir con el formato del mensaje de salida. Esto lo veremos en la siguiente entrada.

jueves, 11 de septiembre de 2014

WSO2 Identificando y solucionando errores.


Cuando se comienza a trabajar en soluciones de integración usando un ESB el tema de Troubleshooting es fundamental tenerlo claro.

Para aquellos que se enfrenta a desarrollos de este tipo usando la suite de WSO2 y su ESB les recomendamos descargar este PDF, que les dará una guía muy útil sobre como identificar los problemas y proceder a resolverlos eficientemente.

Los principales puntos que aborda son:


  • Troubleshooting Techniques
    1. Debug logs
      Trace logs
  • Monitoring Messages
    1. TCPMon
      Wire Logs
  • Solutions for Commonly Occurring Exceptions
  • Troubleshooting Timeout Issues
  • Mediation Fault Handling

Esperamos que les sea de utilidad.

Tomado de: http://wso2.com/library/articles/2014/05/wso2-esb-a-guide-to-troubleshoot/

miércoles, 3 de septiembre de 2014

WSO2 con un IDE en la Nube.


WSO2 no deja de sorprendernos y como parte de su solución en la nube "WSO2 Cloud" tenemos la  funcionalidad de poder editar nuestro código y subir los cambios desde un repo temporal de git en la nube hacia el repo remoto. Nos provee de un autocompletamiento de código que si no está al nivel de los IDEs a los que estamos acostumbrados, si para realizar un trabajo rápido o un cambio menor cumple con las espectativas.

Para llegar a esta pantalla del IDE nos vamos a un proyecto en la solución Cloud y seleccionamos del menú de la derecha la opción "Repos & Builds".

De ahí vamos al final y damos clic en el botón "Edit Code", y ya con esto tendremos acceso al IDE y poder realizar los cambios que queramos.

Espero les sea de utilidad y les agilice el desarrollo, es una buena opción para aquellos que se dan cuenta de un cambio a última hora y no tienen a mano su PC o Laptop con su IDE de preferencia. :-D

martes, 2 de septiembre de 2014

WSO2 ESB y el manejo de los logs para la detección de errores

En esta entrada quisieramos mostrarles algo que es básico en cualquier suite para desarrollar soluciones de integración y es la forma en que capturamos los logs generados por los eventos que ocurren con los servicios desplegados.

En el WSO2 ESB existen 3 formas de capturar los logs de los servicios proxy:

  1. Usando el mediador log, dentro de la misma configuración del servicio proxy, algo muy útil durante el desarrollo y para los temas de troubleshooting.
  2. Generando los logs propios del servicio proxy en un fichero a parte del de los logs del propio ESB. A veces dada la cantidad de logs generados se hace un poco complicado seguirle la traza a lo que ocurre con determinado servicio. Esto es muy útil en los ambientes de producción.
  3. Extendiendo las funcionalidades del Synapse, framework base del WSO2 ESB, para incluir lo que se conoce como Observadores. Los cuales se programan para capturar la información que querramos y son adjuntados al servicio proxy en cuestión, especificando incluso el fichero de salida hacia donde se escribirán los logs.

Veamos ahora como podemos usar cada una de estas formas. Para ello usaremos el servicio JAX-WS desplegado en el WSO2 AS de la entrada anterior  y crearemos un servicio proxy.

El servicio JAX-WS lo pueden ver en la siguiente imagen:



El servicio proxy que creamos para la primera forma de captura de los logs lo pueden ver en la siguiente imagen.



Así los logs capturados se pueden ver en la consola o en el fichero de los logs en el ESB, como se muestra en la siguiente imagen.



La segunda forma de capturar los logs, aunque sería mejor decir que es de separar los logs que nos interesan de los logs propios de la ejecución del ESB, es modificar el fichero log4j.properties y añadir las siguientes líneas:

# Configuracion de appender para capturar los logs generados por el servicio UserCollectionProxy
log4j.category.SERVICE_LOGGER.UserCollectionProxy=INFO, PROXY_APPENDER
log4j.additivity.PROXY_APPENDER=false
log4j.appender.PROXY_APPENDER=org.apache.log4j.DailyRollingFileAppender
log4j.appender.PROXY_APPENDER.File=${carbon.home}/repository/logs/${instance.log}/wso2-esb-UserCollectionProxy${instance.log}.log
log4j.appender.PROXY_APPENDER.Append=true
log4j.appender.PROXY_APPENDER.layout=org.apache.log4j.PatternLayout
log4j.appender.PROXY_APPENDER.layout.ConversionPattern=%d{HH:mm:ss,SSS} [%X{ip}-%X{host}] [%t] %5p %c{1} %m%


De esta manera se creará un fichero de nombre wso2-esb-UserCollectionProxy.log que tendrá los logs generados solo por el servicio proxy indicado.

La tercera variante es una implementación más general de la segunda. En la variante 2 para cada servicio debíamos realizar una modificación en el fichero log4j.properties y esto es conveniente cuando no nos interesa observar los logs de todos los servicios si no de algunos en particular. Pero cuando nos interesa observar los logs de todos los servicios, entonces la 3ra opción es la mejor.

Básicamente consiste en implementar un observer que extiende de la clase AbstractSynapseObserver de synapse y posee un método: private void setLogger(ProxyService proxy) throws IOException

Este método es donde se implementa el comportamiento tal y como se puede observar en la siguiente clase java.



package org.jorgesoftdev;

import org.apache.synapse.config.AbstractSynapseObserver;
import org.apache.synapse.core.axis2.ProxyService;
import org.apache.log4j.DailyRollingFileAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;

/**
 * Created by Jorge on 30/08/14.
 */
public class CustomSynapseObserverForLogging extends AbstractSynapseObserver  {

    private static final Log log = LogFactory.getLog(CustomSynapseObserverForLogging.class);

    public void proxyServiceAdded(ProxyService proxy) {
        try {
            setLogger(proxy);
        } catch (IOException e) {
            log.error("CustomProxyObserver could not set service level logger for the proxy : " + proxy.getName(), e);
        }
    }

    public void proxyServiceRemoved(ProxyService proxy) {
        try {
            setLogger(proxy);
        } catch (IOException e) {
            log.error("CustomProxyObserver could not set service level logger for the proxy : " + proxy.getName(), e);
        }
    }

    private void setLogger(ProxyService proxy) throws IOException {
        String filename = "repository/logs/" + proxy.getName() + ".log";
        String datePattern = "yyyy-MM-dd";
        String SYSTEM_LOG_PATTERN = "[%d] %5p - %x %m {%c}%n";
        PatternLayout layout = new PatternLayout(SYSTEM_LOG_PATTERN);
        DailyRollingFileAppender appender = null;
        appender = new DailyRollingFileAppender(layout, filename, datePattern);
        Logger proxyLogger = Logger.getLogger("SERVICE_LOGGER." + proxy.getName());
        proxyLogger.setLevel(Level.ALL);
        proxyLogger.setAdditivity(false);
        proxyLogger.addAppender(appender);
    }

}




Para implementar la tercera variante he creado un proyecto maven con esta clase implementada, he generado el jar y lo he copiado en la carpeta [ESB_HOME]\repository\components\lib\

Luego he ido al fichero [ESB_HOME]\repository\conf\synapse.properties y he actualizado la línea:
synapse.observers=org.wso2.carbon.mediation.dependency.mgt.DependencyTracker

con la línea:

synapse.observers=org.jorgesoftdev.CustomSynapseObserverForLogging

Luego de reiniciar el servidor en el directorio [ESB_HOME]\repository\logs\ aparecen los ficheros [nombre_servicio_proxy].log con los logs de los respectivos servicios, cada uno por separado.


Cada variante depende de las necesidades de los desarrolladores,  del ambiente en el que se esté ejecutando el ESB, pero al menos a nuestro equipo para un ambiente de desarrollo una combinación de las variantes 1 y 3 es la mejor opción.

La información utilizada para esta entrada fue obtenida de aquí.

Esperamos les sea de utilidad.

lunes, 1 de septiembre de 2014

Introducción a WSO2 Cloud. Nueva propuesta de WSO2.

Hola a todos.

En junio, durante el evento WSO2 Con - Europe 2014 en Barcelona se lanzó la versión beta del servicio WSO2 Cloud de la empresa WSO2. Esta fue la nueva iniciativa luego de la entrega por parte de la empresa a Apache de la solución para la nube StratosLive.

Este nuevo servicio es sumamento fácil de usar. Si ya tienes una cuenta en wso2.com puedes usarla para acceder al mismo, en caso contrario y a través de esta pantalla puedes registrarte y acceder.


La primera prueba que hice fue pasar la implementación de un servicio web desarrollado en jax-ws para esta nueva plataforma y básicamente luego de autenticarse aparece la siguiente pantalla.


Ahí se selecciona App Cloud pues nuestro servicio web estará dentro de un war, ya cuando entramos a la UI debemos crear una aplicación web y esto automáticamente nos provee de un repositorio git y un espacio en jenkin para el despliegue de la aplicación.

Usando el proyecto Example3_JAX-WS expuesto en esta entrada hice los cambios necesarios en el mismo para moverlo al nuevo repo git, cuidando de no modificar los parámetros del pom.xml pues aunque les compile abajo, cuando se trate de subir al repo online les dará problemas.



En nuestro caso usamos SourceTree como cliente GIT para clonar el repo, implementamos los cambios en el servicio y automaticamente cuando se hace un commit al repo online se lanza la tarea en jenkin de compilar y desplegar la aplicación.

Ya con la aplicación desplegada y corriendo usamos el SOAPUI para crear un proyecto y probar el consumo del servicio web desarrollado con JAX-WS.

Para cerrar, revisando los blogs de los desarrolladores de WSO2 me encontré con esta entrada donde se hace un resumen de las principales funcionalidades de este servicio:

WSO2 App Cloud

  • Create applications from scratch - JSP, Jaggery, JAX-WS, JAX-RS
  • Upload existing web applications - JSP, Jaggery
  • Database provisioning for your apps
  • Life cycle management for your app - Dev, Test and Prod environments
  • Team work - A team can collaboratively work on the app
  • Issue tracking tool
  • A Git repository per each application and a build tool.
  • Cloud IDE - For your app development work
  •  And more...

WSO2 API Cloud

  • Create APIs and publish to API store (a store per tenant)
  • Subscribe to APIs in the API store
  • Tier management
  • Throttling
  • Statistics
  • Documentations for APIs