Este documento proporciona una visión general de las técnicas de transformación de datos en Camel, incluyendo el uso de EIPs como Message Translator y Content Enricher, formatos de datos como CSV y JSON, plantillas como Velocity, y el mecanismo de conversión de tipos de Camel. También describe cómo implementar la transformación mediante procesadores, beans y la operación <transform> en XML, con ejemplos de código.
2. Contenido
1. Vista General de la Transformación de Datos
2. Transformando datos usando EIPs y Java
1. Usando el EIP Message Translator
2. Usando el EIP Content Enricher
3. Transformando XML
1. Transformando XML con XSLT
2. Transformando XML con marshalling de objetos
4. Transformando con formatos de datos
1. Formatos de datos provistos con Camel
2. Usando el formato de dato CSV de Camel
3. Usando el formato de dato Bindy de Camel
4. Usando el formato de dato JSON de Camel
5. Configurando formatos de datos Camel
6. Escribiendo tu propio formato de datos
3. Contenido
5. Transformando con plantillas
1. Usando Apache Velocity
6. A cerca de los convertidores de tipo Camel
5. Cómo funciona el mecanismo convertidor de tipo Camel
6. Usando los convertidores de tipo Camel
7. Escribiendo tu propio convertidor de tipo
4. 1.Vista General de la Transformación de Datos
Camel proporciona muchas técnicas para transformación de datos.
La transformación de datos es un término amplio que cubre dos
tipos de transformación:
Transformación de formatos de datos. El formato de datos del
cuerpo del mensaje es transformado de una forma a otra. Por
ejemplo, un registro CSV es formateado como XML.
Transformación de tipo de datos. El tipo de datos del cuerpo del
mensaje es transformado de un tipo a otro. Por ejemplo, un
java.lang.String es transformado en un javax.jms.TextMessage.
La figura 3.1 ilustra el principio de transformar el cuerpo de un
mensaje de un formato a otro. Esta combinación puede involucrar
cualquier combinación de tipo y formato.
5. En la mayoría de los casos la transformación de datos con la que te
enfrentarás con Camel es transformación de formato, donde tienes
que mediar entre dos protocolos. Camel tiene un mecanismo
convertidor de tipo incorporado que puede convertir
automáticamente entre dos tipos, lo cual reduce mucho la
necesidad de tratar para los usuarios finales con transformaciones
de tipo.
1.Vista General de la Transformación de Datos
Figura 3.1. Camel ofrece
muchas características para
transformación de datos de un
formato a otro
6. Transformación de datos con Camel
En Camel la transformación de datos generalmente toma lugar en
las seis formas listadas en la tabla 3.1.
7. Transformación Descripción
Transformación de datos en
routes
Puedes explícitamente forzar la transformación en el route usando los EIPs
Message Translator o Content Enricher. Esto te da el poder de hacer mapeos
de datos usando código regular Java.
Transformación de datos
usando componentes
Camel proporciona un rango de componentes para transformación, como lo
es el componente XSLT para transformación XML.
Transformación de datos
usando formatos de datos
Los formatos de datos son transformadores Camel que vienen en pares para
transformar datos atrás y hacia adelante entre formatos bien conocidos
Transformación de datos
usando templates
Camel proporciona un rango de componentes para transformar usando
plantillas, como Apache Velocity.
Transformación de tipos de
datos usando el mecanismo
convertidor de tipos de Camel
Camel tiene un mecanismo convertidor de tipos elaborado que se activa bajo
demanda. Esto es conveniente cuando necesitas convertir de tipos comunes
como java.lang.Integer a java.lang.String o aún de java.io.File a
java.lang.String.
Transformación de mensajes
en adaptadores de
componentes
Muchos componentes de Camel se adaptan a varios protocolos comúnmente
usados y, como tal, necesita ser capaz de transformar mensajes en la medida
en que viajan a y desde estos protocolos. Frecuentemente estos componentes
usan una combinación de transformaciones de datos y convertidores de tipo
personalizadas.
8. 2. Transformando datos usando EIPs y Java
Data Mapping es el proceso de mapear entre dos modelos de datos
distintos, y es un factor clave en la integración de datos. Hay
muchos estándares existentes para modelos de datos, gobernados
por varias organizaciones o comités. Por tal, frecuentemente te
encontrarás necesario mapear del modelo de datos personalizado
de una compañía a un modelo de datos estándar.
Camel proporciona gran libertad en el mapeo de datos debido a
que te permite usar código Java –no estás limitado a usar una
herramienta de mapeo de datos particular que en la primera podría
parecer elegante pero que torna a hacer las cosas imposibles.
Aquí veremos cómo mapear datos usando un Processor, el cual es
una API Camel. Camel también usa beans para mapear, lo cual es
una buena práctica, debido a que le permite a tu lógica de mapeo
ser independiente de la API de Camel.
9. Usando el EIP Message Translator
El EIP Mesage Translator es ilustrado en la figura 3.2
Este patrón cubre traducir un mensaje de un formato a otro. Es el
equivalente al patrón Adapter del libro Gang of Four.
Camel provee tres formas de usar este patrón:
Usando un Processor
Usando beans
Usando <transform>
10. Transformando usando un Processor
El Processor de Camel es una interface definida en
org.apache.camel.Processor con un sólo método:
public void process(Exchange exchange) throws Exception;
El Processor es una API de bajo nivel donde trabajas directamente en la
instancia Exchange de Camel. Te da acceso completo a todas las partes de
Camel que se mueven del CamelContext, del cual puedes obtener del
Exchange usando el método getCamelContext.
Veamos un ejemplo. En Autopartes Rider se te ha solicitado generar
diariamente reportes de órdenes recientemente recibidas para ser sacadas a
un archivo CSV. La compañía usa un formato personalizado para entradas de
órdenes, pero hace las cosas fáciles, ellos ya tienen un servicio HTTP que
retorna una lista de órdenes para cualquier dato que ingreses. El reto al que
te enfrentas es mapear los datos retornados del servicio HTTP a un formato
CSV y escribir el reporte a un archivo.
11. Listado 3.1 Usando un Processor para traducir desde un formato personalizado a formato CSV
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
public class OrderToCsvProcessor implements Processor {
public void process(Exchange exchange) throws Exception {
String custom = exchange.getIn().getBody(String.class);
String id = custom.substring(0, 9);
String customerId = custom.substring(10, 19);
String date = custom.substring(20, 29);
String items = custom.substring(30);
String[] itemIds = items.split("@");
StringBuilder csv = new StringBuilder();
csv.append(id.trim());
csv.append(",").append(date.trim());
csv.append(",").append(customerId.trim());
for (String item : itemIds) {
csv.append(",").append(item.trim());
}
exchange.getIn().setBody(csv.toString());
}
}
Obtiene payload
personalizado
Extrae datos a
variables locales
Mapea a
formato CSV
Remplaza payload
con payload CSV
12. Primero coges el payload de formato personalizado del exchange.
Es de tipo String, de forma que pasas una String como parametro
de entrada para tener el payload retornado como una String. Luego
extraes datos del formato personalizado a las variables locales. El
formato personalizado podría ser cualquier cosa, pero en este
ejemplo es un formato personalizado de longitud fija. Luego
mapeas el formato CSV construyendo una string con valores
separados por coma. Finalmente, remplaza el payload
personalizado con tu nuevo payload CSV.
Usar un processor tiene una desventaja, requieres hacer uso de la
API Camel.
13. Importante
Usando los métodos getIn y getOut en exchanges
El Exchange Camel define dos métodos para retomar mensajes: getIn y
getOut. El método getIn retorna el mensaje entrante, y el método getOut
accesa el mensaje saliente.
Hay dos escenarios donde el usuario final Camel tendrá que decidir entre usar
estos métodos:
Un escenario de sólo lectura, como cuando envías a log el mensaje
entrante.
Un escenario de escritura, como cuando estás transformando el mensaje
En el segundo escenario asumirías que se debería usar getOut. Eso es correcto
acorde a la teoría, pero en la práctica hay una trampa cuando se usa getOut:
los headers y attachments del mensaje entrante se perderán. Frecuentemente
esto no es lo que quieres, de forma que copiar los headers y attachments del
mensaje entrante al mensaje saliente, puede ser tedioso. La alternativa es
asignar los cambios directamente en el mensaje entrante usando getIn, y no
usar getOut en todo.
14. Transformando usando Beans
Usar beans es una buena práctica debido a que te permite usar
cualquier código Java y librerías que desees. Camel no impone
restricciones en ninguna cosa. Camel puede invocar cualquier bean
que elijas, de forma que puedes usar beans existentes sin tener que
rescribirlos o recompilarlos.
15. Listado 3.2 Usando un bean para traducir de un formato personalizado a formato CSV
public class OrderToCsvBean {
public static String map(String custom) {
String id = custom.substring(0, 9);
String customerId = custom.substring(10, 19);
String date = custom.substring(20, 29);
String items = custom.substring(30);
String[] itemIds = items.split("@");
StringBuilder csv = new StringBuilder();
csv.append(id.trim());
csv.append(",").append(date.trim());
csv.append(",").append(customerId.trim());
for (String item : itemIds) {
csv.append(",").append(item.trim());
}
return csv.toString();
}
}
Extrae datos a
variables locales
Mapea a
formato CSV
Retorna payload
CSV
16. La primer diferencia notable entre los listados 3.1 y 3.2 es que en el
listado 3.2 no se usa ningún import de Camel. Esto significa que tu
bean es totalmente independiente de la API Camel. La siguiente
diferencia es que puedes nombrar la firma del método en el listado
3.2 –en este caso es un método estático llamado map.
La firma del método define el contrato, lo cual significa que el
primer parámetro, (String custom), es el body del mensaje que
vas a usar para la traducción. El método retorna una String, lo
cual significa que los datos traducidos serán de tipo String. En
ejecución, Camel vincula a esta firma del método.
Una ventaja más de usar beans sobre processors para mapeos es
que las pruebas unitarias son mucho más fáciles.
17. Transformando usando el método transform() del DSL Java
Transform() es un método en el DSL Java que puede ser usado en
routes Camel para transformar mensajes. Permitiendo el uso de
expresiones, transform() permite gran flexibilidad, y usando
expresiones directamente en el DSL a veces puede ahorrar tiempo.
Supongamos que necesitas preparar algún texto para formateo
HTML remplazando todos los saltos de línea con una tag <br/>.
Esto puede hacerse con una expresión incorporada de Camel que
busca y remplaza utilizando expresiones regulares.
from("direct:start")
.transform(body().regexReplaceAll("n", "<br/>"))
.to("mock:result");
18. Lo que este route hace es usar el método transform() para decirle a
camel que el mensaje debe ser transformado usando una expresión.
Camel provee lo que es conocido como el patrón Builder para
construir expresiones desde expresiones individuales. Este se hace
encadenando llamadas a métodos, lo cual es la esencia del patrón
Builder.
En este ejemplo combinas body() y regexReplaceAll(). La
expresión debe ser leída como sigue: toma el body y ejecuta una
expresión regular que remplace todas las nuevas líneas (n) con
tags <br/>. Ahora hemos combinado dos métodos que conforman
a una expresión Camel compuesta.
19. Camel te permite usar tus propias expresiones personalizadas. Esto
es útil cuando necesitas tener control completo y tener código Java
en la punta de tus dedos. Por ejemplo, el ejemplo anterior podría
haber sido implementado de la forma siguiente:
from("direct:start")
.transform(new Expression() {
public <T> T evaluate(Exchange exchange, Class<T> type) {
String body = exchange.getIn().getBody(String.class);
body = body.replaceAll("n", "<br/>");
body = "<body>" + body + "</body>";
return (T) body;
}
}).to("mock:result");
20. Transformando usando <transform> de Spring XML
Usar <transform> de Spring XML es un poco diferente que el DSL
Java debido a que el DSL XML no es tan poderoso. No están
disponibles las expresiones del patrón Builder debido a que XML
no tiene un lenguaje de programación real tras él. Lo que puedes
hacer tras ello es invocar un método en un bean o usar lenguajes de
scripting.
Veamos cómo trabaja esto. El método del siguiente bean se usa
dentro del route como la expresión:
public class HtmlBean {
public static String toHtml(String body) {
body = body.replaceAll("n", "<br/>");
body = "<body>" + body + "</body>";
return body;
}
}
21. El route es el siguiente:
<bean id="htmlBean" class="camelinaction.HtmlBean"/>
<camelContext id="camel"
xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="direct:start"/>
<transform>
<method bean="htmlBean" method="toHtml"/>
</transform>
<to uri="mock:result"/>
</route>
</camelContext>
Primero declaras un bean regular de Spring para ser usado para
transformar el mensaje. Luego en el route, usas <transform> con
una expresión llamada <method> para invocar el bean.
22.
23. Usando el patrón EIP Content Enricher
El EIP Content Enricher se ilustra en la figura 3.3. Este patrón
documenta el escenario donde un mensaje es enriquecido con
datos obtenidos de otro recurso.
Figura 3.3 En el EIP Content Enricher, un
mensaje existente tiene datos agregados
de otra fuente.
24. Para ayudar a comprender este patrón, regresaremos a Aupartes
Rider. Nos damos cuenta que el mapeo de datos que hicimos en el
listado 3.1 no era suficiente. Las órdenes también son acumuladas
en un servidor FTP, y tu job va de alguna manera a mezclar esta
información en el reporte existente. La figura 3.4 ilustra este
escenario.
En la figura 3.4, un consumidor calendarizado usando Quartz
inicia el route cada día a las 6:00 am 1. Luego obtiene datos de un
servidor FTP, el cual retorna órdenes en un formato personalizado
2, el cual luego es transformado en formato CSV 3. En este punto,
tienes que ejecutar el paso de enriquecimiento de contenido
adicional 4 con los datos obtenidos del servidor FTP 5. Después de
esto, el reporte final es escrito al servidor FTP 6.
25. Figura 3.4
Una vista general del route
que genera el reporte de
órdenes, ahora con el
enriquecedor de contenido
obteniendo datos de un
servidor FTP.
26. Antes de profundizarnos en el código y ver cómo implementar esto,
necesitamos ir un paso atrás y ver cómo el EIP Content Enricher es
implementado en Camel. Camel proporciona dos operaciones en el
DSL para implementar el patrón.
pollEnrich – Esta operación mezcla datos retomados de otra
fuente usando un consumidor.
enrich – Esta operación mezcla datos retomados de otra fuente
usando un productor.
Camel usa la interfaz org.apache.camel.processor.
AgregationStrategy para mezclar los resultados de la fuente con el
mensaje original, de la forma siguiente:
Exchange aggregate(Exchange oldExchange, Exchange newExchange);
27. Este método aggregate es una callback que debes implementar.
Este método tiene dos parámetros: el primero, llamado
oldExchange, contiene el exchange original; el segundo,
newExchange, es la fuente enriquecida. Tu tarea es enriquecer el
mensaje usando código Java y retornar el resultado mezclado. Esto
puede sonar un poco confuso, veámoslo en acción.
Para resolver el problema, necesitas usar pollEnrich debido a que
es capaz de votear por un archivo de un servidor FTP.
Enriqueciendo usando POLLENRICH.
El listado 3.3 muestra cómo puedes usar pollEnrich para retomar
las órdenes adicionales del servidor FTP remoto y agregar estos
datos con el mensaje existente usando AggregationStrategy de
Camel.
28. Listado 3.3 Usando pollEnrich para mezclar datos adicionales con un mensaje existente
from("quartz://report?cron=0+0+6+*+*+?")
.to("http://riders.com/orders/cmd=received")
.process(new OrderToCSVProcessor())
.pollEnrich("ftp://riders.com/orders/?username=rider&password=secret",
new AggregationStrategy() {
public Exchange aggregate(Exchange oldExchange, Exchange newExchange){
if (newExchange == null) {
return oldExchange;
}
String http = oldExchange.getIn().getBody(String.class);
String ftp = newExchange.getIn().getBody(String.class);
String body = http + "n" + ftp;
oldExchange.getIn().setBody(body);
return oldExchange;
}
})
.to("file://riders/orders");
1
2
Mezcla datos
usando
AggregationStrategy
3 Escribe la salida a archivo
Usa pollEnrich para leer un
archivo FTP
29. El route es disparado por Quartz para que corra todos los días a las
6 am. Invocas el servicio HTTP para retomar las órdenes y
transformarlas a formato CSV usando un processor.
En este punto, necesitas enriquecer los datos existentes con las
órdenes del servidor FTP remoto. Esto se hace usando pollEnrich 1,
lo cual consume el archivo remoto.
Para mezclar los datos, usas AggregationStrategy 2. Primero,
checas si algunos datos fueron o no consumidos. Si newExchange es
null, no hay archivo remoto para consumir, y sólo retornas los
datos existentes. Si hay un archivo remoto, mezclas los datos
concatenando los datos existentes con los nuevos datos y
asignándolos atrás en el oldExchange. Luego, retornas los datos
mezclados retornando el oldExchange. Para escribir el archivo de
reporte CSV, usas el componente file.
30. PollEnrich utiliza un consumidor de voteo para retomar mensajes,
y ofrece tres modos de timeout:
pollEnrich(timeout=-1)- Votea el mensaje y espera hasta que un
mensaje llegue. Este modo bloqueará hasta un mensaje exista.
pollEnrich(timeout=0)- Inmediatamente votea el mensaje si
alguno existe; de otra forma se retornará null. Nunca esperará
para que los mensajes lleguen, así que este modo nunca
bloqueará. Este es el modo default.
pollEnrich(timeout>0)- Votea el mensaje, si no existe mensaje,
esperará por uno, esperando a lo más hasta que el timeout se
dispare. Este modo potencialmente bloqueará.
Es una mejor práctica usar timeout=0 o asignar un valor de
timeout cuando se usa pollEnrich para evitar esperar
indefinidamente si no llegan mensajes.
31. Enriqueciendo usando ENRICH
Enrich es usado cuando necesitas enriquecer el mensaje actual con
datos de otra fuente usando mensajería request-response. Un primer
ejemplo sería enriquecer el mensaje actual con la respuesta de una
llamada a web service. Pero veremos otro ejemplo, usando Spring XML
para enriquecer el mensaje actual usando el transporte TCP:
<bean id="quoteStrategy“ class="camelinaction.QuoteStrategy"/>
<route>
<from uri="activemq:queue:quotes"/>
<enrich url="mina:tcp://riders.com:9876?textline=true&sync=true"
strategyRef="quoteStrategy"/>
<to uri="log:quotes"/>
</route>
Aquí usas el componente mina de Camel para transporte TCP,
configurado para usar mensajería request-response usando la opción
sync=true.
1
Bean implementando AggregationStrategy
32. Para mezclar el mensaje original con datos del servidor remoto,
<enrich> debe referir a una AggregationStrategy. Esto se hace
usando el atributo strategyRef. Como lo puedes ver en el ejemplo,
la quoteStrategy a la que está siendo referida es el id de un bean
1, el cual contiene la implementación real de la
AggregationStrategy, donde la mezcla toma lugar.
33. 3.3 Transformando XML
Camel proporciona dos formas de ejecutar transformaciones XML.
Componente XSLT –Para transformar un payload XML en otro
formato usando hojas de estilo XSLT.
Marshaling XML –Para serializar y deserializar objetos a y desde
XML.
Transformando XML con XSLT
XSL Transformations (XSLT) es un lenguaje XML declarativo usado
para transformar documentos XML en otros documentos. Por
ejemplo, XSLT puede ser usado para transformar XML en HTML
para páginas web o para transformar un documento XML en otro
documento XML con una estructura diferente. XSLT es poderoso y
versátil, pero también es un lenguaje complejo y toma tiempo y
esfuerzo comprenderlo y dominarlo. Hay que pensarlo dos veces.
34. Camel provee XSLT como un componente en camel-spring.jar
debido a que aprovecha la carga de recursos de Spring. Esto
significa mayor flexibilidad en la carga de hojas de estilo debido a
que Spring les permite ser cargadas de diferentes ubicaciones,
como lo es del classpath, rutas de archivos, y sobre HTTP.
Figura 3.5 Un Camel route usando un componente XSLT para transformar un
documento XML antes de que sea enviado a una queue JMS
35. Usar el componente XSLT es sencillo, debido a que es sólo otro
componente Camel. El siguiente route muestra un ejemplo de cómo
podrías usarlo; este route también es ilustrado en la figura 3.5.
from("file://rider/inbox")
.to("xslt://camelinaction/transform.xsl")
.to("activemq:queue:transformed")
El consumidor de archivos selecciona los nuevos archivos y los
enruta al componente XSLT, el cual transforma el payload usando la
hoja de estilos. Después de la transformación, el mensaje es
enrutado a un productor JMS. Nota en el código anterior como es
definida la URL para el componente XSLT:
xslt://camelinaction/transform.xsl. La parte posterior del esquema
es la ubicación URI de la hoja de estilo a usar. Por default Camel
buscará en el classpath.
36. El componente XSLT aprovecha Spring para cargar la hoja de
estilos. Puedes prefijar el nombre del recurso con cualquiera de los
tres prefijos listados en la tabla 3.2.
Prefijo Ejemplo Descripción
<ninguno> xslt://camelinaction/transform.xsl Si no se proporciona prefijo, camel
carga el recurso del classpath
classpath xslt://classpath:com/mycompany/tra
nsform.xsl
Carga el recurso del classpath
file xslt://file:/rider/config/transform.xsl Carga el recurso del sistema de
archivos local
http: xslt://http://rider.com/styles/transfor
m.xsl
Carga el recurso de una URL
37. Transformando XML con Marshalling de Objetos
Cualquier ingeniero de software quien ha trabajado con XML sabe
que es un reto usar la API XML de bajo nivel que ofrece Java. En
lugar de eso, la gente frecuentemente prefiere trabajar con objetos
Java regulares y usar marshalling para transformar entre objetos
Java y representaciones XML.
En Camel este proceso de marshalling es provisto en componentes
listos-para-usar conocidos como formatos de datos.
Transformando usando XStream
XStream es una simple librería para serializar objetos a XML y hacia
atrás. Para usarla, necesitas el camel-xstream.jar en el classpath y la
misma librería de XStream.
Supón que necesitas enviar mensajes en formato XML a una queue
JMS compartida, la cual luego es usada para integrar dos sistemas.
38. Transformando usando Marshalling de Objetos
Listado 3.4 Usando XStream para transformar un mensaje en XML
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<dataFormats>
<xstream id="myXstream"/>
</dataFormats>
<route>
<from uri="direct:foo"/>
<marshal ref="myXstream"/>
<to uri="activemq:queue:foo"/>
</route>
</camelContext>
Cuando usas el DSL XML, puedes declarar los formatos de datos
usados arriba 1 del <CamelContext>. Haciendo esto, puedes
compartir los formatos de datos en múltiples routes. En este primer
route, donde envías mensajes a una queue JMS, usas marshall 2, lo
cual se refiere al id de 1,, de forma que Camel sabe que el formato
de datos XStream está siendo usado.
Especifica formato de datos XStream
Transforma a XML
39. Transformando usando Marshalling de Objetos
También puedes usar directamente en el route, lo cual puede
acortar un poco la sintaxis, como esto:
<route>
<from uri="direct:foo"/>
<marshal> <xstream/> </marshall>
<to uri="activemq:queue:foo"/>
</route>
</camelContext>
El mismo route es un poco más acortador para escribir en el DSL
Java, debido a que puedes hacerlo con una línea por route:
from("direct:foo").marshal().xstream().to("uri:activemq:queue:foo");
Sí, usar Xstream es tan fácil. Y la operación inversa, unmarshalling
de XML a un objeto, es tan simple:
40. Transformando usando Marshalling de Objetos
<route>
<from uri="activemq:queue:foo"/>
<unmarshal ref="myXstream"/>
<to uri="direct:handleFoo"/>
</route>
Ya has visto cuan fácil es usar XStream con Camel.
41. Transformando usando JAXB
JAXB (Java Architecture for XML Binding) es una especificación
estándar para XML binding, y es provista lista en el runtime Java.
Como XStream, te permite serializar objetos a XML y hacia atrás
otra vez. No es tan simple, pero ofrece más opciones para controlar
la salida XML. Y debido a que es distribuido en Java, no necesitas
algunos archivos Jars especiales en el classpath.
A diferencia de XStream, JAXB requiere que hagas un poco de
trabajo para declarar el binding entre objetos Java y el formato
XML. Esto frecuentemente se hace usando anotaciones. Supón que
defines un modelo de bean para representar una orden, como se
muestra en el listado 3.5, y quieres transformar esto en XML antes
de enviarlo a una queue JMS. Luego quieres transformarlo atrás al
bean de orden otra vez, cuando consumes de la queue JMS. Esto
puede hacerse como se muestra en los listados 3.5 y 3.6.
42. Transformando usando JAXB
package com.acme.order;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class PurchaseOrder {
@XmlAttribute
private String name;
@XmlAttribute
private double price;
@XmlAttribute
private double amount;
}
1
PurchaseOrder en una clase JAXB anotada
Listado 3.5 Anotando un bean con JAXB de
forma que pueda ser transformado a y desde XML
43. Transformando usando JAXB
El listado 3.5 muestra como usar anotaciones JAXB para decorar tu objeto
modelo (omitiendo los getters y setters usuales). Primero define
@XmlRootElement 1 como una anotación a nivel de clase para indicar que
esta clase es un elemento XML. Luego defines el @XmlAccessorType
para permitir que JAXB accese los campos directamente. Para exponer los
campos de este objeto modelo como atributos XML, los marcas con la
anotación @XmlAttribute.
Usando JAXB, debes ser capaz de serializar un objeto modelo en una
representación XML como esta:
<purchaseOrder name="Camel in Action" price="4995" amount="1"/>
El listado 3.6 muestra como puedes usar JAXB en routes para transformar
el objeto PurchaseOrder a XML antes de que sea enviado a una queue
JMS, y luego atrás otra vez de XML al objeto PurchaseOrder cuando se
consume de la misma queue JMS.
44. Transformando usando JAXB
Listado 3.5 Usando JAXB para serializar objetos a y desde XML
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<dataFormats>
<jaxb id="jaxb" contextPath="camelinaction"/>
</dataFormats>
<route>
<from uri="direct:order"/>
<marshal ref="jaxb"/>
<to uri="activemq:queue:order"/>
</route>
<route>
<from uri="activemq:queue:order"/>
<unmarshal ref="jaxb"/>
<to uri="direct:doSomething"/>
</route>
</camelContext>
1
Declara formato de datos JAXB
2
Transforma de modelo a XML
3
Transforma de XML a modelo
45. Transformando usando JAXB
Primero necesitas declarar el formato de datos JAXB 1. Nota que también
se define un atributo contextPath en el formato de datos JAXB –este es un
nombre de package que instruye a JAXB que busque en este package
clases que son anotadas de JAXB.
El primer route luego serializa a XML 2 y el segundo route deserializa para
transformar el XML atrás al objeto PurchaseOrder 3.
Nota: Para decirle a JAXB qué clases son anotadas de JAXB, necesitas
arrastrar un archivo especial jaxb.index en la ruta del contexto. Es un
archivo de texto plano en el cual cada línea lista el nombre de la clase. En
el ejemplo anterior, el archivo contiene una sola línea con el texto
PurchaseOrder.
46. 3.4 Transformando con formatos de datos
En Camel, los formatos de datos son transformadores pluggables que
pueden transformar mensajes de un formato a otro y viceversa. Cada
formato de dato es representado en Camel como una interface
org.apache.camel.spi.DataFormat conteniendo dos métodos:
marshall – Para serializar un objeto en otro formato, tal como serializar
objetos Java a XML, CSV, EDI, HL7, u otros modelos de datos conocidos.
unmarshall – Para ejecutar la operación inversa, la cual transforma datos de
formatos bien conocidos atrás en un mensaje.
Figura 3.6 Un objeto es
serializado a una
representación binaria;
unmarshall puede utilizarse
para llevar el objeto atrás.
47. Ya pudiste haberte dado cuenta que estas dos funciones son
opuestas, significando que una es capaz de hacer lo inverso de lo
que la otra ha hecho, como lo ilustra la figura 3.6.
En esta sección cubriremos los formatos de datos en más
profundidad y usando otros tipos de datos diferentes a XML, como
es CSV y JSON. Aún también veremos cómo puedes crear tus
propios formatos de datos.
Comenzaremos nuestra jornada checando los formatos de datos
Camel provistos listos para usar.
Formatos de datos provistos con Camel
Camel provee formatos de datos para un rango de modelos de datos
bien conocidos, como se lista en la tabla 3.3.
48. Tabla 3.3 Formatos de datos provistos listos con Camel
Formato
de dato
Modelo de
dato
Artefacto Descripción
Bindy CSV, FIX,
longitud fija
camel-bindy Vincula varios modelos de datos para modelar objetos usando
anotaciones
Castor XML camel-castor Usa Castor para vincular a y desde objetos Java
Crypto Cualquiera camel-crypto Encripta y desencripta datos usando la Java Cryotography
Extension
CSV CSV camel-csv Transforma a y desde CSV usando la librería Apache Commons
CSV
Flatpack CSV camel-flatpack Transforma a y desde CSV usando la librería FlatPack
GZip Cualquiera camel-gzip Comprime y descomprime archivos (compatible con las populares
herramientas gzip/gunzip)
HL7 HL7 camel-hl7 Transforma a y desde HL7, el cual es un formato de datos bien
conocido en la industria del cuidado de la salud
JAXB XML camel-jaxb Usa el estándar JAXB 2.x para vincular XML a y desde objetos Java
Jackson JSON camel-jackson Transforma a y desde JSON usando la ultrarrápida librería Jackson
49. Tabla 3.3 Formatos de datos provistos listos con Camel (cont)
Formato de
dato
Modelo de
dato
Artefacto Descripción
Protobuf XML camel-
protobuf
Transforma a y desde XML usando la librería Google Protocol
Buffers
SOAP XML camel-soap Transforma a y desde SOAP
Serialization Objeto camel-core Usa serialización de objetos para transformar objetos a y desde
un stream serializado
TidyMarkup HTML camel-tagsoup
xmalBeans XML camel-
xmlsbeans
Usa XmlBeans para vincular XML a y desde objetos Java
XMLSecurity XML camel-
xmlsecurity
Facilita la encripción y desencripción de documentos XML
XStream XML camel-xstream Usa XStream para vinculación XML a y desde objetos Java
XStream JSON camel-xstream Transforma a y desde JSON usando la librería XStream
Zip Cualquiera camel-core Usa el estándar JAXB 2.x para vincular XML a y desde objetos
Java
50. Como puedes ver, Camel proporciona 18 formatos de datos listos
para usar. Aquí estaremos viendo 3 de ellos. Ellos son los formatos
más comúnmente usados, y lo que aprendas sobre estos aplicarán
también para los restantes formatos de datos.
Usando el formato de datos CSV de Camel
El formato de datos camel-csv es capaz de transformar a y desde
formato CSV. Aprovecha Apache Commons CSV para hacer el
trabajo real.
Supón que necesitas consumir archivos CSV, dividir cada fila, y
enviarla a una queue JMS. Suena difícil de hacer, pero es posible
con poco esfuerzo en un route Camel:
51. from.(“file://rider/csvfiles”)
.unmarshall().csv()
.split(body()).to(“activemq:queue.csv.record”);
Todo lo que tienes que hacer es unmarshall los archivos CSV, lo
cual leerá el archivo línea por línea y almacenará todas las líneas en
el cuerpo del mensaje como un tipo java.util.List<List>. Luego
usas el splitter para dividir el cuerpo, lo cual fraccionará la
java.util.List<List<String>> en filas (cada fila representada
como otra List<String> conteniendo los campos) y enviar la fila a
la queue JMS. Podrías no querer enviar cada fila como un tipo List
a la queue JMS, de forma que puedes transformar la fila antes de
enviarla, quizás usando un processor.
El mismo ejemplo en Spring XML es un poco diferente, como se
muestra aquí:
52. <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="file://rider/csvfiles"/>
<unmarshal><csv/></unmarshal>
<split>
<simple>body</simple>
<to uri="activemq:queue.csv.record"/>
</split>
</route>
</camelContext>
La notable diferencia es como le dices a <split> que debe dividir el cuerpo
del mensaje. Para hacer esto necesitas proveerle a <split> con una
Expression, qué es lo que el splitter debe iterar cuando ejecute el splitting.
Para hacerlo así, puedes usar el lenguaje de expresión incorporado de Camel
llamado Simple, el cual sabe cómo hacer eso.
53. A la primera, los tipos de datos que el formato de datos usa pueden
parecer un poco confusos. Estos son listados en la tabla 3.4.
Un problema con camel-csv es que usa tipos de datos genéricos, como
Maps o Lists, para representar registros CSV. Frecuentemente ya tienes
modelos de datos para representar tus datos en memoria.
Operación De tipo A tipo Descripción
marshall Map<String,
Object>
OutputStream Contiene una sola fila en formato CSV
marshall List<Map<String,
Object>>
OutputStream Contiene múltiples filas en formato
CSV donde cada fila está separada por
n (nueva línea)
unmarshall InputStream List<List<String>> Contiene una List de filas donde
cada fila es otra List de campos
54. Usando el formato de datos Bidy de Camel
Los dos formatos de datos existentes relacionados a CSV (camel-csv
y camel-flatpack) son librerías más viejas que no toman ventaja de
las nuevas características de Java 1.5, tales como anotaciones y
generics. A la luz de esta deficiencia, Charles Moulliard salió y
escribió el componente camel-bindy para tomar ventaja de estas
nuevas posibilidades. Es capaz de vincular CSV, FIX, y formatos de
datos de longitud fija a modelos de objetos existentes usando
anotaciones. Esto es similar a lo que hace JAXB para XML.
Supón que tienes un objeto modelo que representa una orden de
compra. Anotando el objeto modelo con anotaciones camel-bindy,
puedes fácilmente transformar mensajes entre CSV y objetos
modelo Java.
55. package camelinaction.bindy;
import java.math.BigDecimal;
import org.apache.camel.dataformat.bindy.annotation.CsvRecord;
import org.apache.camel.dataformat.bindy.annotation.DataField;
@CsvRecord(separator = ",", crlf = "UNIX")
public class PurchaseOrder {
@DataField(pos = 1)
private String name;
@DataField(pos = 2, precision = 2)
private BigDecimal price;
@DataField(pos = 3)
private int amount;
}
Mapea a registro CSV
1
2 Mapea a columna en
registro registro CSV
56. Primero marcas la clase con la anotación @CsvRecord 1 para indicar
que representa un registro en formato CSV. Entonces anotas los
campos con @DateField acorde al layout del registro CSV 2.
Usando el atributo pos, puedes dictar la orden en la cual son
sacados en formato CSV; pos inicia con un valor de 1. Para campos
numéricos, puedes adicionalmente declarar precision, la cual en
este ejemplo es asignada a 2, indicando que el precio debe usar dos
dígitos por centavo. Bindy tiene atributos para layout de grano fino
de los campos, tales como pattern, trim y length. Puedes usar
pattern para indicar el patrón de datos, trim para recortar la
entrada, y length para restringir la descripción de un texto a un
cierto número de caracteres.
Antes de ver cómo usar Bindy en routes Camel, necesitamos ir un
paso atrás y checar los tipos de datos que Bindy espera usar. Ellos
son listados en la tabla 3.5.
57. Tabla 3.5 Tipos de datos que Bindy usa cuando transforma a y desde formato CSV
Operación De tipo A tipo Descripción de la salida
marshall List<Map<String,
Object>>
OutputStream Contiene múltiples filas en formato CSV donde
cada fila es separada por una n (nueva línea)
unmarshall InputStream List<Map<String,
Object>>
Contiene una List de filas donde cada fila
contiene 1..n modelos de datos contenidos en un
Map
La cosa importante a notar en la tabla 3.5 es que Bindy usa un
Map<String, Object> para representar una fila CSV. A la primera puede
parecer extraño. ¿Por qué no sólo usa un único objeto modelo para eso?
La respuesta es que puedes tener múltiples objetos modelo con el registro
CSV que está siendo dispersado a través de estos objetos. Por ejemplo,
podrías tener los campos 1 a 3 en un objeto modelo, los campos 4 a 9 en
otro, y los campos 10 al 12 en un tercero.
58. La entrada del map <String, Object> es destilada como sigue:
Map key (String) –Debe contener el nombre de la clase
completamente cualificado del objeto modelo.
Map value (Object) –Debe contener el objeto modelo.
Si te parece ser un poco confuso, no te preocupes. El siguiente ejemplo
debe hacerlo más claro:
Listado 3.8 Usando Bindy para transformar un objeto modelo a formato CSV
public class PurchaseOrderBindyTest extends TestCase {
public void testBindy() throws Exception {
CamelContext context = new DefaultCamelContext();
context.addRoutes(createRoute());
context.start();
MockEndpoint mock = context.getEndpoint("mock:result",
MockEndpoint.class);
59. mock.expectedBodiesReceived("Camel in Action,49.95,1n");
PurchaseOrder order = new PurchaseOrder();
order.setAmount(1);
order.setPrice(new BigDecimal("49.95"));
order.setName("Camel in Action");
ProducerTemplate template = context.createProducerTemplate();
template.sendBody("direct:toCsv", order);
mock.assertIsSatisfied();
}
public RouteBuilder createRoute() {
return new RouteBuilder() {
public void configure() throws Exception {
from("direct:toCsv")
.marshal().bindy(BindyType.Csv, "camelinaction.bindy")
.to("mock:result");
}
};
}
}
Crea el objeto modelo
como es usual1
Inicia el test
2
Transforma
el objeto
modelo a
CSV
60. En este listado, primero creas y llenas el modelo de orden usando
setters Java regulares 1. Luego envías el modelo de orden al route
enviándolo al endpoint direct:toCsv 2 que es usado en el route. El
route luego serializará el modelo de orden a CSV usando Bindy 3.
Nota cómo Bindy es configurado para usar modo CSV vía
BindyType.Csv. Para permitirle a Bindy saber cómo mapear a objeto
modelo orden, necesitas proporcionar un nombre de package que
será escaneado para clases anotadas con anotaciones Bindy. Esta es
la misma solución que ofrece JAXB.
Nota: El listado 3.8 usa MockEndpoint para probar fácilmente que
el registro CSV está como se espera.
61. Usando el formato de datos JSON de Camel
JSON (JavaScript Jason Notation) es un formato de intercambio de datos,
y Camel proporciona dos componentes que soportan el formato de datos
JSON: camel-xstream y camel-jackson. En esta sección nos enfocaremos
en camel-jackson debido a que Jackson es una librería JSON muy popular.
Yendo atrás a Autopartes Rider, ahora tienes que implementar un nuevo
servicio que retorna resúmenes de órdenes entregadas en formato JSON.
Hacer esto con Camel es muy fácil, debido a que Camel tiene todos los
ingredientes necesarios fabricar este servicio. El listado 3.9 muestra cómo
puedes subir a un prototipo.
62. <bean id="orderService" class="camelinaction.OrderServiceBean"/>
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<dataFormats>
<json id="json" library="Jackson"/>
</dataFormats>
<route>
<from uri="jetty://http://0.0.0.0:8080/order"/>
<bean ref="orderService" method="lookup"/>
<marshal ref="json"/>
</route>
</camelContext>
Primero necesitas montar el formato de datos JSON y especificar que la
librería Jackson debe ser usada 1. Luego defines un route que expone el
servicio HTTP usando el endpoint Jetty. Este ejemplo expone el endpoint Jetty
directamente en la URL. Usando http://0.0.0.0:8080/order , le dices a Jetty
que cualquier cliente puede alcanzar este servicio en el puerto 8080.
Monta un formato de datos JSON
1
2
Invoca bean para retomar datos
para respuesta
63. Siempre que una request golpee este servicio HTTP, es enrutado al bean
orderService 2 y el método lookup es invocado en ese bean. El resultado
de la invocación a este bean es luego serializado a formato JSON y
retornado atrás al cliente HTTP.
El bean order service podría tener una firma de método como esta:
public PurchaseOrder lookup(@Header(name = "id") String id)
Esta firma te permite implementar la lógica de lookup como desees.
Nota que el bean del servicio puede retornar un POJO que la librería
JSON es capaz de serializar. Por ejemplo, supón que usaste el
PurchaseOrder del listado 3.7, y tuviste salida JSON como la siguente:
{"name":"Camel in Action","amount":1.0,"price":49.95}
El mismo servicio HTTP puede ser invocado por una request HTTP Get
con el id de la orden como parámetro:
http://0.0.0.0:8080/order/service?id=123.
64. Configurando formatos de datos de Camel
En secciones anteriores utilizaste el formato de datos CSV, pero este
formato de datos ofrece muchas configuraciones adicionales. Este listado
muestra como puedes configurar el formato de datos CSV.
public void configure() {
CSVConfig custom = new CSVConfig();
custom.setDelimiter(';');
custom.setEndTrimmed(true);
custom.addField(new CSVField("id"));
custom.addField(new CSVField("customerId"));
custom.addField(new CSVField("date"));
custom.addField(new CSVField("item"));
custom.addField(new CSVField("amount"));
custom.addField(new CSVField("description"));
CsvDataFormat myCsv = new CsvDataFormat();
myCsv.setConfig(custom);
myCsv.setAutogenColumns(false);
from("direct:toCsv").marshal(myCsv).to("file://acme/outbox/csv");
}
Configura formato de datos
CSV personalizado
1
Crea el formato de datos
CSV personalizado
2
3
Usa el formato de datos CSV
65. Configurar formatos de datos en Camel se hace usando código Java
regular; usas la API que proporciona el formato de datos. En el
listado 3.10, el formato de datos CSV ofrece un objeto CSVConfig 1
que es usado para asignar el punto y coma como delimitador y para
especificar el orden de los campos. El mismo formato de datos es
luego creado 2 y asignado para usar la configuración. El uso del
formato de datos sigue siendo el mismo, así que todo lo que
necesitas hacer es referirte a él desde los métodos marshall y
unmarshall. Este mismo principio aplica a todos los formatos de
datos en Camel. Puedes configurarlos usando las APIs que ellos
proveen.
Ahora que sabes como usar los formatos de datos, vamos a ver
como puedes escribir tus propios formatos de datos.
66. Escribiendo tu propio formato de datos
Pudieras encontrarte necesario transformar datos a y desde un formato de
datos personalizado. En esta sección, veremos cómo puedes desarrollar
un formato de datos que puede reversar strings.
Desarrollar tu propio formato de datos es muy fácil, debido a que Camel
provee una sola API que debes implementar:
org.apache.camel.spi.DataFormat. Vamos a ver cómo podrías
implementar un formato de dato de reversa de string.
El formato de datos personalizado debe implementar la interface
DataFormat, la cual te forza a desarrollar dos métodos: marshall 1 y
unmarshall 2. Eso no es sorpresa, ya que ellos son los mismos métodos
que usas en el route. El método marshall 1 necesita sacar el resultado al
OutputStream. Para hacer esto necesitas obtener el payload del mensaje
como un byte[], y luego reversarlo con un método helper. Luego escribes
los datos al OutputStream.
67. Nota que utilizas os convertidores de tipo de Camel para retornar el
payload del mensaje como un byte[]. Esto es muy poderoso y te ahorra de
hacer un typecast manual en Java o intentar convertir el payload por ti
mismo.
El método unmarshall es cercanamente el mismo. Usas el mecanismo
convertidor de tipo de Camel otra vez para proporcionar el payload del
mensaje como un byte[]. unmarshall también reversa los bytes para
obtener los datos atrás en su orden original. Nota que en este método
retornas los datos en vez de escribirlos a un stream.
TIP: Como mejor práctica usa los convertidores de tipo Camel en vez del
typecasting o convertirlos entre tipos por ti mismo.
Para usar este nuevo formato de datos en un route, todo lo que tienes que
hacer es definirlo como un bean de Spring y referirte a él desde
<marshall> y <unmarshall> de la forma siguiente:
69. 3.5 Transformando con templates
Camel proporciona una hábil integración con dos diferentes lenguajes de
plantillas:
Apache Velocity-Probablemente el lenguaje de plantillas más conocido
FreeMarker –Otro lenguaje de plantillas popular que puede ser un poco
más avanzado que Velocity.
Los dos lenguajes de plantilla son muy similares de usar, así que con
Velocity nos basta.
Usando Apache Velocity
Autopartes Rider ha implementado un nuevo sistema de órdenes que
debe enviar una respuesta de email cuando un cliente ha emitido una
orden. Tu trabajo es implementar esta característica.
70. El email de respuesta podría parecerse a este:
Querido cliente
Gracias por ordenar X pieza(s) de XXX a un costo de XXX.
Este es un email automatizado, por favor no responda.
Hay tres piezas de información en el email que deben ser remplazadas en
tiempo de ejecución con valores reales. Lo que necesitas hacer es ajustar
el email para usar el lenguaje de plantillas Velocity, y luego colocarlas en
el repositorio de fuentes como /src/test/resources/email.vm:
Querido cliente
Gracias por ordenar ${body.amount} de ${body.name} a un
costo de ${body.price}.
Este es un email automatizado, por favor no responda.
71. Nota que hemos insertado placeholders ${} en la plantilla, lo cual instruye
a Velocity que las evalúe y las remplace en tiempo de ejecución. Camel
prellena el contexto de Velocity con un número de entidades que luego
están disponibles a Velocity. Estas entidades son listadas en la tabla 3.6.
NOTA: Las entidades en la tabla 3.6 también aplican para otros lenguajes de
plantilla como FreeMarker.
72. Entidad Tipo Descripción
camelContext org.apache.camel.CamelContext El Contexto de Camel
exchange org.apache.camel.Exchange El exchange actual
in org.apache.camel.Message El mensaje de entrada. Este puede chocar con
alguna palabra reservada en algunos lenguajes. En
su lugar usa request.
request org.apache.camel.Message El mensaje de entrada
body java.lang.Object El cuerpo del mensaje de entrada
headers java.util.Map Los headers del mensaje de entrada
response org.apache.camel.Message El mensaje de salida
out org.apache.camel.Message El mensaje de salida. Este puede chocar con alguna
palabra reservada en algunos lenguajes. En su lugar
usa response.
73. 3.6 A cerca de los convertidores de tipo de Camel
Camel proporciona un sistema conversor de tipo incorporado que
convierte automáticamente entre tipos bien conocidos. Este sistema le
permite a los componentes de Camel trabajar juntos fácilmente sin tener
diferencias de tipos. Y desde la perspectiva del usuario Camel, las
conversiones de tipo están incorporadas en la API en muchos lugares sin
ser invasivas. Por ejemplo, has usado en el listado 3.1:
String custom = exchange.getIn().getBody(String.class);
Al método getBody le es pasado el tipo que quieres que se le retorne. Tras
bambalinas, el sistema conversor de tipo convierte el tipo retornado a un
String si es necesario.
Aquí se explicará cómo Camel escanea el classpath al arranque para
registrar dinámicamente los convertidores de tipo. También mostraremos
cómo puedes usarlo desde un route Camel, y cómo construir tus propios
convertidores de tipo.
74. Cómo funciona el mecanismo convertidor de tipo de Camel
Para comprender el sistema conversor de tipo, primero necesitamos saber
qué es un convertidor de tipo en Camel. La figura 3.7 ilustra la relación
entre el TypeConverterRegistry y los TypeConverters que contiene.
El TypeConverteRegistry es donde todos los conversores de tipos son
registrados cuando Camel inicia. En tiempo de ejecución, Camel usa el
método lookup de TypeConverterRegistry para buscar un
TypeConverter adecuado.
TypeConverter lookup(Class<?> toType, Class<?> fromType);
Figura 3.7
El TypeConverterRegistry contiene
muchos TypeConverter
75. Usando el TypeConverter, Camel entonces puede convertir un tipo a
otro usando el método convertTo de TpeConverter, el cual está
definido de la siguiente forma:
<T> T convertTo(Class<T> type, Object value);
Nota: Camel implementa alrededor de 150 o más conversores de tipo listos
para usar, los cuales son capaces de convertir a y desde los tipos más
comúnmente usados.
CARGA DE CONVERTIDORES DE TIPO EN EL REGSITRO
Al arranque, Camel carga todos los convertidores de tipo en el
TypeConverterRegistry usando una solución de escaneo del classpath.
Esto le permite a Camel elegir no sólo un conversor de tipo de camel-core
sino también desde cualesquier a otros componentes de Camel.
Para escanear y cargar los conversores de tipo, Camel usa
org.apache.camel.impl.converter.AnnotationTypeConverterLoader.
76. Para evitar cargar silos de clases, lee un archivo de descubrimiento de
servicio en la carpeta META-INF: META-
INF/services/org/apache/camel/TypeConverter. Este es un archivo de
texto plano que tiene una lista de paquetes que contiene conversores de
tipo Camel. El archivo especial es necesario para evitar escanear cada JAR
posible y todos sus packeges, lo cual sería consumidor de tiempo. El
archivo especial le dice a Camel si el archivo JAR contiene o no
conversores de tipo. Por ejemplo, el archivo en camel-core contiene las
siguientes tres entradas:
org.apache.camel.converter
org.apache.camel.component.bean
org.apache.camel.component.file
La AnnotationTypeConverterLoader escaneará estos packages y sus
subpackages para clases que han sido anotadas con @Converter, y busca
en ellas métodos públicos que son anotados con @Converter. Cada uno de
estos métodos es considerado un conversor de tipo.
77. Esto es mejor ilustrado con un ejemplo. El siguiente código es un snippet de la
clase IOConverter del JAR camel-core:
@Converter
public final class IOConverter {
@Converter
public static InputStream toInputStream(URL url) throws IOException {
return url.openStream();
}
}
Camel irá sobre cada método anotado con @Converter y buscará la firma del
método. El primer parámetro es del tipo from, y el tipo de retorno es del tipo to
–en este ejemplo tienes un TypeConverter que puede convertir desde una URL
a un InputStream. Haciendo esto, Camel carga todos los conversores de tipo
incorporados, incluyendo aquellos de los componentes Camel en uso.
78. Usando convertidores de tipo Camel
Podrías querer usar los convertidores de tipo para forzar un tipo específico
para que sea usado en un route, como antes enviando datos atrás a un
llamante o a un destino JMS. Vamos a ver cómo hacer eso:
Supón que necesitas enrutar archivos a una queue JMS usando
java.jms.TextMessage. Para hacer lo así, puedes convertir cada archivo a
una String, lo cual forza al componente JMS a usar TextMessage. Esto es
fácil de hacer en Camel –puedes usar el método convertBodyTo, como es
mostrado aquí:
from("file://riders/inbox")
.convertBodyTo(String.class)
.to("activemq:queue:inbox");
79. Si estás usando Spring XML. en vez de ello proporcionas el tipo como un
atributo, como lo siguiente:
<route>
<from uri="file://riders/inbox"/>
<convertBodyTo type="java.lang.String"/>
<to uri="activemq:queue:inbox"/>
</route>
Puedes omitir el prefijo java.lang en el tipo, lo cual puede acortar la
sintaxis un poco:
<convertBodyTo type="java.lang.String"/>
Otra razón para usar convertBodyTo es leer archivos usando un
encoding fijo como UTF-8. Esto se hace pasando el encoding el segundo
parámetro de entrada:
from("file://riders/inbox")
.convertBodyTo(String.class, "UTF-8")
.to("activemq:queue:inbox");
80. Escribiendo tu propio convertidor de tipo
Escribir tu propio convertidor de tipo es fácil en Camel. Ya has visto como un
conversor de tipo se ve en la sección 3.6.1, cuando viste cómo funcionan los
conversores de tipo.
Supón que necesitaras escribir un conversor de tipo personalizado que puede
convertir un byte en un objeto modelo PurchaseOrder (un objeto que usaste
en el listado 3.7). Como lo viste anteriormente, necesitas crear una clase
@Converter conteniendo el método conversor de tipo:
Listado 3.12 Un conversor de tipo personalizado para convertir de tipo byte[] a
PurchaseOrden
@Converter
public final class PurchaseOrderConverter
@Converter
public static PurchaseOrder toPurchaseOrder(byte[] data,
Exchange exchange) {
TypeConverter converter = exchange.getContext()
.getTypeConverter();
Coge el TypeConverter a
reutilizar
1
81. String s = converter.convertTo(String.class, data);
if (s == null || s.length() < 30) {
throw new IllegalArgumentException("data is invalid");
}
s = s.replaceAll("##START##", "");
s = s.replaceAll("##END##", "");
String name = s.substring(0, 9).trim();
String s2 = s.substring(10, 19).trim();
BigDecimal price = new BigDecimal(s2);
price.setScale(2);
String s3 = s.substring(20).trim();
Integer amount = converter.convertTo(Integer.class, s3);
return new PurchaseOrder(name, price, amount);
}
}
Convierte de String
a PurchaseOrden
2
82. En el listado 3.12 el Exchange te da acceso al CamelContext y así al padre
TypeConverter 1, el cual usas en este método para convertir entre
cadenas y números. El resto del código. El resto del código es la lógica
para parsear el protocolo personalizado y retornar el PurchaseOrder 2.
Nota como puedes usar el converter para convertir fácilmente entre
tipos bien conocidos.
Todo lo que necesitas hacer es agregar el archivo de descubrimiento de
servicio, llamado TypeConverter, en el directorio META-INF. Como se
explicó anteriormente, este archivo contiene una línea identificando cada
package para ser escaneado por las clases @Converter.