Apache Camel - Transforming Data

Apache Camel - Transforming Data


集成工具是属于构造企业服务总线 ESB 的基础,势必会处理不同组件不同格式的消息,那么数据转换也就是继承工具包括 Camel 的关键特性。

数据转换(Data Transformation)可以分为两种类型:

  • 数据格式转换 Data format transformation - 消息体的数据格式从一种转换成另外一种,例如CSV格式转换成XML格式
  • 数据类型转换 Data type transformation - 消息体的数据类型从一种转换成另外一种,例如 java.lang.String 转换成 javax.jms.TextMessage

Camel 对数据格式和数据类型的转换有几种方式:

  • Data formats - XML, JSON ...
  • Expression - Languages
  • Java - Processor, Bean, Content Enricher
  • Templates - XSLT, Apache Velocity ...

Data formats

Data formats 在 Camel 里以可插拔的转换器形式存在。每个 data format 实现了接口 org.apache.camel.spi.DataFormat 并包含两个方法:

  • marshal - 把一个 Message 编译成常见的数据格式,例如编译java对象成 XML, CSV, Json 等
  • unmarshal - 反编译,把常见数据格式转换成 Message

Data formats 可以用在 Java DSL 和 XML DSL 中:

  • 用在 Java DSL 中
from("file://rider/csvfiles")
 .unmarshal().csv()
 .split(body()).to("activemq:queue.csv.record");
  • 用在 XML DSL 中
<route>
 <from uri="file://rider/csvfiles"/>
 <unmarshal><csv/>unmarshal>
 <split>
   <simple>bodysimple>
   <to uri="activemq:queue.csv.record"/>
 split>
route>

Camel 支持的 Data formats 完整列表参见 Apache Camel > Documentation > Architecture > Data formats.

XML Json

企业集成开发中常用的两种数据格式 XML 和 Json,经常需要相互转换。camel-xmljson组件是用来做 XML 和 Json 之间转换的。

  • marshalling => converting from XML to JSON
  • un-marshaling => converting from JSON to XML.

添加此组件依赖包:

<dependency>
  <groupId>org.apache.camelgroupId>
  <artifactId>camel-xmljsonartifactId>
  <version>x.x.xversion>
  
dependency>


<dependency>
  <groupId>xomgroupId>
  <artifactId>xomartifactId>
  <version>1.2.5version>
dependency>

在 Java DSL 中使用此 Data format:

// From XML to JSON - inline dataformat
from("direct:marshalInline")
  .marshal().xmljson()
  .to("mock:jsonInline");

// From JSON to XML - inline dataformat
from("direct:unmarshalInline")
  .unmarshal().xmljson()
  .to("mock:xmlInline");

更多设置参见Apache Camel > Documentation > Architecture > Data Format > XmlJson

Expression

Expressions and Predicates (expressions that evaluate to true or false) can then be used to create the various Enterprise Integration Patterns in the DSL or Xml Configuration like the Recipient List.

To support dynamic rules Camel supports pluggable Expression strategies using a variety of different Languages.

表达式 Expression 是使用不同的语言 Languages 书写,常用的语言例如:Simple Language, XPath, Scripting Languages, Constant 等等。

例如 Simple Language 书写的表达式: Hello ${body} ,也可以用作断言如 ${body} == 'Camel', ${header.zip} between '30000..39999'

通常表达式会作为获取 Message Body Header Properties 值的方式,断言会作为 Route 判断的依据。

Transform

Transform 是 Camel route DSL 中的一个方法,它接受一个 Expression 作为参数,执行表达式的结果作为转换后的 Message Body 。Transform 可以用在 Java DSL 和 XML DSL 中,例如在 Java DSL 中使用 Transform 与 Simple language:

from("direct:start")
  .transform().simple("Hello ${body}!")
    .to("mock:result");

如果是使用 JavaScript 语言的表达式:

from("direct:start")
  .transform().javaScript("'Hello ' + request.body + '!'")
    .to("mock:result");

Java

如果 Camel 提供的 Data formats 和 Expression 不能满足你所需要的逻辑书写的话,你可以还需要写一些 Java 逻辑。
可以编写 Java 逻辑的方式有几种:

  • Processor
  • Beans
  • Content Enricher

Processor

The Camel Processor is an interface defined in org.apache.camel.Processor with a
single method:
public void process(Exchange exchange) throws Exception;

它可以任意处理传入的 Exchange 对象。

from("direct:start")
 .process(new Processor() {
  public void process(Exchange exchange) throws Exception {
    exchange.getIn().setBody("Hello " + exchange.getIn().getBody() + "!");
  }
 })
 .to("mock:result");

Beans

