Apache Daffodil 快速入门与集成示例

Apache Daffodil 4.2.0 快速入门与集成示例,从环境准备到首个 DFDL 解析/回写示例逐步演示,并给出 Java API 与 Maven 依赖集成、以及在数据处理流程中嵌入 Daffodil 的样例代码与真实运行结果。

Apache DaffodilApache Daffodil2026/10/11

Apache Daffodil 快速入门与集成示例

目标:从零建立对 Apache Daffodil 的完整认知——先用 CLI 跑通一个定长文本的解析与回写,再通过 Java API 与 Maven 把 Daffodil 嵌入到数据处理流程中。所有命令、代码与回显均基于 Daffodil 4.2.0 实测。

一、快速入门目标与环境准备

1.1 你将完成

  1. 用 DFDL schema 描述一个定长文本记录。
  2. 用 daffodil parse 把数据解析为 XML/JSON 信息集。
  3. 用 daffodil unparse 把信息集回写为原始数据,并验证字节一致。
  4. 用 Java API + Maven 在应用中集成 Daffodil。

1.2 环境准备

已安装 JDK 17 与 Daffodil 4.2.0(安装位置 /opt/daffodil),并配置:

export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export DAFFODIL_HOME=/opt/daffodil
export PATH=$JAVA_HOME/bin:$DAFFODIL_HOME/bin:$PATH

验证:

daffodil --version

真实回显:

Apache Daffodil 4.2.0

Daffodil 4.x 需 JDK 17。所有示例工作目录为 /root/dfdl。

二、第一个 DFDL 例子

2.1 编写 schema

创建 person.dfdl.xsd:一条定长文本记录,姓名 5 字符、年龄 3 字符、编号 2 字符。

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:dfdl="http://www.ogf.org/dfdl/dfdl-1.0/"
           targetNamespace="http://example.com/dfdl/person"
           xmlns:tns="http://example.com/dfdl/person"
           elementFormDefault="unqualified">

  <xs:include schemaLocation="/org/apache/daffodil/xsd/DFDLGeneralFormat.dfdl.xsd"/>

  <xs:annotation>
    <xs:appinfo source="http://www.ogf.org/dfdl/">
      <dfdl:format ref="tns:GeneralFormat" encoding="ASCII" representation="text"/>
    </xs:appinfo>
  </xs:annotation>

  <xs:element name="person" type="tns:PersonType" dfdl:lengthKind="implicit"/>

  <xs:complexType name="PersonType">
    <xs:sequence>
      <xs:element name="name" type="xs:string"
                  dfdl:lengthKind="explicit" dfdl:length="5" dfdl:lengthUnits="characters"/>
      <xs:element name="age" type="xs:unsignedInt"
                  dfdl:lengthKind="explicit" dfdl:length="3" dfdl:lengthUnits="characters"
                  dfdl:textNumberPattern="000"/>
      <xs:element name="code" type="xs:string"
                  dfdl:lengthKind="explicit" dfdl:length="2" dfdl:lengthUnits="characters"/>
    </xs:sequence>
  </xs:complexType>

</xs:schema>

2.2 准备数据

创建 person.txt(共 10 字符):

Alice025AB

字段切分:name=Alice(5) / age=025(3) / code=AB(2)。

2.3 解析

cd /root/dfdl
daffodil parse -s person.dfdl.xsd person.txt

真实回显(exit=0):

<?xml version="1.0" encoding="UTF-8"?>
<tns:person xmlns:tns="http://example.com/dfdl/person">
  <name>Alice</name>
  <age>25</age>
  <code>AB</code>
</tns:person>

2.4 解读信息集

  • 输出是 DFDL 信息集(infoset),根元素为 tns:person,命名空间来自 schema 的 targetNamespace。
  • name、code 为字符串;age 为无类型值的十进制数(025 → 25)。
  • 信息集 XML 即为 Daffodil 的标准输入/输出格式,可直接被 unparse 消费。

三、unparse 回写闭环

daffodil parse -s person.dfdl.xsd person.txt -o person.xml
daffodil unparse -s person.dfdl.xsd person.xml -o person.out
od -c person.out
cmp person.txt person.out && echo IDENTICAL

