CaboLabs: expertos en informática médica, estándares e interoperabilidad
Análisis de capacidades de transformacion de mensajes en jboss-esb
1. Proyecto: análisis de capacidades de
transformación de mensajes en
JBossESB
Informe final
Pablo Pazos Gutierrez
Taller de Sistemas de Información 4, Instituto de Computación
Facultad de Ingeniería, Universidad de la República
2. Resumen
Hoy en día los sistemas informáticos orientados al dominio de la salud han crecido en importancia,
tamaño y complejidad, siendo la interoperabilidad entre los sistemas existentes, y también con nuevos
sistemas que se generen, el problema a resolver. Para intentar resolver los problemas de
interoperabilidad entre estos sistemas informático-clínicos existen distintos estándares propuestos por la
industria, dentro de estos HL7 es un grupo de estándares para resolver la interoperabilidad entre sistemas
de registro clínico orientado al intercambio de mensajes. El presente proyecto intenta explorar una
solución de comunicación entre sistemas que son compatibles con HL7 y sistemas que no lo son y que
manejan su propio formato de mensajes para interoperar con otros sistemas. Esta solución se basará en
un middleware JBossESB en donde se resolverán las diferencias entre los formatos de mensajes que
soportan los sistemas que intentan interoperar, de modo de que no sea necesario modificar un sistema
existente para hacerlo compatible con mensajes HL7, si no que dicha “compatibilización” se hará dentro
del ESB.
3. Planteo del problema
El objetivo principal del presente proyecto es investigar distintas opciones para implementar la
comunicación entre sistemas que se comuniquen mediante mensajes y que utilicen distintos formatos
para dichos mensajes, en particular se buscará la interoperación entre sistemas cuyos mensajes sigan el
estándar HL7 (*) y sistemas que utilicen otro formato de mensajes distinto a HL7. El objetivo es que
ninguno de los sistemas deba adaptar su formato de mensajes al formato del otro sistema, es decir que la
conversión entre los distintos sistemas de mensajes será realizada dentro de un ESB. Dicho ESB
manejará los distintos aspectos referentes a la comunicación entre los sistemas. Este enfoque de manejo
de transformaciones entre distintos formatos de mensajes en el propio ESB permite que los sistemas
existentes no necesiten ser modificados para poder interactuar con otros sistemas compatibles con HL7.
El ESB que se estará probando es JBossESB, un subproyecto dentro de JBoss. Sobre JBossESB se
desea relevar y probar las distintas capacidades de transformación entre formatos de mensajes y también
relevar las distintas capacidades de ruteo de mensajes con las que cuente JBossESB.
La siguiente figura muestra opciones de transformación de mensajes entre sistemas. En la opción
“clásica” se agrega un componente dentro del mismo sistema para que realice la estandarización de sus
mensajes a HL7. En la opción centrada en “ESB” la estandarización de los mensajes se realiza en el ESB
y es transparente a ambos sistemas, este es el enfoque que se intentará seguir.
Figura 1: opciones de estandarización de mensajes entre sistemas
(*) HL7 es un conjunto de estándares de comunicación orientados a mensajes, para el dominio de la salud. Los
mensajes están basados en el modelo de referencia de HL7 llamado RIM por sus siglas en inglés. Estos mensajes se
utilizan para comunicar información clínica y demográfica de pacientes entre sistemas, como por ejemplo: resultados
de estudios, medidas de presión de sangre, datos personales, etc.
4. Introducción a JBossESB
JBossESB es la solución de middleware para comunicación del stack de proyectos JBoss. JBossESB
sirve como infraestructura de comunicación para otros proyectos como jBMP, la solución de BPM del
stack JBoss. Con JBossESB se pueden definir servicios que consisten en una serie de acciones que se
ejecutan en secuencia llamado “pipeline” de acciones. Estos servicios pueden tener diversos puntos de
entrada a los cuales se puede acceder mediante distintos protocolos de comunicación, por ejemplo
mediante mensajería JMS, FTP, o vía Web Services, entre otros. La siguiente imagen muestra un ejemplo
de un servicio con su pipeline de acciones al cual se accede mediante el protocolo FTP y termina
ejecutando lógica de negocio. [1]
Figura 2: Acciones en un servicio del ESB
El pipeline de acciones se parece mucho al estilo de arquitectura “tubos y filtros”, donde un mensaje es
pasado por cada acción, las cuales realizan algún tipo de transformación en los datos del mensaje, y el
resultado queda disponible para la siguiente acción. Es necesario diferenciar los conceptos de “mensaje
del ESB” [2] y “mensaje”. El ESB utiliza un mensaje interno para transportar los mensajes que le son
enviados, ese es el “mensaje del ESB”. El “mensaje del ESB” podría transportar múltiples mensajes
distintos dentro del ESB, y las acciones podrías acceder a diferentes mensajes para obtener datos o
modificarlos. Por ejemplo, el mensaje del ESB podría contener varios mensajes XML distintos en su Body.
Para el caso del taller, el mensaje del ESB contendrá solo un mensaje XML en su Body.
El mensaje del ESB cuenta con los siguientes campos:
• Body: mantiene información arbitraria que puede ser agregada y modificada por el usuario y por
las acciones del canal.
• Attachment: contiene información extra a la que aparece en el Body.
• Context: el contexto es la sección del mensaje que contiene información para manejar la sesión,
transacciones, seguridad, etc.
• Fault: sirve para especificar distintas fallas que se podrían dar en la comunicación y devolver un
mensaje acorde (es similar a una excepción).
• Header: es el cabezal del mensaje.
• Properties: mantiene propiedades arbitrarias del mensaje en la forma de un mapa <String,
Object>.
En la figura 3 se muestra la arquitectura de JBossESB, con las diversas opciones de puntos de entrada al
ESB y las opciones de comunicación con componentes de más bajo nivel (lógica de negocio).
6. Alternativas de transformación de mensajes
JBossESB utiliza como principal componente de transformación al proyecto Smooks. Smooks es un
componente de transformación que “vive” dentro de otro proyecto mayor relacionado con el
procesamiento y transformación de datos, el proyecto Milyn (Data Transformation and Análisis). [5][6][7]
Smooks ofrece transformaciones entre distintos formatos de mensajes, por ejemplo brinda transformación
desde y hacia: XML, EDI, Java (*), CSV, JSON, entre otros. Los mecanismos de transformaciones que
ofrece Smooks y que pueden ser de utilidad para este taller son:
Transformaciones Java
Transformaciones XSLT
Transformaciones FreeMarker
Transformaciones basadas en modelos (model-driven transformations)
Transformaciones Java-Java mediante configuración Smooks (Java Bindings)
Los tipos de transformación a analizar serían ente los siguientes formatos (**):
Transformación Java-Java
Transformación XML-XML
Transformación XML-Java [7.2]
Transformación Java-XML [7.3]
Los mecanismos de transformación indican “como” es que se lleva a cabo la transformación, los tipos de
transformación indican “entre que” formatos se está transformando, es decir que el “tipo” de
transformación es independiente del “mecanismo” que se aplique para implementar la transformación.
Por ejemplo, se podría hacer una transformación XML-XML mediante XSLT o FreeMarker, o una
transformación Java-Java basada en modelos o mediante configuración Smooks.
(*) Cuando se menciona que un mensaje se encuentra en formato Java, se refiere a que el mensaje es una instancia
en memoria de un modelo de datos particular.
(**) La notación de los tipos de transformación es: formato_origen – formato_destino, por ejemplo: XML-Java, es la
notación de una transformación que toma un mensaje en formato XML y devuelve un mensaje en formato Java.
Mecanismos de transformación
Transformación Java
Este mecanismo de transformación es implementada directamente con lógica Java. La configuración es
mínima y la lógica hace todo el trabajo de transformación, el tipo de transformación puede ser Java-Java
o XML-XML. La transformación Java puede hacerse dentro de Smooks o directamente como una acción
del pipeline de acciones de un canal del ESB. Para implementar una acción en el ESB, es necesario crear
una clase que extienda a org.jboss.soa.esb.actions.AbstractActionPipelineProcessor, si la clase que
implementa dicha acción se llama ActionProcessor, el segmento de configuración del ESB necesario para
agregar dicha acción al pipeline de acciones de un servicio del ESB sería similar al siguiente:
<actions>
...
<action class="paquete.ActionProcessor" name="ContentFilter"/>
...
</actions>
La propia implementación de la transformación dependerá del formato del mensaje de entrada y del
formato del mensaje de salida. Este mecanismo puede ser ventajoso cuando se necesiten hacer
transformaciones muy particulares o que necesiten poco código para ser implementadas, comparando
con otros mecanismos que para algún caso particular necesiten más código que la implementación
directa en Java.
7. Transformación XSL
En la transformación XSL el trabajo de transformación se configura mediante reglas XSL en un
documento XML. La configuración de Smooks es mínima, solo se necesita decir donde está el archivo
XSL con la transformación, y el intérprete XSL es el que ejecuta la transformación. También se puede
poner la transformación XSL directamente dentro del mismo archivo de configuración de Smooks, por lo
que no es obligatorio tener un archivo XSL aparte.
Ejemplo de configuración de Smooks para una transformación XSL:
<smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.0.xsd">
<resource-config selector="$document">
<resource>/ConversionXSL/transformacion.xsl</resource>
</resource-config>
</smooks-resource-list>
En las últimas versiones de JBossESB se ha agregado también la posibilidad de realizar transformaciones
XSL nativas, es decir, sin necesidad de utilizar Smooks para dicho propósito. Entonces con
transformaciones XSL nativas se utilizaría el intérprete XSL de JBossESB en lugar del intérprete XSL de
Smooks.
Ejemplo de transformación XSL nativa:
Fue creada una clase llamada XSLActionProcessor extiende la clase AbstractActionPipelineProcessor,
esto es para poder ser llamada como una acción cuando se ejecute el pipeline de acciones del servicio en
el ESB. En XSLActionProcessor se implementa la configuración, carga de la transformación XSL y
ejecución de la transformación.
El constructor de la clase oficia de operación de configuración de la transformación, que previamente a
que el ESB ejecute la acción XSLActionProcessor en el pipeline de acciones, configura la acción para su
correcta operación obteniendo el nombre del archivo donde se encuentra la transformación y el mensaje
del ESB que contiene el mensaje XML a ser transformado.
Luego, automáticamente, el ESB invoca al método “process”, que es donde se ejecuta la lógica de la
acción, en este caso, es donde se carga la transformación XSL, donde se realiza la transformación y
donde se coloca el resultado en el mensaje del ESB para que pueda ser accedido por la próxima acción
del pipeline.
El método “getStylesheet” se utiliza como auxiliar para cargar el archivo XSL (con la transformación)
desde el sistema de archivos. Luego se creó la clase XSLTransformer, que es en la que se realiza
efectivamente la transformación XLS.
public class XSLActionProcessor
extends AbstractActionPipelineProcessor {
private static Map<String, String> cache = new HashMap<String, String>();
private String xsl;
private MessagePayloadProxy payloadProxy;
public XSLActionProcessor(ConfigTree configTree) {
xsl = configTree.getAttribute("xsl");
payloadProxy = new MessagePayloadProxy(configTree);
}
public Message process(Message message) throws ActionProcessingException {
try {
XSLTransformer transformer = new XSLTransformer();
System.err.println("XSLActionProcessor INPUT: " + message);
byte[] bytes = ((String) payloadProxy.getPayload(message)).getBytes();
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
8. String theXSL = getStylesheet(xsl);
ByteArrayOutputStream output =
(ByteArrayOutputStream) transformer.transform(input, theXSL);
message.getBody().add(output);
return message;
}
catch (Exception e) {
throw new ActionProcessingException(e);
}
}
/**
* Carga hoja de estilo XSL de un archivo o del cache.
*
* @return
* @throws IOException
*/
private String getStylesheet(String stylesheetFileName)
throws IOException {
String result = null;
synchronized (cache) {
result = cache.get(stylesheetFileName);
if (result == null) {
File file = new File(stylesheetFileName);
if(!file.exists()) {
throw new IllegalArgumentException("Input message file [" +
file.getAbsolutePath() + "] not found.");
}
result = FileUtil.readTextFile(file);
cache.put(stylesheetFileName, result);
}
else {
System.err.println("Stylesheet retrieved from cache");
}
return result;
}
}
}
public class XSLTransformer {
private static TransformerFactory tf = TransformerFactory.newInstance();
/**
* Ejecuta la transformacion XSL.
*
* @param input stream con datos de entrada
* @param xsl transformacion XSL (no el nombre del archivo es el contenido)
* @return La informacion transformada
* @throws TransformerException
*/
public OutputStream transform(InputStream input,
String xsl) throws TransformerException
{
if (input == null || xsl == null)
throw new IllegalArgumentException("input cannot be null");
Transformer t = tf.newTransformer( new StreamSource(
new StringReader(xsl) ) );
ByteArrayOutputStream output = new ByteArrayOutputStream(4096);
t.transform(new StreamSource(input), new StreamResult(output));
return output;
}
}
9. Transformaciones Java-Java mediante configuración Smooks
Este mecanismo de transformación se utiliza cuando se tiene una instancia de un modelo Java y se
quiere generar una instancia de otro modelo con la información que tiene el primer modelo. Existen dos
conceptos importantes llamados “Binding” y “Wiring”. “Binding” se utiliza para generar las instancias
simples de las clases del modelo destino, a las cuales se les setean los datos del modelo origen. “Wiring”
se utiliza para generar relaciones entre las instancias simples del modelo destino para formar una
estructura de datos completa. Ambas tareas se realizan mediante reglas de configuración de Smooks
basadas en XML. [7.1]
Una característica interesante es que para seleccionar los valores de los atributos del modelo origen se
utilizan expresiones XPath sobre un XML. Dicho XML es generado internamente por Smooks, a partir del
modelo de entrada, mediante XStream. XStream es una librería que permite serializar cualquier instancia
de modelo de datos a XML y volver del XML a la instancia. Entonces es necesario saber que forma tiene
el XML generado a partir del modelo de entrada para poder escribir las expresiones XPath.
Este mecanismo de transformación tiene varias restricciones, sobre todo en el modelo destino. En el
modelo origen no existen restricciones particulares, ya que en realidad la transformación se realiza
tomando la serialización a XML del modelo origen y no el propio modelo.
Para la transformación Java-Java es necesario que el modelo destino se comporte según las siguientes
reglas:
• Debe tener un constructor simple (sin argumentos).
• Debe tener setters públicos para las propiedades de cada clase. No es necesario seguir algún
formato de nombres particular para estos métodos, pero se aconseja seguir el estándar de
nombres para setters de Java.
• Setear una propiedad de forma directa no está soportado, es necesario hacerlo invocando al
setter de la propiedad.
Si no se siguen estas reglas, el transformador Smooks configurado mediante XML no sabrá como
establecer los valores, extraídos del modelo origen, en el modelo destino.
El problema que presenta esto, es que en la transformación de un modelo “custom” en el modelo de
referencia de HL7 (RIM), se debe utilizar alguna implementación de RIM. JavaSIG es una librería para
generar y consumir mensajes HL7, pero su implementación no sigue las reglas impuestas por este
mecanismo de transformación, por lo que no es posible utilizar dicha implementación. Se hizo una prueba
de esto, constatándose la imposibilidad de generar una instancia del RIM mediante este mecanismo de
transformación.
Para resolver este problema se plantearon las siguientes alternativas:
1. Probar la transformación generando el RIM a partir de esquemas XSD mediante JAXB y probar
utilizar esas clases.
2. No hacer la transformación mediante configuración XML, si no hacer una transformación
directamente implementada en Java.
3. Modificar el RIM implementado en JavaSIG para que las clases sigan las reglas antes
mencionadas.
1. Utilizando JAXB y transformación Java-Java:
Se pudo generar un modelo de clases a partir de los esquemas XSD para los mensajes CDA provistos
por la especificación de HL7 mediante la librería JAXB. En el anexo “Prueba transformación Java-Java
BindingAndWiring” se comenta cómo se generó el modelo a partir del XSD utilizando JAXB. Uno de los
problemas que tiene este modelo de clases es que para las colecciones no tiene un método del tipo “add
to collection”, que es lo que necesita Smooks para generar la colección y agregarle elementos, las clases
que tienen atributos de tipo colección tienen un método get para la colección y luego se invoca el “add”
directamente sobre la colección. Para poder ejecutar una transformación mínima se tuvo que modificar
algunas clases que tenían atributos de tipo colección, agregando un método “addXXX” donde “XXX” es el
nombre del campo de tipo colección.
Un tema a tener en cuenta es que Smooks sirve para colocar el modelo origen en un modelo destino,
pero no se puede generar más modelo. Por ejemplo, si el modelo destino tiene más información que el
10. mensaje original, este tipo de transformación no permite generar el modelo extra. Una posible solución es
la de agregar la información faltante en una etapa posterior a la transformación Java-Java, por ejemplo
utilizando transformaciones XSL o templates FreeMarker. Esto sucede cuando se tiene un modelo
“custom” y se quiere llevar a un CDA, se pueden colocar los valores presentes en el modelo “custom” que
se correspondan en la estructura CDA, pero CDA necesita mucha más información, porque tiene una
estructura más compleja y muchos atributos que no existen en el modelo “custom”, esto es porque CDA
agrega información para poder hacer una interpretación semántica de los documentos CDA. Por ejemplo
existen muchos atributos con valores fijos que no pueden ser generados directamente con este
mecanismo de transformación. En el caso inverso no habría problemas, es decir pasar CDA a el modelo
“custom”, porque se toman del CDA solo los valores que se necesiten, los demás valores no serían
utilizados para generar la instancia del modelo “custom”.
En conclusión, esta prueba necesita gran cantidad de código de configuración para poder hacer la
transformación Java-Java mediante Binding y Wiring, y las restricciones sobre el modelo de salida lo
hacen poco flexible. Además la configuración es como implementar la transformación directamente en
Java, lo que no solo llevaría menos código, si no sería más flexible, ya que puedo utilizar la API que me
provea el modelo destino sin ninguna restricciones sobre los métodos que debe proveer.
En el anexo “Prueba transformación Java-Java BindingAndWiring” están las referencias de
implementación de la transformación probada y los resultados obtenidos.
2. Implementar la transformación directamente en Java:
Esta opción no fue probada por ser considerada la opción más simple, ya que lo único que se necesita
hacer es generar una instancia del modelo destino, e ir pidiendo los valores a la instancia del modelo
origen y setear dichos valores en la instancia destino. Esta opción no tendría inconvenientes con la API
del modelo destino, al contrario del mecanismo de Bindings y Wirings que pone estrictas reglas sobre
dicha API. De modo que esta es una solución más genérica que la antes mencionada y también necesita
escribir menos código para ser implementada, la diferencia es que mientras aquí se escribe código Java,
en la opción anterior se escribían reglas en formato XML. Como conclusión, esta sería una buena opción
si se tiene un modelo de datos destino con una API que no cumple con las restricciones del método
anterior, o también si la estructura es muy compleja, cuanto más compleja es la estructura más reglas
deben ser escritas y cada regla lleva en promedio 8 líneas de código, si en una implementación Java se
necesitaran menos líneas que estas, al agregar atributos o relaciones, se necesitaría mucho menos
código para implementar la misma transformación.
3. Modificar JavaSIG
Esta puede ser la opción más costosa, ya que la API de la implementación del RIM de JavaSIG es muy
particular, no solo habría que modificar decenas de clases, si no que habría que escribir gran cantidad de
reglas para poder realizar la transformación. Esta opción no se llevó a cabo por las razones mencionadas.
En este caso, implementar la transformación directamente en Java sería una mejor opción.
11. Transformación FreeMarker
En la transformación FreeMarker, la información de la transformación se encuentra en la configuración de
Smooks (definido mediante reglas FreeMarker) o también puede estar en un archivo aparte (los archivos
FreeMarker tienen extensión .FTL por FreeMarker Template). El formato en el que se especifica es el
marcado por FreeMarker y se puede acceder aquí: http://www.milyn.org/xsd/smooks/freemarker-1.1.xsd,
es declarativo como el XSL pero parece menos verboso que este último, o sea, con menos código se
hace lo mismo.
FreeMarker es una herramienta de “templating” o aplicación de plantillas que sirve para recorrer un
archivo de texto (estructurado o no), extrayendo su información y generando un nuevo archivo te texto
(estructurado o no) con un formato distinto. FreeMarker es soportado por los creadores de Smooks, los
cuales lo prefieren sobre XSL.
Comparándolo con XSL, FreeMarker está hecho para transformación de texto en general, mientras que
XSL solo sirve para procesar texto estructurado en forma de árbol, como ser un archivo XML. Por otra
parte, FreeMarker tiene constructores basados en programación imperativa, mientras que XSL los
constructores están basados en programación funcional, esto se refiere a la característica de poder definir
funciones y variables en cada una de las herramientas, los desarrolladores de FreeMarker afirman que
esto es una ventaja porque la mayoría de los desarrolladores conocen la programación procedural-
imperativa, mientras que pocos conocen la programación funcional. FreeMarker también apunta a que
sea más fácil de aprender y entender que XSL. Obviamente la principal diferencia entre ambas
tecnologías es que XSL es un estándar y tiene diversas implementaciones, y FreeMarker una herramienta
Java.
Si bien se realizó una implementación de transformación utilizando FreeMarker, sería interesante poder
comparar más a fondo las opciones de transformación utilizando FreeMarker y XSL, y verificar cual es la
opción más simple de utilizar, la que posee más funcionalidades que ayuden a realizar transformaciones
según las características particulares de las transformaciones que se necesiten, si es necesario escribir
más o menos reglas de transformación, y evaluar la capacidad, características y estabilidad de cada
herramienta. Lo que si se constató es que en las pruebas, para lograr el mismo resultado en la
transformación XML custom a XML CDA, el trabajo que tomó hacer funcionar la transformación XSL fue
aproximadamente una semana, mientras que el trabajo que tomó hacer funcionar la transformación
completa con FreeMarker fueron alrededor de cuatro horas. También se pudo ver que el código necesario
para especificar la transformación XSL es aproximadamente tres veces más que el código necesario para
especificar el template FreeMarker. Obviamente esta es una comparación en solo una transformación
particular, por lo que no se puede derivar un resultado general y decir que estas diferencias se dan en
otras transformaciones.
Se realizó una implementación de una transformación de un mensaje XML “custom” a un mensaje XML
CDA, la documentación se encuentra en el documento anexo “Prueba transformación XML-XML
FreeMarker”.
Transformación basada en modelo
Por último, para las transformaciones basadas en modelos, es necesario definir un modelo de datos
intermedio entre la entrada y la salida, al cual se pueda mapear la entrada y desde el cual se pueda
generar la salida. El único código Java a implementar es dicho modelo de datos y la transformación se
especifica en la configuración de Smooks, donde se definen las correspondencias del formato de entrada
al modelo y del modelo al formato de salida. La ventaja de este esquema es la posibilidad de definir
múltiples formatos de entrada y salida pudiendo hacer combinaciones de éstos sin necesidad de
especificar todas las combinaciones entrada-salida, solo se especifican las diferentes combinaciones
entrada-modelo y modelo-salida, luego automáticamente se obtienen todas las combinaciones entrada-
modelo-salida. Para la transformación Java-Java se utiliza la notación que se utiliza en la transformación
basada en modelos para especificar dicha transformación. Esta notación está basada en “bindings” que
toman un valor de la fuente y lo ponen en un lugar del destino, en el caso Java-Java, esto es hacer “gets”
en el modelo fuente y “sets” en el modelo destino.
12. Propuesta de transformación Java-Java híbrida
Si bien se analizaron varias opciones de transformación entre instancias de modelos Java, hasta ahora no
se analizó la posibilidad de mezclar otros mecanismos de transformación para lograrlo. Como se comentó
en la respectiva sección, para realizar la transformación Java-Java, Smooks internamente serializa la
instancia del modelo origen a un XML mediante la librería XStream.
Transformación Java-Java mediante FreeMarker + XStream
Esta opción utilizaría la transformación FreeMarker y la funcionalidad de convertir XML a una instancia y
viceversa de XStream, los pasos serían los siguientes:
1. Recibir una instancia del modelo origen.
2. Registrarla como bean en el mapa de beans del contexto de la transformación Smooks.
3. Tener un template FreeMarker que ejecuta la transformación obteniendo los datos desde el bean
registrado con el modelo original y generando el XML del modelo destino, siguiendo el formato
de XStream de una instancia válida de dicho modelo.
4. Mediante XStream, levantar el XML generado y convertirlo a la instancia del modelo destino en
memoria.
Figura 4: Transformación Java-Java con FreeMarker+XStream
Aunque esta opción es similar a la prueba de transformación XML-XML mediante FreeMarker, los pasos
de transformación no son los mismos (por lo menos no en el mismo orden). La documentación completa
sobre la transformación XML-XML mediante FreeMarker se encuentra en el documento anexo llamado
“Prueba transformacion XML-XML FreeMarker”.
Otra opción sería teniendo una transformación XSL en lugar del template FreeMarker para generar el
XML de salida compatible con XStream para que este pueda generar una instancia del modelo de salida.
Distintas opciones de transformación se pueden generar mezclando los mecanismos ya vistos, también
se pueden utilizar otras herramientas como XStream, esto posibilita la realización de distintos pasos de
transformación de uno o más mensajes de forma simultánea, donde se pueden realizar distintas
transformaciones mediante distintos mecanismos para diferentes secciones de los mensajes que se
procesen.
13. Especificación Técnica de Implementación de HL7
Si bien todos los mecanismos vistos hasta el momento son válidos para definir transformaciones entre
distintos formatos de mensajes, siendo uno de los formatos HL7, la mayoría de los mismos no cumplen
con la especificación técnica de implementación de HL7 (ITS por sus siglas en inglés). La ITS es la forma
estándar en la que dos sistemas deberían interoperar utilizando mensajes HL7. En la misma se definen
una serie de transformaciones que son necesarias para que dos sistemas que tengan su propio modelo
de datos, lo puedan hacer corresponder en el RIM (modelo de referencia de HL7). Estas
correspondencias de su modelo de datos al RIM y viceversa garantiza que el intercambio de datos será
semánticamente correcto, y esto habilita a los sistemas para poder enviar y recibir mensajes HL7, que no
son más que instancias de RIM serializadas a XML. En la siguiente imagen se muestran los pasos
definidos por la ITS:
Figura 5: Especificación Técnica de Implementación
El escenario que plantea la ITS es el de dos sistemas, con un sistema emisor y otro receptor. Cada uno
con un modelo particular de datos, más algún mecanismo que ejecute transformaciones entre esos datos
e instancias de RIM. Los pasos que sigue la ITS son:
1. El sistema “emisor” tiene los datos que desea enviar al receptor en su propio modelo.
2. Mediante una transformación, genera una instancia de RIM a la cual le incluye los datos de su
modelo.
3. Ahora se tiene una instancia de RIM con todos los datos que se quieren enviar. Esta instancia no
es más que el mensaje que se va a enviar, en memoria.
4. Mediante algún mecanismo que serialice modelos RIM, se genera un mensaje XML válido según
los esquemas XSD distribuidos por HL7 (dependiendo del tipo de mensaje, el XSD puede variar).
5. Ahora el mensaje en formato XML es enviado a través de un canal hacia el sistema “receptor”.
6. El “receptor” recibe el XML y reconstruye la instancia de RIM a partir del mensaje XML.
7. Se obtiene una instancia de RIM análoga a la que se obtuvo en el paso 3 dentro del sistema
“emisor”.
8. A través de una transformación definida en el sistema “receptor”, se genera una instancia del
modelo de datos de dicho sistema a partir de los datos contenidos en la instancia de RIM.
9. Se tiene la instancia del modelo de información del sistema “receptor”, que es semánticamente
equivalente a la instancia del sistema de información del sistema “emisor” (la información que
este sistema quería transmitir).
14. Propuesta de transformación siguiendo la HL7 ITS
Por lo mencionado anteriormente resulta interesante plantear una solución de transformación que cumpla
con la ITS y utilice el ESB para realizar las transformaciones necesarias.
Figura 6: Arquitectura del problema de envío y transformación de mensajes
En el caso de que el Sistema A (emisor) envíe mensajes “custom” al Sistema B (receptor) el cual los
recibe en formato HL7, donde las comunicaciones se realizan mediante Web Services, en el ESB se haría
el pasaje del modelo SOAP a una instancia del modelo de datos del Sistema A. Luego, dentro del ESB se
hace la conversión entre ese modelo de datos y el modelo RIM, por último se genera el XML a partir de la
instancia RIM obtenida (esta transformación es automática utilizando una librería como JavaSIG). Ese
XML es el que es enviado al Sistema B.
Para el caso inverso, donde el Sistema B envía un XML HL7 al ESB, el ESB lo transforma a una instancia
del RIM (esta transformación es automática utilizando una librería como JavaSIG). Luego, con los datos
del modelo RIM se genera el modelo de datos que espera recibir el Sistema A, ese modelo es enviado
directamente al Sistema A mediante Web Services SOAP (SOAP se encarga de hacer la serialización a
XML).
15. Relevamiento de opciones para ruteo de mensajes
El esquema general de procesamiento de un mensaje recibido es similar al estilo de arquitectura de
“tubos y filtro”, donde un mensaje es recibido por un filtro, procesado y luego puesto en un tubo hacia otro
filtro. En este esquema debe haber algún mecanismo que decida en que tubo se coloca la salida de cada
filtro, porque puede haber varios tubos distintos de donde elegir. La selección del tubo en el que se
colocará la salida del filtro es llamado “ruteo” del mensaje, y se realiza mediante la verificación de un
conjunto de condiciones, lo que permite tomar la decisión de cual será el tubo destino, entonces la ruta
del mensaje será el conjunto de tubos y filtros por los que ha pasado el mismo.
JBossESB ofrece cuatro tipos de ruteo:
1. Aggregator: es una implementación del patrón “Aggregator” de los “Enterprise Integration
Patterns”, el mismo se utiliza para combinar información de varios mensajes distintos. [9]
2. Content Based Router (CBR): acción de ruteo basado en el contenido de los mensajes y la
definición de reglas.
3. Static Router: versión simplificada del Content Based Router que no soporta reglas de ruteo
basadas en contenido.
4. Notifier: envía notificaciones a una lista de destinos configurable. Está implementada como
ejemplo, no debería ser usada directamente, si no que debería ser extendida por el usuario.
Considerando los casos de prueba planteados para realizar en el presente trabajo (*), el “Content Based
Router” podría ser de gran utilidad porque podría detectar que tipo de mensaje está recibiendo el ESB, y
en base a reglas que se definan, rutear el mensaje al servicio correcto que pueda procesarlo. El CBR
puede ser utilizado para enrutar el mensaje al próximo destino basándose en el contenido del mensaje.
Por defecto el ESB utiliza “JBossRules” como motor de evaluación de reglas, aunque puede ser
configurado para utilizar otro motor.
Para utilizar el CBR se debe levantar el servicio de CBR. Luego se deben definir un conjunto de reglas
(**). Para la definición de reglas sobre mensajes XML es conveniente utilizar evaluaciones basadas en
XPath, especialmente diseñado para poder extraer valores presentes en el XML de forma sencilla.
Una vez que se tiene el CBR andando, se le pueden enviar mensajes. Para esto se debe agregar dicho
envío como una acción en algún lugar del pipeline de acciones.
Luego existen tres modos de ejecución:
1. routeAndDeliver: rutea y entrega el mensaje al(los) destino(s)
2. route: rutea el mensaje y retorna un mensaje con una lista de destinos adjuntos a él.
3. deliver: simplemente entrega el mensaje, un mensaje ruteado previamente será entregado a
su(s) destino(s).
(*) Los casos de prueba se mencionan en el documento “TSI4 Anexo 1 – Bitácora.doc”, en la sección “Etapa 2:
Definición de la arquitectura y casos de prueba”.
(**) Estas pueden ser creadas mediante JBossIDE [Eclipse+JBossTools] con el plugin de JBossRules.
16. Descripción del prototipo logrado
Para la implementación del prototipo se eligió la transformación XML-XML mediante XSL. El prototipo
define dos canales, uno que recibe un mensaje XML “custom”, mediante un punto de entrada expuesto
mediante Web Services, y genera un mensaje CDA que es enviado a un sistema externo mediante Web
Services, el otro recibe un mensaje CDA, mediante un punto de entrada expuesto mediante Web
Services, y genera un mensaje XML “custom” que es enviado a otro sistema mediante Web Services. El
prototipo fue implementado en el proyecto “ESBWS” incluido en el directorio de código de la entrega del
taller.
Los pipelines de acciones de ambos canales son similares:
Figura 7: Canales en el ESB prototipo.
Lista de acciones y su funcionalidad:
• print-before: se encarga de mostrar en pantalla el mensaje entrante.
• ContentFilter: transforma el mensaje en el formato de entrada al formato de salida.
• print-after: muestra en pantalla el mensaje de salida.
• testStore: almacena el mensaje de salida en el filesystem.
• request-ws-processor: se encarga de preparar la llamada al Web Service, especificando que
método del servicio será invocado y con que parámetros.
• soap-ws-client: esta acción invoca al Web Service.
• response-ws-processor: muestra en pantalla el resultado de la invocación al Web Service.
Algunas acciones tienen asociadas una clase Java que es la que implementa la acción, otras poseen una
implementación por defecto provista por el ESB, este último caso es el de las acciones: print-before, print-
after y soap-ws-client.
17. Descripción de la implementación de las acciones
Acción ContentFilter
Esta acción está asociada a la clase XSLActionProcessor, que es la clase en la que se implementó dicha
acción. La funcionalidad básica de esta clase es cargar la transformación XSL del filesystem, obtener el
mensaje XML a transformar (el cual se encuentra en el body del mensaje del ESB), y aplicar
efectivamente la transformación al mensaje. El mensaje XML resultante se coloca en el mensaje del ESB,
así la próxima acción en el pipeline de acciones puede acceder al mensaje transformado.
A continuación se presenta la implementación de la clase XSLActionProcessor, la cual ejecuta una
transformación XSL nativa sobre un mensaje XML. Esta acción es utilizada en ambos canales, al que se
envía un mensaje custom y al que se envía un mensaje HL7, como implementación de sus respectivas
acciones ContentFilter. Obviamente, en cada canal debe implementar una transformación distinta, esto se
lleva a cabo mediante la configuración de una transformación XSL distinta para cada caso.
Todas las acciones en el pipeline deben extender la clase AbstractActionPipelineProcessor [4], o su
superclase AbstractActionLifecycle [3], la cual define el método “process” que será descrito más adelante.
public class XSLActionProcessor
extends AbstractActionPipelineProcessor {
El campo “cache” es utilizado para almacenar la transformación XSL que es levantada desde el
filesystem, de manera que se tenga que hacer una sola lectura del filesystem y en posteriores ejecuciones
se obtiene el XSLT desde el cache.
private static Map<String, String> cache = new HashMap<String, String>();
El campo “xsl” es el que almacena el nombre del archivo XSL que deberá ser leído.
private String xsl;
El campo “payloadProxy” se utiliza para acceder a los contenidos del mensaje del ESB.
private MessagePayloadProxy payloadProxy;
El constructor recibe la configuración del ESB con toda la información necesaria para que la acción pueda
ejecutarse correctamente. Por ejemplo se extrae el nombre de la transformación XSL que hay que leer, y
trae el mensaje del ESB de donde se leerá el mensaje XML a ser transformado.
public XSLActionProcessor(ConfigTree configTree) {
xsl = configTree.getAttribute("xsl");
payloadProxy = new MessagePayloadProxy(configTree);
}
El método “process” es donde se realiza la transformación.
public Message process(Message message)
throws ActionProcessingException {
Aquí se crea la instancia del transformador, donde se realiza la transformación.
try {
SAXONTransformer transformer = new SAXONTransformer();
System.err.println("XSLActionProcessor INPUT: " + message);
Se leen los bytes del mensaje XML de entrada y ponen dentro de la variable “input”.
byte[] bytes = ((String) payloadProxy.getPayload(message)).getBytes();
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
18. Se lee el archivo de la transformación del filesystem, o desde cache si ya fue cargado, y se pone su
contenido en la variable “theXSL”.
String theXSL = getStylesheet(xsl);
Mediante el transformador, se ejecuta la transformación XSL sobre el input, obteniéndose el resultado en
la variable “output”.
ByteArrayOutputStream output =
(ByteArrayOutputStream) transformer.transform(input, theXSL);
System.err.println("XSLActionProcessor OUTPUT: " + output);
El mensaje obtenido se agrega al mensaje del ESB para que quede disponible para la siguiente acción
del pipeline.
message.getBody().add(output);
return message;
}
catch (Exception e) {
throw new ActionProcessingException(e);
}
}
El método “getStylesheet” se utiliza para leer el archivo que contiene la transformación desde el
filesystem, o desde el cache si es que ya ha sido leído.
private String getStylesheet(String stylesheetFileName)
throws IOException {
String result = null;
synchronized (cache) {
Se intenta obtener el contenido de la transformación desde el cache.
result = cache.get(stylesheetFileName);
if (result == null) {
System.err.println("Loading stylesheet from file "+stylesheetFileName);
Se lee el archivo de disco.
File file = new File(stylesheetFileName);
if(!file.exists()) {
throw new IllegalArgumentException("Input message file [" +
file.getAbsolutePath() + "] not found.");
}
result = FileUtil.readTextFile(file);
Se agrega el contenido leído al cache para acelerar futuras cargas.
cache.put(stylesheetFileName, result);
}
else {
System.err.println(" Stylesheet retrieved from cache");
}
return result;
}
}
19. Acción testStore
Esta acción almacena el mensaje transformado en el filesystem del sistema donde esté corriendo el ESB.
La misma está implementada en la clase StoreMessage.
Como toda acción del pipeline, la clase StoreMessage extiende la clase AbstractActionPipelineProcessor.
public class StoreMessage extends AbstractActionPipelineProcessor {
private MessagePayloadProxy payloadProxy;
public StoreMessage(ConfigTree config) {
payloadProxy = new MessagePayloadProxy(config);
}
El método “appendToFile” toma cualquier contenido en forma de String y lo agrega al final de un archivo,
conservando su contenido actual. El archivo es creado si es que no existe.
public static void appendToFile(String dir, String fileName, String content)
{
BufferedWriter bw = null;
try {
bw = new BufferedWriter(new FileWriter(dir + "/" + fileName, true));
bw.write(content);
bw.newLine();
bw.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
} finally { // always close the file
if (bw != null) try {
bw.close();
} catch (IOException ioe2) { /* just ignore it */ }
} // end try/catch/finally
}
El método “process” genera un nombre de archivo basado en la fecha actual y una serie de números
random, de forma de generar un archivo diferente a los demás. Luego obtiene el contenido del mensaje a
almacenar, esto lo saca del mensaje del ESB a través del payloadProxy que se utiliza para acceder a los
datos del mensaje del ESB. Por último se llama al método “appendToFile” para generar el archivo con el
contenido obtenido.
public Message process(Message message) throws ActionProcessingException
{
Date d = new Date();
String name = ""+(d.getYear()+1900)+(d.getMonth()+1)+d.getDate();
try {
System.err.println("PayloadMessageClass: "+
payloadProxy.getPayload(message).getClass());
byte[] btextToStore = ((ByteArrayOutputStream)
payloadProxy.getPayload(message)).toByteArray();
System.err.println( "textToStore: " + new String(btextToStore) );
String textToStore = new String(btextToStore);
appendToFile ("./", "message_"+name+"["+(Math.random()*5)+"].xml",
textToStore.toString());
}
catch (MessageDeliverException e) {
throw new ActionProcessingException(e);
}
return message;
}
}
20. Acción request-ws-processor
Esta acción se utiliza para preparar la llamada al Web Service al que se enviará el mensaje transformado,
esto está implementado en la clase MyRequestAction. Esta clase MyRequestAction extiende a la clase
AbstractActionLifecycle [3]. Antes se vio que las acciones extendían a la clase
AbstractActionPipelineProcessor, esto es porque AbstractActionPipelineProcessor a su vez extiende a
AbstractActionLifecycle.
public class MyRequestAction extends AbstractActionLifecycle {
protected ConfigTree _config;
private MessagePayloadProxy payloadProxy;
En el constructor se recibe la configuración del ESB y el estado actual del mensaje.
public MyRequestAction(ConfigTree config) {
_config = config;
payloadProxy = new MessagePayloadProxy(config);
}
El método “process” es quien obtiene el mensaje a enviar y configura los parámetros para la invocación al
Web Service. En este caso se obtiene el mensaje XML transformado, que está dentro del mensaje del
ESB. Luego se agrega a un mapa de parámetros mediante la clave “sendMessage.message”, esto quiere
decir que se va a invocar al método “sendMessage” del Web Service, en donde existe un parámetro
llamado “message” que tendrá como valor el mensaje XML transformado.
public Message process(Message message) throws Exception {
byte[] btext = ((ByteArrayOutputStream)
payloadProxy.getPayload(message)).toByteArray();
String msgBody = new String(btext);
HashMap requestMap = new HashMap();
// add paramaters to the web service request map
// Esto es: metodo.paramName
requestMap.put("sendMessage.message", msgBody);
message.getBody().add(requestMap);
System.out.println("Request map is: " + requestMap.toString());
return message;
}
}
Acción response-ws-processor
Esta acción simplemente muestra en pantalla el resultado de la invocación al Web Service. Está
implementada en la clase MyResponseAction, la cual es análoga a la anterior, solo que en el método
“process” se obtiene el resultado de la invocación al Web Service desde el mensaje del ESB y se hace un
print del mismo a la consola.
public class MyResponseAction extends AbstractActionLifecycle {
protected ConfigTree _config;
public MyResponseAction(ConfigTree config) {
_config = config;
}
public Message process(Message message) throws Exception {
Map responseMsg = (Map)message.getBody().get(Body.DEFAULT_LOCATION);
System.out.println("Response Map is: " + responseMsg);
return message;
}
}
21. Trabajo futuro y mejoras
1. Transformación XSL genérica: sería el generar una transformación XSL que sea equivalente al
modelo CDA, de esa forma se podría consumir o producir cualquier CDA válido. Posteriormente
a esta prueba se buscarían las conclusiones de si es posible crear tal transformación, de que tan
costosa sería crearla (comparándola con otros tipos de transformaciones), verificar si tiene
problemas de performance y probar varios casos con distintos CDA.
2. Ruteo del mensaje: si bien en el presente trabajo se relevaron las opciones de ruteo que ofrece
JBossESB, no se agregó la funcionalidad de ruteo al prototipo. Sería interesante probar, por lo
menos, el ruteo basado en contenido, de forma que si se recibe un mensaje CDA dependiendo
de sus características sea enviado a un determinado canal del ESB (o a un servicio externo al
ESB) para ser procesado total o parcialmente en dicho canal.
3. Solicitar información a servicios externos: un posible caso es que el mensaje entrante tenga
menos información de la necesaria para generar el mensaje saliente, sería interesante que,
basándose en la información que si se tiene, se consulten determinados servicios que provean la
información faltante, de forma de contar con toda la información necesaria para generar el
mensaje saliente.
4. Comparar en profundidad las alternativas de utilizar XSL y FreeMarker como opciones de
transformación de mensajes, probando y comparando las funcionalidades que sean útiles según
los casos particulares de prueba, con cual alternativa se necesita escribir menos código, cual es
más sencilla de usar y aprender, etc.
Referencias
[1] Proyecto JBossESB
http://www.jboss.org/jbossesb/
[2] Mensaje de JBossESB
http://www.jboss.org/jbossesb/docs/4.4.GA/javadoc/esb/org/jboss/soa/esb/message/package-
summary.html
[3] Clase AbstractActionLifecycle
http://www.jboss.org/jbossesb/docs/4.4.GA/javadoc/esb/org/jboss/soa/esb/actions/AbstractActionLifecycle.
html
[4] Clase AbstractActionPipelineProcessor
http://www.jboss.org/jbossesb/docs/4.4.GA/javadoc/esb/org/jboss/soa/esb/actions/AbstractActionPipelineP
rocessor.html
[5] Message Transformation on JBossESB
http://www.jboss.org/community/docs/DOC-11397
[6] Smooks User Guide
http://docs.codehaus.org/display/MILYN/Smooks+User+Guide
[7] Smooks Examples
http://www.smooks.org/documentation/documentation-smooks-1-1-x/examples
[7.1] Smooks Example JavaToJava:
http://docs.codehaus.org/display/MILYN/Smooks+Example+-+java-to-java
[7.2] Smooks Example XMLToJava:
http://docs.codehaus.org/display/MILYN/Smooks+Example+-+xml-to-java
[7.3] Smooks Example JavaToXML:
http://svn.codehaus.org/milyn/trunk/smooks-examples/java-to-xml/
[8] Componente Smooks de JavaBeans
http://milyn.codehaus.org/javadoc/v1.0/smooks-cartridges/javabean/
[9] Enterprise Integration Patterns
http://www.enterpriseintegrationpatterns.com/eaipatterns.html