Camel 使用了类似于 Spring Framework POJO 的轻量级 Bean 管理容器。Camel 的 Bean 管理注册表(registry)是一个 Service Provider Interface (SPI),它实现了接口 org.apache.camel.spi.Registry。常见的实现有:

  • SimpleRegistry - A simple implementation to be used when unit testing or running Camel in the Google App engine, where only a limited number of JDK classes are available.
  • JndiRegistry - An implementation that uses an existing Java Naming and Directory Interface (JNDI) registry to look up beans.
  • ApplicationContextRegistry - An implementation that works with Spring to look up beans in the Spring ApplicationContext. This implementation is automatically used when you’re using Camel in a Spring environment.
  • OsgiServiceRegistry - An implementation capable of looking up beans in the OSGi service reference registry. This implementation is automatically used when using Camel in an OSGi environment.

在 Java DSL 中使用 Bean,指定 Bean 的标识和想要调用的方法名称:

public void configure() throws Exception {
  from("direct:hello").beanRef("helloBean", "hello");
}

也可以只指定 Bean 的 Class,Camel 会在 startup 时初始化 Bean,并在调用时根据 Message Body data type 去匹配方法的参数类型并决定实际调用哪个方法:

public void configure() throws Exception {
  from("direct:hello").bean(HelloBean.class);
}

Camel 调用 Bean 时还支持很多 Camel annotations

public String orderStatus(@Body Integer orderId, @Headers Map headers) {
  Integer customerId = (Integer) headers.get("customerId");
  String customerType = (String) headers.get("customerType");
  ...
}

和 Camel language annotations

public Document handleIncomingOrder( @Body Document xml,
  @XPath("/order/@customerId") int customerId,
  @Bean(ref = "guid", method="generate") int orderId );

Content Enricher

Content Enricher 模式是一种 Message Transformation 模式。

Image: DataEnricher

它分为 Poll enrich 和 Enrich 两种方式。

Pollenrich

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");

Enrich

Templates

对于更高级的数据转换我们还可以使用 Template 技术,如 XSLT, Apache Velocity, FreeMarker 等。

XSLT

XSLT (Extensible Stylesheet Language Transformations) ) is a language for transforming XML documents into other XML documents or other formats such as HTML for web pages, plain text or XSL Formatting Objects, which may subsequently be converted to other formats, such as PDF, PostScript and PNG.[2] XSLT 1.0 is widely supported in modern web browsers.

XSLT 是一个 Camel 组件,所以使用其 endpoint uri:

from("file://rider/inbox")
 .to("xslt://camelinaction/transform.xsl")
 .to("activemq:queue:transformed")

Velocity

Apache Velocity is a Java-based template engine that provides a template language to reference objects defined in Java code. It aims to ensure clean separation between the presentation tier and business tiers in a Web application (the model–view–controller design pattern).

Camel Type Converters

Camel 有一套数据类型转换系统,当 Camel 遇到 From Type 和 To Type 不同时,会去 Type Converters 注册系统查找相应的 Converter ,如果存在相应的 Converter 则使用其转换数据类型,否则会报出异常。

自定义的 Converter 如下:

@Converter
public final class PurchaseOrderConverter {

    @Converter
    public static PurchaseOrder toPurchaseOrder(byte[] data, Exchange exchange) {
        TypeConverter converter = exchange.getContext().getTypeConverter();
        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);
    }

Camel中的重要概念

