«1. Обзор

В этом уроке мы познакомимся с фреймворком Smooks.

Мы опишем, что это такое, перечислим его ключевые функции и, в конце концов, узнаем, как использовать некоторые из его более продвинутых функций.

Прежде всего, давайте кратко объясним, для чего предназначен фреймворк.

2. Smooks

Smooks — это платформа для приложений обработки данных, которая работает со структурированными данными, такими как XML или CSV.

Он предоставляет как API, так и модель конфигурации, которые позволяют нам определять преобразования между предопределенными форматами (например, XML в CSV, XML в JSON и т. д.).

Мы также можем использовать ряд инструментов для настройки нашего сопоставления, включая скрипты FreeMarker или Groovy.

Помимо преобразований, Smooks также предоставляет другие функции, такие как проверка сообщений или разделение данных.

2.1. Ключевые возможности

Давайте рассмотрим основные варианты использования Smooks:

    Преобразование сообщений — преобразование данных из различных исходных форматов в различные форматы вывода Обогащение сообщений — заполнение сообщения дополнительными данными, которые приходят из внешнего источника данных, такого как база данных Разделение данных — обработка больших файлов (ГБ) и разбиение их на более мелкие Связывание Java — создание и заполнение объектов Java из сообщений Проверка сообщений — выполнение проверок, таких как регулярное выражение, или даже создание собственных правила проверки

3. Исходная конфигурация

Начнем с зависимости Maven, которую нам нужно добавить в наш pom.xml:

<dependency>
    <groupId>org.milyn</groupId>
    <artifactId>milyn-smooks-all</artifactId>
    <version>1.7.0</version>
</dependency>

Последнюю версию можно найти на Maven Central.

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.

4.1. Basic Concepts

We’ll start with a simple example. Consider the following 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.

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.

Basically, the mappings are an XML file which contains transformation logic. In this article, we’ll use three different types of rules:

  • 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.

First, we need to construct a Smooks object and pass input XML as a stream:

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

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;
    // ...
}

Мы также должны расширить отображение конфигурации с помощью определений bean-компонента поставщика и элемента данных.

Обратите внимание, что мы также определили bean-компонент с отдельными элементами, который будет содержать все элементы элементов в ArrayList.

Наконец, мы будем использовать атрибут проводки Smooks, чтобы связать все это вместе.

Посмотрите, как в этом случае будут выглядеть сопоставления:

<?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>

Наконец, мы добавим несколько утверждений к нашему предыдущему тесту:

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. Проверка сообщений

Smooks поставляется с механизмом проверки, основанным на правилах. Давайте посмотрим, как они используются.

Определение правил хранится в конфигурационном файле, вложенном в тег ruleBases, который может содержать множество элементов ruleBase.

Каждый элемент ruleBase должен иметь следующие свойства:

    name – уникальное имя, используемое только для ссылки src – путь к поставщику исходного файла правил – полное имя класса, реализующего интерфейс RuleProvider ~ ~~ Smooks поставляется с двумя провайдерами из коробки: RegexProvider и MVELProvider.

Первый используется для проверки отдельных полей в стиле регулярных выражений.

Второй используется для выполнения более сложной проверки в глобальной области действия документа. Давайте посмотрим на них в действии.

5.1. RegexProvider

Давайте используем RegexProvider для проверки двух вещей: формата имени клиента и номера телефона. RegexProvider в качестве источника требуется файл свойств Java, который должен содержать проверку регулярных выражений в виде ключ-значение.

Чтобы удовлетворить наши требования, мы будем использовать следующую настройку:

5.2. MVELProvider

supplierName=[A-Za-z0-9]*
supplierPhone=^[0-9\\-\\+]{9,15}$

Мы будем использовать MVELProvider, чтобы проверить, меньше ли общая цена каждой позиции заказа 200. В качестве источника мы подготовим CSV-файл с двумя столбцами: имя правила и выражение MVEL.

Чтобы проверить правильность цены, нам нужна следующая запись:

5.3. Конфигурация проверки

"max_total","orderItem.quantity * orderItem.price < 200.00"

После того, как мы подготовили исходные файлы для баз правил, мы перейдем к реализации конкретных проверок.

Проверка — это еще один тег в конфигурации Smooks, который содержит следующие атрибуты:

executeOn — путь к имени проверяемого элемента — ссылка на базу правил onFail — указывает, какое действие будет выполнено при проверке не удается

    Давайте применим правила проверки к нашему файлу конфигурации 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>

«Теперь, когда конфигурация готова, давайте попробуем проверить, не пройдет ли проверка номера телефона поставщика.

Опять же, мы должны построить объект 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();
    }
}

И, наконец, утверждать, если произошла ошибка проверки:

@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. Преобразование сообщения

Следующее. мы хотим сделать, это преобразовать сообщение из одного формата в другой.

В Smooks этот метод также называется шаблонированием, и он поддерживает:

    FreeMarker (предпочтительный вариант) Шаблон строки XSL

В нашем примере мы будем использовать механизм FreeMarker для преобразования XML-сообщения в нечто, очень похожее на EDIFACT и даже подготовить шаблон сообщения электронной почты на основе порядка XML.

Давайте посмотрим, как подготовить шаблон для 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>

И для сообщения электронной почты:

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>

На этот раз конфигурация 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>

На этот раз нам нужно просто передать StringResult в движок Smooks:

Smooks smooks = new Smooks(config);
StringResult stringResult = new StringResult();
smooks.filterSource(new StreamSource(OrderConverter.class
  .getResourceAsStream(path)), stringResult);
return stringResult.toString();

И мы, конечно, можем его протестировать:

@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. Заключение

В этом руководстве мы сосредоточились на том, как преобразовывать сообщения в различные форматы или преобразовывать их в объекты Java с помощью Smooks. Мы также увидели, как выполнять проверки на основе регулярных выражений или правил бизнес-логики.

Как всегда, весь используемый здесь код можно найти на GitHub.