真实回显:

0000000   A   l   i   c   e   0   2   5   A   B
0000012
IDENTICAL

要点:age 字段因 dfdl:textNumberPattern="000" 采用零填充,回写后恢复为 025,从而与原始数据字节级一致。

四、输出 JSON 信息集

daffodil parse -s person.dfdl.xsd -I json person.txt

真实回显:

{
  "person": {
    "name": "Alice",
    "age": 25,
    "code": "AB"
  }
}

五、Java API 集成

5.1 Maven 依赖坐标

Daffodil 4.x 的 Java API 类(org.apache.daffodil.api.*)由 daffodil-core_3 提供(经 Maven Central 校验,daffodil-core_3-4.2.0.jar 含 org/apache/daffodil/api/Daffodil.class):

<dependency>
  <groupId>org.apache.daffodil</groupId>
  <artifactId>daffodil-core_3</artifactId>
  <version>4.2.0</version>
</dependency>

完整 pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>daffodil-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>
  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.daffodil</groupId>
      <artifactId>daffodil-core_3</artifactId>
      <version>4.2.0</version>
    </dependency>
  </dependencies>
</project>

5.2 样例代码

src/main/java/DaffodilDemo.java:

import java.io.File;
import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
import java.nio.channels.Channels;
import org.apache.daffodil.api.Compiler;
import org.apache.daffodil.api.Daffodil;
import org.apache.daffodil.api.DataProcessor;
import org.apache.daffodil.api.Diagnostic;
import org.apache.daffodil.api.InputSourceDataInputStream;
import org.apache.daffodil.api.ParseResult;
import org.apache.daffodil.api.ProcessorFactory;
import org.apache.daffodil.api.UnparseResult;
import org.apache.daffodil.api.infoset.InfosetInputter;
import org.apache.daffodil.api.infoset.InfosetOutputter;

public class DaffodilDemo {
    public static void main(String[] args) throws Exception {
        File schemaFile = new File("/root/dfdl/person.dfdl.xsd");
        File dataFile = new File("/root/dfdl/person.txt");
        File infosetFile = new File("/root/dfdl/person.xml");

        // 1) 编译 schema 得到 ProcessorFactory
        Compiler compiler = Daffodil.compiler();
        ProcessorFactory pf = compiler.compileFile(schemaFile);
        if (pf.isError()) {
            for (Diagnostic d : pf.getDiagnostics()) {
                System.out.println("[schema] " + d.getMessage());
            }
            System.exit(1);
        }
        DataProcessor dp = pf.onPath("/");

        // 2) 解析数据 → XML 信息集
        InputSourceDataInputStream in =
                Daffodil.newInputSourceDataInputStream(new FileInputStream(dataFile));
        InfosetOutputter out = Daffodil.newXMLTextInfosetOutputter(System.out, true);
        ParseResult pr = dp.parse(in, out);
        System.out.println();
        System.out.println("parse isProcessingError=" + pr.isProcessingError());

        // 3) 回写信息集 → 原始数据
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        InfosetInputter inp =
                Daffodil.newXMLTextInfosetInputter(new FileInputStream(infosetFile));
        UnparseResult ur = dp.unparse(inp, Channels.newChannel(bos));
        System.out.println("unparse isProcessingError=" + ur.isProcessingError());
        System.out.println("unparse output bytes: ["
                + new String(bos.toByteArray(), "US-ASCII") + "]");
    }
}

5.3 编译与运行(两种方式)

方式一:直接用发行版 jar(无需 Maven):

javac -cp "/opt/daffodil/lib/*" -d . DaffodilDemo.java
java  -cp ".:/opt/daffodil/lib/*" DaffodilDemo

方式二:Maven 构建:

mvn -B -q compile org.codehaus.mojo:exec-maven-plugin:3.1.0:java -Dexec.mainClass=DaffodilDemo

真实回显(两种方式输出一致,exit=0):

=== parse data -> infoset (XML) ===
<?xml version="1.0" encoding="UTF-8"?>
<tns:person xmlns:tns="http://example.com/dfdl/person">
  <name>Alice</name>
  <age>25</age>
  <code>AB</code>
</tns:person>