Endpoint:即节点,即消息经过的地方 Camel支持的EndPoint 1.JMS队列 2.web service 3.文件 4.FTP服务 5.email地址 6.POJO 在基于Camel的应用中,你会创建一些EndPoint然后用路由将这些EndPoint连接起来。Camel定义了一个名这EndPoint的接口,每一个Camel支持的EndPoint都有一个类实现了该EndPoint接口,Camel提供了单独的Javadoc继承树为Camel支持的每一种通信技术。 CamelContext 一个CamelContext对象代表了Camel的运行时系统,典型的你会有一个CamelContext对象在一个应用中,一个典型的应用遵循下面的步骤: 1.创建一个CamelContext对象 2.添加Endpoints和可能的组件到该CamelContext对象中 3.添加路由到该CamelContext对象中去连接这些EndPoint 4.调用CamelContext的start()方法,这样会启动Camel内部线程,这些线程用于处理EndPoint中消息的发送,接收与处理 5.调用CamelContext的stop()方法,这样将会停止所有Camel内部线程 注意:CamelContext.start()方法,并不会无限制阻塞下去,而是该方法会为每一个组件与Endpoint开始内部线程然后返回。同理,CamelContext.stop()方法会等待每一个组件与EndPoint的所有内部线程都终止后该方法才会返回。 如果在你的应用中没有调用CamelContext.start()方法,消息将不会被处理因为内部线程还没有被创建。如果在停止你的应用之前你没有调用CamelContext.stop()方法,应用可能会以一种不一致的状态停止。如果在JUnit测试中没调用CamelContext.stop()方法,测试可能会因为消息没有机会完全被处理而失败。 CamelTemplate Camel以前有一个叫CamelClient的类,但是现在已经更名为CamelTemplate了,这样做的原因是迎合其它开源项目的命名约定,如Spring中的TransactionTemplate和JmsTemplate。 CamelTemplate类是一个包装了CamelContext的精简类,它有发送消息或Exchange到EndPoint的方法。这样就提供了一个输入消息到源Endpoint的途径,所以消息会沿着路由移动直到目的Endpoint。 Component Component是一个容易术语,用EndpointFactory可能会更恰当因为Component是一个用于创建EndPoint实例的工厂。例如,如果一个基于Camel的应用使用了多个JMS queues,这时应用将会创建一个JmsComponent(实现了Component接口)类的实例,然后应用调用其createEndpoint()方法来创建一个EndPoint实例。实际上,应用级别的代码不会直接调用Component.createEndpoint()方法,而通常会调用CamelContext.getEndpoint()方法来代替,该方法内部,CamelContext对象查找到相应的Component然后调用它的createEndpoint()方法。例如下面的代码: myCamelContext.getEndpoint("pop3://[email protected]?password=myPassword"); getEndpoint()方法的参数是一个URI,这个URI的前缀指明了这个组件的名称(name)。在内部,CamelContext对象拥有一个映射名称到组件的映射表。在上面的例子的URI中,CamelContext对象很可能将pop3前缀映射到一个MailComponent的实例。然后CamelContext对象调用这个MailComponent对象的createEndpoint("pop3://[email protected]?password=myPassword")方法。这个createEndpoint()方法将会把这个URI分隔成若干个部分然后使用这些部分来配置这个EndPoint对象。 在以前的段落中,提到了一个CamelContext对象拥有一个Component name到Component对象的映射。这就引出了一个问题,这张映射表如何来映射这些组件,有两种方式: 一是应用级别调用CamelContext.addComponent(String componentName, Component component)方法。下面这个例子将一个MailComponent对象用三个不同的名称注册在映射表中:
[java] view plain copy
  1. Component mailComponent = new org.apache.camel.component.mail.MailComponent();  
  2. myCamelContext.addComponent("pop3", mailComponent);  
  3. myCamelContext.addComponent("imap", mailComponent);  
  4. myCamelContext.addComponent("smtp", mailComponent);  
