DDEX

介绍

DDEX是"数字音乐交换"(Digital Data Exchange)的缩写,它是一个全球性的标准化组织,致力于为数字音乐行业提供统一的数据交换标准和协议。

DDEX格式主要以文本 xml格式为主,目前行业内主要以 ftp存储方式进行交换数据。

版本

截至到目前为止,DDEX格式已经走过多个年头,版本繁多,行业内以 mlc-14rdr-15两个版本为主。

请参考DDEX版本

解析

DDEX格式部分数据较为原始,为尽量理解该格式,该文章主要以 DDEX格式各版本 xsd文件使用 jaxb方式生成 Java代码来解析。

Maven项目中引入以下插件,并配置以下三项:

  • packageName: 生成的 Java代码包名前缀。
  • source: DDEX格式的 xsd文件位置。
  • xjbSource: jaxb生成限制等,示例如下。
<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>jaxb2-maven-plugin</artifactId>
    <version>3.2.0</version>
    <executions>
        <execution>
            <goals>
                <goal>xjc</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <locale>en</locale>
        <arguments>
            <arg>-XautoNameResolution</arg>
        </arguments>
        <packageName>net.ddex.ern</packageName>
        <sources>
            <source>src/main/resources/ern/341/release-notification.xsd</source>
        </sources>
        <xjbSources>
            <xjbSource>src/main/resources/binding.xjb</xjbSource>
        </xjbSources>
    </configuration>
</plugin>
<?xml version="1.0" ?>
<jaxb:bindings
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:jaxb="https://jakarta.ee/xml/ns/jaxb"
    version="3.0">

   <!-- Raise theEnumMemberSizeCap limit -->
   <jaxb:bindings>
       <jaxb:globalBindings typesafeEnumMaxMembers="8000" typesafeEnumMemberName="generateName"/>
   </jaxb:bindings>
</jaxb:bindings>

配置完成后使用 Maven运行该插件中 xjc指令即可在 target目录下找到生成的对应代码,将代码复制到对应项目中即可使用。

代码使用

该章节主要以个人理解编写,不代表官方案例,可能部分理解错误,具体请以实际为准。

  1. xsd生成的代码中主要以 ObjectFactory为主要创建形式,示例如下:
// rdr-15 协议中创建 xml 基础对象方法
DeclarationOfSoundRecordingRightsClaimMessage claimMessage = rdrFactory.createDeclarationOfSoundRecordingRightsClaimMessage();
// 头部信息
MessageHeader messageHeader = rdrFactory.createMessageHeader();
// 设置头部信息的其他方法
...
// 将头部信息设置到基础对象中
claimMessage.setMessageHeader(createMessageHeader());

// 设置本次传输的具体资源信息(音频、视频等)
ResourceList resourceList = rdrFactory.createResourceList();
claimMessage.setResourceList(resourceList);

// 创建音频信息
SoundRecording soundRecording = rdrFactory.createSoundRecording();
// 设置音频信息
...
// 将音频添加到资源里
resourceList.getSoundRecording().add(soundRecording);

// 创建视频
Video video = rdrFactory.createVideo();
// 设置音频信息
...
// 将视频添加到资源里
resourceList.getVideo().add(video);
  1. 使用 jaxb生成 xml,示例代码如下:
// 以下代码仅用于 jdk8,之后的版本因 jarkata 替换问题,请自行探索

// jaxb 相关类都为 javax.xml.bind 下类
import javax.xml.bind.JAXBContext;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

public static String beanToXml(Object object, String schemaLocation, String prefix) {
    StringWriter stringWriter = new StringWriter();
    try {
        // 根据 xml 基础对象类创建 jaxb context
        JAXBContext jaxbContext = JAXBContext.newInstance(object.getClass());
        Marshaller marshaller = jaxbContext.createMarshaller();

        //编码格式
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        // 是否格式化生成的xml串
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        // 是否生成报文头
        marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
        // 不同版本的 schema 不同,在这里单独设置
        marshaller.setProperty(Marshaller.JAXB_SCHEMA_LOCATION, schemaLocation);
        // 定义 namespace 的前缀,prefix 为自己定义的变量
        marshaller.setProperty("com.sun.xml.bind.namespacePrefixMapper", new NamespacePrefixMapper() {
            @Override
            public String getPreferredPrefix(String namespaceUri, String suggestion, boolean requirePrefix) {
                if (namespaceUri.equals("http://www.w3.org/2001/XMLSchema-instance")) {
                    return "xs";
                }

                return prefix;
            }
        });

        // 因上方设置报文头,所以需要手动添加
        stringWriter.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        // 自动转换对象为 xml 文本输出
        marshaller.marshal(object, stringWriter);
    } catch (Exception e) {
            log.error("{}, {}", e.getClass().getSimpleName(), ExceptionUtil.stacktraceToOneLineString(e));
    }

    return stringWriter.toString();
}

@SuppressWarnings("unchecked")
public static <T> T xmlToBean(Class<T> clazz, String xml) {
    // 根据不同类,将 xml 反序列化为不同对象
    T xmlObject = null;
    try (StringReader stringReader = new StringReader(xml)) {
        JAXBContext context = JAXBContext.newInstance(clazz);
        // 进行将Xml转成对象的核心接口
        Unmarshaller unmarshaller = context.createUnmarshaller();
        xmlObject = (T) unmarshaller.unmarshal(stringReader);
    } catch (Exception e) {
        log.error("{}, {}", e.getClass().getSimpleName(), ExceptionUtil.stacktraceToOneLineString(e));
    }

    return xmlObject;
}
  1. 其他 ddex使用 jaxb中常见问题:

    • 部分生成的以 JAXBElement<?>为类型的字段,创建示例如下:
    Name performerName = rdrFactory.createName();
    performerName.setValue(performerSplit);
    PartyName performerPartyName = rdrFactory.createPartyName();
    performerPartyName.setFullName(performerName);
    JAXBElement<PartyName> performerElement = rdrFactory.createPartyDescriptorPartyName(performerPartyName);
    performerElement.setValue(performerPartyName);
    Artist artist = rdrFactory.createArtist();
    artist.getContent().add(performerElement);
    territory.getDisplayArtist().add(artist);
    
    • jaxbddex中时间,创建示例如下:
    GregorianCalendar gregorianCalendar = new GregorianCalendar();
    gregorianCalendar.setTime(new Date());
    XMLGregorianCalendar xmlDatetime = DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
    //本次信息生成时间
    messageHeader.setMessageCreatedDateTime(xmlDatetime);