parse isProcessingError=false
parse isValidationError=false
=== unparse infoset -> data ===
unparse isProcessingError=false
unparse output bytes: [Alice025AB]

六、Scala API 参考写法

Daffodil 的 Java API 与 Scala API 共用同一套 org.apache.daffodil.api 类型(发行 jar 为 Scala 3 编译)。以下 Scala 写法与上文 Java 逻辑一一对应,供 Scala 工程参考:

import java.io.{ByteArrayOutputStream, File, FileInputStream}
import java.nio.channels.Channels
import org.apache.daffodil.api.Daffodil

object DaffodilDemoScala {
  def main(args: Array[String]): Unit = {
    val compiler = Daffodil.compiler()
    val pf = compiler.compileFile(new File("/root/dfdl/person.dfdl.xsd"))
    if (pf.isError()) sys.error("schema error")
    val dp = pf.onPath("/")

    val in = Daffodil.newInputSourceDataInputStream(new FileInputStream("/root/dfdl/person.txt"))
    val out = Daffodil.newXMLTextInfosetOutputter(System.out, true)
    dp.parse(in, out)

    val bos = new ByteArrayOutputStream()
    val inp = Daffodil.newXMLTextInfosetInputter(new FileInputStream("/root/dfdl/person.xml"))
    dp.unparse(inp, Channels.newChannel(bos))
    println("unparse bytes: [" + new String(bos.toByteArray, "US-ASCII") + "]")
  }
}

说明:Java API 已在本环境实测通过;Scala 写法为同一 API 的等价调用,需在含 Scala 3 运行时的工程中编译。

七、在数据处理流程中嵌入 Daffodil

典型 ETL/网关场景:把定长报文解析为结构化信息集后进入下游处理,再按需回写。

// 每收到一批报文,复用同一个 DataProcessor(避免重复编译 schema)
ProcessorFactory pf = Daffodil.compiler().compileFile(new File("/opt/schemas/msg.dfdl.xsd"));
DataProcessor dp = pf.onPath("/");

for (File record : inbox) {
    ByteArrayOutputStream xml = new ByteArrayOutputStream();
    InfosetOutputter out = Daffodil.newXMLTextInfosetOutputter(xml, true);
    dp.parse(Daffodil.newInputSourceDataInputStream(new FileInputStream(record)), out);

    // 交给下游(校验、入库、转发…)
    process(xml.toByteArray());

    // 需要时再回写为原始格式
    ByteArrayOutputStream raw = new ByteArrayOutputStream();
    dp.unparse(
        Daffodil.newXMLTextInfosetInputter(new java.io.ByteArrayInputStream(xml.toByteArray())),
        Channels.newChannel(raw));
    archive(raw.toByteArray());
}

性能与稳定性建议:

  • 复用 DataProcessor:compileFile 只做一次;CLI 侧也可用 save-parser 生成 parser 文件后用 -P 复用。
  • 限制堆内存:export DAFFODIL_JAVA_OPTS="-Xms256m -Xmx512m"。
  • 用 tunable 兜底:如 maxOccursBounds、maxFieldContentLengthInBytes 防止异常数据导致内存激增。
  • 开启校验:-V daffodil 或 API withValidation("daffodil") 在解析阶段暴露数据问题。

八、常见问题

Q1:Java API 报 UnsupportedClassVersionError ... class file version 61.0?

运行环境 JDK 低于 17。Daffodil 4.x 需 JDK 17,升级 JDK 或指定 JAVA_HOME。

Q2:compileFile 返回的 pf.isError() 为 true?

schema 有定义错误。遍历 pf.getDiagnostics() 打印诊断;常见原因是缺少 DFDLGeneralFormat 引用(报 Property ... is not defined)。

Q3:parse 报 Parse Error: Unable to parse xs:unsignedInt from text?

数据与 schema 字段切分不匹配。核对 dfdl:length 与真实数据,注意数值字段需 dfdl:textNumberPattern 才能在回写时保留前导零。

Q4:Maven 找不到 daffodil-japi_3?

Daffodil 4.x 未发布 daffodil-japi_3;Java API 类包含在 org.apache.daffodil:daffodil-core_3:4.2.0 中,直接依赖该坐标即可。