二是(更好)让CamelContext对象中的映射表延迟初始化,这种方式要开发者实现Component接口时遵循一定的约定。例如,假设你写了个名为com.example.myproject.FooComponent的类,而且你想让Camel自动以"foo"来进行识别。为了达到这样的目的你必须写一个叫"META-INF/services/org/apache/camel/component/foo"的Properties文件(没有.properties文件后缀),该Properties只有一个条目class,该条目的值就是该组件的全路径名: class=com.example.myproject.FooComponent 如果你还让com.example.myproject.FooComponent以name "bar"识别,你还得写另外一个properties文件(文件名为bar)在相同的目录中,而且含有相同的内容。一旦你写了这些Properties文件,你创建的jar包中含有com.example.myproject.FooComponent以及相应的Properties文件,并且这个jar包加入到了CLASSPATH下。这样在应用级别就可以用代码在CamelContext对象上调用createEndpoint("foo:...")方法,Camel将在CLASSPATH中找到"foo"properties文件,获取该文件中class条目的值,然后使用反射技术创建指定类的实例。 Camel支持多种开箱即用通信技术,该开箱即用支持由多个实现了Component接口的类以及它们相应的properties文件来实现,以供CamelContext对象来构建这张named Component对象映射表。 在前面的部分我给出了一个如下例子,调用CamelContext.getEndpoint(). myCamelContext.getEndpoint("pop3://[email protected]?password=myPassword"); 当我第一次给出这个例子时,我说getEndpoint()方法的参数是一个URI,我之所以那样说的原因是Camel的在线帮助文档和Camel的源代码都声称这是一个URI。而实际上,这个参数严格上来说是一个URL。这是因为当Camel从参数中抽取component name时,它第一步找":",这是一个简单算法。要理解其原因,就得回到Section4.4("The meaning of URL,URI,URN and IRI"),一个URI可能是一个URL或者是一个URN,考虑下面的getEndpoint()方法调用: myCamelContext.getEndpoint("pop3:..."); myCamelContext.getEndpoint("jms:..."); myCamelContext.getEndpoint("urn:foo:..."); myCamelContext.getEndpoint("urn:bar:..."); 在上面的例子中Camel标识这些组件用的名称分别是:"pop3","jms","urn"和"urn".如果后两个组件用"urn:foo"和"urn:bar"来标识可能会更贴切,实际上用的是"urn"和"urn"(只取第一个":"号前面部分作为Component name)。所以在实际应用中你必须用一个URL(":...形式")来标识一个endpoint而不是用一个URN("urn::..."形式)。因为这里缺少对URN的更多支持所以getEndpoint()方法的参数是一个URL而不是所谓的URI。 Message and Exchange Message接口是一个消息的抽象,比如说一个请求,一个回复或者一个例外消息。 Camel为每一个支持的通信技术提供了Message接口的相应实现类。例如,JmsMessage类提供了JMS-specific的Message接口实现。公共接口Message提供了getter与setter方法来访问message id,消息休和消息头字段。 Exchange接口是消息交换的抽象,一个请求消息和它相应的应答或者例外消息。在Camel技术中,请求,应答和例外消息分别被称为输入(in),输出(out),和错误(fault)消息。 Camel也为第一种支持的通信技术提供了Exchange接口的实现。例如,JmsExchange类提供了JMS-specific的Exchange接口实现。Exchange公共接口的API有很大的局限性,这是有意设计成这样的。因为每一个实现Exchange接口的实现类将会提供具体的技术相关的方法。 在应用级别程序员几乎不会直接访问Exchange接口(或者其实现类)。然而,在Cmael的很多类是泛型类are instantiated on(a class that implements)Exchange。这样做的原因是Exchange接口出现在了许多泛型的类和方法中。 Processor Processor接口代表一个可以处理消息的类,其签名如下: package org.apache.camel; public interface Processor {     void process(Exchange exchange) throws Exception; } 注意process()方法的参数是一个Exchange对象而不是一个Message对象。这样提供了扩展性。例如,一个实现类的process()方法初始可能会调用exchange.getIn()方法得到输入消息并且进行处理。如果在处理的过程中发生了错误,该方法可以调用exchange.setException()方法。一个应用级别的开发者可能会开发一个类实现Process接口来执行一个业务逻辑,然后在Camel类库中已经有很多类实现了Processor接口来支持EIP book中的设计模式。例如,ChoiceProcessor实现了消息的路由 ,它使用一种级联的if-then-else语句来路由一个来自队列的消息到多个输出队列中的一个。另外一个例子是FilterProcessor类,它将会抛弃那些不满足条件的消息。 Routes, RouteBuilders and Java DSL 一个路由是消息从一个输入队列一步一步地,通过任意类型的决断移动到一个目的队列。Camel为应用提供了两种方式供开发者指定路由,一种是将路由信息配置在一个XML文件当中,另一种方式是Camel中所谓的Java DSL(domain-specific language)。 Introduction to Java DSL 对很多人来说"domain-specific language"词语意味着一个编译器或解释器可以处理一个包含了关键字和具体语义的输入文件到一个特定的域中。但这不是Camel所采用的方式。Camel文档中坚持用“Java DSL”代替“DSL”,但这也不能完全避免潜在的混淆。Camel的“Java DSL”是一个类库,可以以一种与DSL很类似的方式使用,除非它包含很少的java语法。你可以看下面的例子,后面的注释解释了这个例子的构成。
[java] view plain copy
  1. RouteBuilder builder = new RouteBuilder() {  
  2.     public void configure() {  
  3.         from("queue:a").filter(header("foo").isEqualTo("bar")).to("queue:b");  
  4.         from("queue:c").choice()  
  5.                 .when(header("foo").isEqualTo("bar")).to("queue:d")  
  6.                 .when(header("foo").isEqualTo("cheese")).to("queue:e")  
  7.                 .otherwise().to("queue:f");  
  8.     }  
  9. };  
  10. CamelContext myCamelContext = new DefaultCamelContext();  
  11. myCamelContext.addRoutes(builder);  
第一行创建了一个RouteBuilder的匿名子类对象,覆盖了configure()方法。 The CamelContext.addRoutes(RouterBuilder builder)方法中调用了builder.setContext(this),所以RouterBuilder对象知道它自己关联的CamelContext对象,然后调用builder.configure()方法,configure()方法体调用的例如from(),filter(),choice(),when()isEqualTo(),otherwise()和to(). RouteBuilder.from(String uri)方法调用CamelContext对象的getEndpoint(uri),并且与RouteBuilder对象一起查找指定的Endpoint,然后放置一个FromBuilder包装器到这个Endpoint。FromBuilder.filter(Predicate predicate)方法为Predicate(条件)创建一个FilterProcessor对象,从header("foo").isEqualTo("bar")语句构建而来。这样上些操作递增地构造了一个Route对象并且将它添加进CamelContext。

你可能感兴趣的:(camel)