1. 概要

このチュートリアルでは、Smooksフレームワークを紹介します。

それが何であるかを説明し、その主要な機能をリストし、最終的にはそのより高度な機能のいくつかを使用する方法を学びます。

まず、フレームワークが何を達成することを意味するのかを簡単に説明しましょう。

2. スムークス

Smooksは、XMLやCSVなどの構造化データを処理するデータ処理アプリケーションのフレームワークです。

APIと、事前定義された形式(XMLからCSV、XMLからJSONなど)間の変換を定義できる構成モデルの両方を提供します。

FreeMarkerやGroovyスクリプトなど、さまざまなツールを使用してマッピングを設定することもできます。

変換に加えて、Smooksはメッセージ検証やデータ分割などの他の機能も提供します。

2.1. 主な機能

Smooksの主なユースケースを見てみましょう。

  • メッセージ変換–さまざまなソース形式からさまざまな出力形式へのデータの変換
  • メッセージの強化–データベースなどの外部データソースからの追加データをメッセージに入力します
  • データ分割–大きなファイル(GB)を処理し、それらを小さなファイルに分割します
  • Javaバインディング–メッセージからのJavaオブジェクトの構築と入力
  • メッセージの検証–正規表現などの検証を実行したり、独自の検証ルールを作成したりすることもできます

3. 初期構成

pom.xmlに追加する必要のあるMaven依存関係から始めましょう。

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

最新バージョンはMavenCentralにあります。

4. Javaバインディング

それでは、メッセージをJavaクラスにバインドすることに焦点を当てることから始めましょう。 ここでは、単純なXMLからJavaへの変換について説明します。

4.1. 基本概念

簡単な例から始めましょう。 次のXMLについて考えてみます。

<order creation-date="2018-01-14">
    <order-number>771</order-number>
    <order-status>IN_PROGRESS</order-status>
</order>

Smooksでこのタスクを実行するには、POJOとSmooks構成の準備という2つのことを行う必要があります。

モデルがどのように見えるか見てみましょう:

public class Order {

    private Date creationDate;
    private Long number;
    private Status status;
    // ...
}

public enum Status {
    NEW, IN_PROGRESS, FINISHED
}

それでは、Smooksマッピングに移りましょう。

基本的に、マッピングは変換ロジックを含むXMLファイルです。 この記事では、次の3種類のルールを使用します。

  • bean –具体的な構造化セクションのJavaクラスへのマッピングを定義します
  • value –Beanの特定のプロパティのマッピングを定義します。 一部のデータ型(日付や10進形式など)に値をマップするために使用されるデコーダーなどのより高度なロジックを含めることができます
  • w iring – を使用すると、Beanを他のBeanに接続できます(たとえば、 SupplierBeanはOrder Beanに接続されます)

ここでこのケースで使用するマッピングを見てみましょう。

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

構成の準備ができたら、POJOが正しく構築されているかどうかをテストしてみましょう。

まず、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();
    }
}

そして最後に、構成が適切に行われたかどうかを表明します。

@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. 高度なバインディング–他のBeanとリストの参照

前の例をサプライヤおよびorder-itemsタグで拡張してみましょう。

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

それでは、モデルを更新しましょう。

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定義を使用して構成マッピングを拡張する必要があります。

ArrayList内のすべてのitem要素を保持する分離されたitemsbeanも定義していることに注意してください。

最後に、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には、RegexProviderMVELProviderの2つのプロバイダーが付属しています。

最初のフィールドは、正規表現のようなスタイルで個々のフィールドを検証するために使用されます。

2つ目は、ドキュメントのグローバルスコープでより複雑な検証を実行するために使用されます。 それらの動作を見てみましょう。

5.1. RegexProvider

RegexProvider を使用して、顧客名の形式と電話番号の2つを検証してみましょう。 RegexProvider は、ソースとしてJavaプロパティファイルを必要とします。このファイルには、キー値方式で正規表現検証が含まれている必要があります

要件を満たすために、次の設定を使用します。

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

5.2. MVELProvider

MVELProvider を使用して、各注文アイテムの合計価格が200未満であるかどうかを検証します。 ソースとして、ルール名とMVEL式の2つの列を持つCSVファイルを準備します。

価格が正しいかどうかを確認するには、次のエントリが必要です。

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

5.3. 検証構成

ruleBases のソースファイルを準備したら、具体的な検証の実装に進みます。

検証は、Smooks構成の別のタグであり、次の属性が含まれています。

  • executeOn –検証された要素へのパス
  • name ruleBaseへの参照
  • 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では、この手法はtemplating とも呼ばれ、以下をサポートします。

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

このチュートリアルでは、メッセージをさまざまな形式に変換する方法、またはSmooksを使用してメッセージをJavaオブジェクトに変換する方法に焦点を当てました。 また、正規表現またはビジネスロジックルールに基づいて検証を実行する方法も確認しました。

いつものように、ここで使用されるすべてのコードは、GitHubにあります。