1. Overview
1.概述
In this tutorial, we’ll introduce the Smooks framework.
在本教程中,我们将介绍Smooks框架>。
We’ll describe what it’s, list its key features, and eventually learn how to use some of its more advanced functionality.
我们将描述它是什么,列出它的主要特点,并最终学习如何使用它的一些更高级的功能。
First of all, let’s briefly explain what the framework is meant to achieve.
首先,让我们简单解释一下这个框架的目的是什么。
2. Smooks
2.Smooks
Smooks is a framework for data processing applications – dealing with structured data such as XML or CSV.
Smooks是一个数据处理应用程序的框架–处理结构化数据,如XML或CSV。
It provides both APIs and a configuration model that allow us to define transformations between predefined formats (for example XML to CSV, XML to JSON and more).
它同时提供了API和配置模型,使我们能够定义预定义格式之间的转换(例如XML到CSV,XML到JSON等等)。
We can also use a number of tools to set up our mapping – including FreeMarker or Groovy scripts.
我们还可以使用一些工具来设置我们的映射 – 包括FreeMarker或Groovy脚本。
Besides transformations, Smooks also delivers other features like message validations or data splitting.
除了转换,Smooks还提供其他功能,如消息验证或数据分割。
2.1. Key Features
2.1.关键特征
Let’s take a look at Smooks’ main use cases:
让我们来看看Smooks的主要使用情况。
- Message conversion – transformation of data from various source formats to various output formats
- Message enrichment – filling out the message with additional data, which comes from external data source like database
- Data splitting – processing big files (GBs) and splitting them into smaller ones
- Java binding – constructing and populating Java objects from messages
- Message validation – performing validations like regex, or even creating your own validation rules
3. Initial Configuration
3.初始配置
Let’s start with the Maven dependency we need to add to our pom.xml:
让我们从需要添加到pom.xml的Maven依赖性开始。
<dependency>
<groupId>org.milyn</groupId>
<artifactId>milyn-smooks-all</artifactId>
<version>1.7.0</version>
</dependency>
The latest version can be found on Maven Central.
最新版本可以在Maven Central上找到。
4. Java Binding
4.Java 绑定
Let’s now start by focusing on binding messages to Java classes. We’ll go through a simple XML to Java conversion here.
现在让我们开始关注将消息绑定到Java类。我们将在这里经历一个简单的XML到Java的转换。
4.1. Basic Concepts
4.1.基本概念
We’ll start with a simple example. Consider the following XML:
我们将从一个简单的例子开始。考虑一下下面的XML。
<order creation-date="2018-01-14">
<order-number>771</order-number>
<order-status>IN_PROGRESS</order-status>
</order>
In order to accomplish this task with Smooks, we have to do two things: prepare the POJOs and the Smooks configuration.
为了用Smooks完成这项任务,我们必须做两件事:准备POJOs和Smooks配置。
Let’s see what our model looks like:
让我们看看我们的模型是什么样子的。
public class Order {
private Date creationDate;
private Long number;
private Status status;
// ...
}
public enum Status {
NEW, IN_PROGRESS, FINISHED
}
Now, let’s move on to Smooks mappings.
现在,让我们继续讨论Smooks的映射。
Basically, the mappings are an XML file which contains transformation logic. In this article, we’ll use three different types of rules:
基本上,映射是一个包含转换逻辑的XML文件。在本文中,我们将使用三种不同类型的规则。
- bean – defines the mapping of a concrete structured section to Java class
- value – defines the mapping for the particular property of the bean. Can contain more advanced logic like decoders, which are used to map values to some data types (like date or decimal format)
- wiring – allows us to wire a bean to other beans (for example Supplier bean will be wired to Order bean)
Let’s take a look at the mappings we’ll use in our case here:
让我们来看看我们在这里的案例中要使用的映射。
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd">
<jb:bean beanId="order"
class="com.baeldung.smooks.model.Order" createOnElement="order">
<jb:value property="number" data="order/order-number" />
<jb:value property="status" data="order/order-status" />
<jb:value property="creationDate"
data="order/@creation-date" decoder="Date">
<jb:decodeParam name="format">yyyy-MM-dd</jb:decodeParam>
</jb:value>
</jb:bean>
</smooks-resource-list>
Now, with the configuration ready, let’s try to test if our POJO is constructed correctly.
现在,配置准备好了,让我们试着测试一下我们的POJO是否被正确构建。
First, we need to construct a Smooks object and pass input XML as a stream:
首先,我们需要构造一个Smooks对象,并将输入的XML作为一个流传。
public Order converOrderXMLToOrderObject(String path)
throws IOException, SAXException {
Smooks smooks = new Smooks(
this.class.getResourceAsStream("/smooks-mapping.xml"));
try {
JavaResult javaResult = new JavaResult();
smooks.filterSource(new StreamSource(this.class
.getResourceAsStream(path)), javaResult);
return (Order) javaResult.getBean("order");
} finally {
smooks.close();
}
}
And finally, assert if the configuration is done properly:
最后,断言配置是否正确。
@Test
public void givenOrderXML_whenConvert_thenPOJOsConstructedCorrectly() throws Exception {
XMLToJavaConverter xmlToJavaOrderConverter = new XMLToJavaConverter();
Order order = xmlToJavaOrderConverter
.converOrderXMLToOrderObject("/order.xml");
assertThat(order.getNumber(), is(771L));
assertThat(order.getStatus(), is(Status.IN_PROGRESS));
assertThat(
order.getCreationDate(),
is(new SimpleDateFormat("yyyy-MM-dd").parse("2018-01-14"));
}
4.2. Advanced Binding – Referencing Other Beans and Lists
4.2.高级绑定 – 引用其他Bean和列表
Let’s extend our previous example with supplier and order-items tags:
让我们用供应商和订单项目标签扩展我们之前的例子。
<order creation-date="2018-01-14">
<order-number>771</order-number>
<order-status>IN_PROGRESS</order-status>
<supplier>
<name>Company X</name>
<phone>1234567</phone>
</supplier>
<order-items>
<item>
<quanitiy>1</quanitiy>
<code>PX1234</code>
<price>9.99</price>
</item>
<item>
<quanitiy>1</quanitiy>
<code>RX990</code>
<price>120.32</price>
</item>
</order-items>
</order>
And now let’s update our model:
现在让我们更新一下我们的模型。
public class Order {
// ..
private Supplier supplier;
private List<Item> items;
// ...
}
public class Item {
private String code;
private Double price;
private Integer quantity;
// ...
}
public class Supplier {
private String name;
private String phoneNumber;
// ...
}
We also have to extend the configuration mapping with the supplier and item bean definitions.
我们还必须用supplier和item bean定义来扩展配置映射。
Notice that we’ve also defined separated items bean, which will hold all item elements in ArrayList.
注意,我们还定义了分离的items Bean,它将保存ArrayList中的所有item元素。
Finally, we will use Smooks wiring attribute, to bundle it all together.
最后,我们将使用Smooks wiring 属性,将其全部捆绑在一起。
Take a look at how mappings will look like in this case:
看看在这种情况下,映射会是什么样子。
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd">
<jb:bean beanId="order"
class="com.baeldung.smooks.model.Order" createOnElement="order">
<jb:value property="number" data="order/order-number" />
<jb:value property="status" data="order/order-status" />
<jb:value property="creationDate"
data="order/@creation-date" decoder="Date">
<jb:decodeParam name="format">yyyy-MM-dd</jb:decodeParam>
</jb:value>
<jb:wiring property="supplier" beanIdRef="supplier" />
<jb:wiring property="items" beanIdRef="items" />
</jb:bean>
<jb:bean beanId="supplier"
class="com.baeldung.smooks.model.Supplier" createOnElement="supplier">
<jb:value property="name" data="name" />
<jb:value property="phoneNumber" data="phone" />
</jb:bean>
<jb:bean beanId="items"
class="java.util.ArrayList" createOnElement="order">
<jb:wiring beanIdRef="item" />
</jb:bean>
<jb:bean beanId="item"
class="com.baeldung.smooks.model.Item" createOnElement="item">
<jb:value property="code" data="item/code" />
<jb:value property="price" decoder="Double" data="item/price" />
<jb:value property="quantity" decoder="Integer" data="item/quantity" />
</jb:bean>
</smooks-resource-list>
Finally, we’ll add a few assertions to our previous test:
最后,我们将在之前的测试中添加一些断言。
assertThat(
order.getSupplier(),
is(new Supplier("Company X", "1234567")));
assertThat(order.getItems(), containsInAnyOrder(
new Item("PX1234", 9.99,1),
new Item("RX990", 120.32,1)));
5. Messages Validation
5.信息验证
Smooks comes with validation mechanism based on rules. Let’s take a look at how they are used.
Smooks带有基于规则的验证机制。让我们来看看它们是如何使用的。
Definition of the rules is stored in the configuration file, nested in the ruleBases tag, which can contain many ruleBase elements.
规则的定义存储在配置文件中,嵌套在ruleBases标签中,它可以包含许多ruleBase元素。
Each ruleBase element must have the following properties:
每个ruleBase元素必须有以下属性。
- name – unique name, used just for reference
- src – path to the rule source file
- provider – fully qualified class name, which implements RuleProvider interface
Smooks comes with two providers out of the box: RegexProvider and MVELProvider.
Smooks开箱就有两个提供者。RegexProvider和MVELProvider。
The first one is used to validate individual fields in regex-like style.
第一个是用来验证类似regex风格的单个字段。
The second one is used to perform more complicated validation in the global scope of the document. Let’s see them in action.
第二个是用来在文档的全局范围内进行更复杂的验证。让我们看看它们的作用。
5.1. RegexProvider
5.1.RegexProvider
Let’s use RegexProvider to validate two things: the format of the customer name, and phone number. RegexProvider as a source requires a Java properties file, which should contain regex validation in key-value fashion.
让我们使用RegexProvider来验证两件事:客户姓名的格式和电话号码。RegexProvider作为一个源需要一个Java属性文件,该文件应包含以键值方式进行的重码验证。
In order to meet our requirements, we’ll use the following setup:
为了满足我们的要求,我们将使用以下设置。
supplierName=[A-Za-z0-9]*
supplierPhone=^[0-9\\-\\+]{9,15}$
5.2. MVELProvider
5.2.MVELProvider
We’ll use MVELProvider to validate if the total price for each order-item is less then 200. As a source, we’ll prepare a CSV file with two columns: rule name and MVEL expression.
我们将使用MVELProvider来验证每个order-item的总价格是否小于200。作为一个来源,我们将准备一个CSV文件,其中有两列:规则名称和MVEL表达式。
In order to check if the price is correct, we need the following entry:
为了检查价格是否正确,我们需要以下条目。
"max_total","orderItem.quantity * orderItem.price < 200.00"
5.3. Validation Configuration
5.3.验证配置
Once we’ve prepared the source files for ruleBases, we’ll move on to implementing concrete validations.
一旦我们准备好了ruleBases的源文件,我们将继续实现具体的验证。
A validation is another tag in Smooks configuration, which contains the following attributes:
验证是Smooks配置中的另一个标签,它包含以下属性。
- executeOn – path to the validated element
- name – reference to the ruleBase
- onFail – specifies what action will be taken when validation fails
Let’s apply validation rules to our Smooks configuration file and check how it looks like (note that if we want to use the MVELProvider, we’re forced to use Java binding, so that’s why we’ve imported previous Smooks configuration):
让我们将验证规则应用于我们的Smooks配置文件,并检查它看起来如何(注意,如果我们想使用MVELProvider,我们被迫使用Java绑定,所以这就是为什么我们导入以前的Smooks配置):。
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:rules="http://www.milyn.org/xsd/smooks/rules-1.0.xsd"
xmlns:validation="http://www.milyn.org/xsd/smooks/validation-1.0.xsd">
<import file="smooks-mapping.xml" />
<rules:ruleBases>
<rules:ruleBase
name="supplierValidation"
src="supplier.properties"
provider="org.milyn.rules.regex.RegexProvider"/>
<rules:ruleBase
name="itemsValidation"
src="item-rules.csv"
provider="org.milyn.rules.mvel.MVELProvider"/>
</rules:ruleBases>
<validation:rule
executeOn="supplier/name"
name="supplierValidation.supplierName" onFail="ERROR"/>
<validation:rule
executeOn="supplier/phone"
name="supplierValidation.supplierPhone" onFail="ERROR"/>
<validation:rule
executeOn="order-items/item"
name="itemsValidation.max_total" onFail="ERROR"/>
</smooks-resource-list>
Now, with the configuration ready, let’s try to test if validation will fail on supplier’s phone number.
现在,配置准备好了,让我们试着测试一下验证是否会在供应商的电话号码上失败。
Again, we have to construct Smooks object and pass input XML as a stream:
同样,我们必须构建Smooks对象,并将输入的XML作为一个流传。
public ValidationResult validate(String path)
throws IOException, SAXException {
Smooks smooks = new Smooks(OrderValidator.class
.getResourceAsStream("/smooks/smooks-validation.xml"));
try {
StringResult xmlResult = new StringResult();
JavaResult javaResult = new JavaResult();
ValidationResult validationResult = new ValidationResult();
smooks.filterSource(new StreamSource(OrderValidator.class
.getResourceAsStream(path)), xmlResult, javaResult, validationResult);
return validationResult;
} finally {
smooks.close();
}
}
And finally assert, if validation error occurred:
最后断言,如果发生验证错误。
@Test
public void givenIncorrectOrderXML_whenValidate_thenExpectValidationErrors() throws Exception {
OrderValidator orderValidator = new OrderValidator();
ValidationResult validationResult = orderValidator
.validate("/smooks/order.xml");
assertThat(validationResult.getErrors(), hasSize(1));
assertThat(
validationResult.getErrors().get(0).getFailRuleResult().getRuleName(),
is("supplierPhone"));
}
6. Message Conversion
6.信息转换
The next thing we want to do is convert the message from one format to another.
我们要做的下一件事是将信息从一种格式转换为另一种格式。
In Smooks, this technique is also called templating and it supports:
在Smooks中,这种技术也被称为模板化,它支持。
- FreeMarker (preferred option)
- XSL
- String template
In our example, we’ll use the FreeMarker engine to convert XML message to something very similar to EDIFACT, and even prepare a template for the email message based on XML order.
在我们的例子中,我们将使用FreeMarker引擎将XML信息转换为与EDIFACT非常相似的东西,甚至根据XML顺序为电子邮件信息准备一个模板。
Let’s see how to prepare a template for EDIFACT:
让我们看看如何为EDIFACT准备一个模板。
UNA:+.? '
UNH+${order.number}+${order.status}+${order.creationDate?date}'
CTA+${supplier.name}+${supplier.phoneNumber}'
<#list items as item>
LIN+${item.quantity}+${item.code}+${item.price}'
</#list>
And for the email message:
而对于电子邮件的信息。
Hi,
Order number #${order.number} created on ${order.creationDate?date} is currently in ${order.status} status.
Consider contacting the supplier "${supplier.name}" with phone number: "${supplier.phoneNumber}".
Order items:
<#list items as item>
${item.quantity} X ${item.code} (total price ${item.price * item.quantity})
</#list>
The Smooks configuration is very basic this time (just remember to import the previous configuration in order to import Java binding settings):
这次的Smooks配置非常基本(只要记得导入之前的配置,以便导入Java绑定设置)。
<?xml version="1.0"?>
<smooks-resource-list
xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
xmlns:ftl="http://www.milyn.org/xsd/smooks/freemarker-1.1.xsd">
<import file="smooks-validation.xml" />
<ftl:freemarker applyOnElement="#document">
<ftl:template>/path/to/template.ftl</ftl:template>
</ftl:freemarker>
</smooks-resource-list>
This time we need to just pass a StringResult to Smooks engine:
这一次,我们只需要将一个StringResult传递给Smooks引擎。
Smooks smooks = new Smooks(config);
StringResult stringResult = new StringResult();
smooks.filterSource(new StreamSource(OrderConverter.class
.getResourceAsStream(path)), stringResult);
return stringResult.toString();
And we can, of course, test it:
当然,我们也可以测试它。
@Test
public void givenOrderXML_whenApplyEDITemplate_thenConvertedToEDIFACT()
throws Exception {
OrderConverter orderConverter = new OrderConverter();
String edifact = orderConverter.convertOrderXMLtoEDIFACT(
"/smooks/order.xml");
assertThat(edifact,is(EDIFACT_MESSAGE));
}
7. Conclusion
7.结论
In this tutorial, we focused on how to convert messages to different formats, or transform them into Java objects using Smooks. We also saw how to perform validations based on regex or business logic rules.
在本教程中,我们重点讨论了如何将消息转换为不同的格式,或使用Smooks将其转化为Java对象。我们还看到了如何根据重码或业务逻辑规则进行验证。
As always, all the code used here can be found over on GitHub.
像往常一样,这里使用的所有代码都可以在GitHub上找到。