使用开源组件smooks解析非标准EDI报文

来源:互联网 发布:algorithm 风格算法 编辑:程序博客网 时间:2024/06/05 14:17

最近项目需要使用上传EDI报文,然后解析EDI报文格式并持久化到数据库。然后在网上找了一下,有个非常强大的smooks组件可以实现这样的功能。而且不仅仅只实现从EDI -> Java的转化,还可以多种格式互相转化,唯一要做的就是配置两个非常重要的XML配置文件。

下面是关于smooks的比较详细的中文介绍:

Java的XML转换框架 Smooks


下面是smooks的下载地址,由于smooks的官网是属于河蟹的范围,然后我一直都懒得去翻墙访问了,详细的API在这里也能下载到。

smooks下载

现在的最新版本为 1.5.1


下载完成后解压,可以直接进入examples 文件夹,里面有smooks提供的所有功能的示例程序,部分示例程序使用了JUnit测试。

所以测试的时候可以直接把lib下的所有文件都导入进去。


上面就是所有的示例,可以看见smooks提供很多格式文件的转化功能,个人觉得比较常用的应该就是EDI XML CSV Java 之间的互相转换了吧。然后这次我主要关注的就是edi-to-java功能!


然后在这个示例程序里面,有标准的EDI报文格式,据带我的师傅说,这个EDI格式有很多种标准,由于这个smooks框架是国外的,所以他里面的EDI报文格式可能是国外的标准,然后我们国家有自己的EDI报文格式标准,有的公司有的行业或者又有他们自己规定的报文格式,所以这次我主要讨论比较复杂的自定义的报文格式怎么解析成JavaBean。


首先你要根据你自己的报文格式定义好各个JavaBean类。比如我的就复杂一点。我的是一个总类,总类下有公共信息,然后还有订单列表,每个订单下又有订单信息和货物信息和箱型信息列表。

文件格式如下

BillConfirm       |- Ship       |- List<ShipBillConfirm>                      |- (n * Fields)                      |- List<ShipBillConfirmCargo>                                        |- ShipBillConfirmCargo                                                    |- (n * Fields)                            |- List<ShipBillConfirmCntr>                                        |- ShipBillConfirmCntr                                                    |- (n * Fields)

这个就比标准结构复杂一点了,其实那个BillConfirm是不用放到数据库的,关键要关心的就是多个订单ShipBillConfirm,但是由于一个EDI里面可以有多个订单信息,所以在smooks中就要使用List,所以那个BillConfirm就只是一个容器而已。Ship是所有订单的公共信息。


下面给出测试的EDI报文格式。

00:BOOKOF:BOOKING ORDER FROM:9:SWS::200912281627'10:HENG YU:1001E::20100105:SHANGHAI'12:SJJ:JJNH4848240ATEST1:CY/CY:P::KOBE::KOBE::P&H CO.,LTD::NOPAL INT?'L C.S.CO.,LTD TEL?:045-253-8213 FAX?:045-253-8214::SAME AS CONSIGNEE::'41:1:O:1:1.00::1.000:N/M:SPORTWEAR'41:2:O:2:3.00::3.000:N/M:SPORTWEAR22'12:SJJ:JJNH4848240ATEST2:CY/CY:P::KOBE::KOBE::P&H CO.,LTD::NOPAL INT?'L C.S.CO.,LTD TEL?:045-253-8213 FAX?:045-253-8214::SAME AS CONSIGNEE::'41:1:O:1:11.00::1.000:N/M:SPORTWEAR333'99:9

这是一段比较简单的EDI报文格式了,其实实际中我的项目用到的比这个还要复杂一点,但是现在就只是测试和学习,我就改了一下,改得比较简单了。

下面就根据EDI报文格式和JavaBean来写配置文件。


然后smooks示例中解析的主要代码是在Main.Java中,去看代码就发现,关键的就是三个文件,一个是报文文件,这个可以我们自己修改获取的方式来解析,然后另外就是两个XML配置文件,一个是smooks-config.xml,一个是edi-to-java-order-mapping.xml。


然后就主要就是修改这两个文件。

其中smooks-config.xml是主配置文件,是用来把解析的东西来生成JavaBean的。

edi-to-java-order-mapping.xml是来匹配报文格式的配置文件。


其中smooks-config.xml的配置比较简单,和Hibernate差不多,用过的都比较容易上手。下面是我根据我的测试要求配置的:

<?xml version="1.0"?><smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"xmlns:edi="http://www.milyn.org/xsd/smooks/edi-1.1.xsd" xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd"><!--Configure the EDI Reader to parse the message stream into a stream ofSAX events.--><edi:reader mappingModel="/shhh/edi-confirm-mapping.xml" /><!--这里就是主Bean,最后生成返回的就是这个对象;其中使用了<jb:wiring>的标签,这个表示的是类的引用,property就是类中属性的name, beanIdRef 就是对这个文件中的bean的引用。 然后createOnElement这个不怎么好解释,这个是和那个报文配置文件绑定的, 也可以理解为在哪个类中生成下面的数据。--><jb:bean beanId="bill" class="shhh.entity.BillConfirm"createOnElement="BillConfirm"><jb:wiring property="ship" beanIdRef="ship" /><jb:wiring property="sbc" beanIdRef="sbcList" /></jb:bean><!--这个是在BillConfirm中的Ship属性,由于是单个的,所以createOnElement中需要引用${bill};其实这个到底代表什么意思我还没有去深入了解,我的暂时的理解是表示这个ship是生成在bill这个beanId下的,但是他引用的报文配置文件中的segment的XMLtag是"Ship";另外的decoder就是表示这个属性用的什么数据类型;data 表示的是在报文配置文件中的引用,报文配置文件中对应的是XMLtag;<jb:decodeParam> 一看就知道了,这个是日期格式的转换配置。--><jb:bean beanId="ship" class="shhh.entity.Ship"createOnElement="${bill}/Ship"><jb:value property="voyageNo" decoder="Integer" data="#/voyage-no" /><jb:value property="lineNam" decoder="String" data="#/line-nam" /><jb:value property="lineCod" decoder="String" data="#/line-cod" /><jb:value property="leavPortTim" decoder="Date" data="#/leave-date"><jb:decodeParam name="format">yyyyMMdd</jb:decodeParam></jb:value><jb:value property="EDestPortNam" decoder="String" data="#/edestportnam" /></jb:bean><!-- 这个就是关键的订单列表配置了,由于是有多个的列表的形式,所以要先配置列表,BillComfirm中引用的也是这个列表,然后这个列表再引用ShipBillComfirm这个bean --><jb:bean beanId="sbcList" class="java.util.ArrayList"createOnElement="BillConfirm"><jb:wiring beanIdRef="shipbillconfirm" /></jb:bean><!--这个就是关键的订单配置了,其中这个里面又配置了两个列表--><jb:bean beanId="shipbillconfirm" class="shhh.entity.ShipBillConfirm"createOnElement="ShipBillConfirm"><jb:value property="carrierCod" decoder="String" data="#/carriercod" /><jb:value property="billNbr" decoder="String" data="#/billnbr" /><jb:value property="drTypeId" decoder="String" data="#/drtypeid" /><jb:value property="payWayId" decoder="String" data="#/paywayid" /><jb:value property="dischrgPortNam" decoder="String"data="#/dischrgportnam" /><jb:value property="destPortNam" decoder="String" data="#/destportnam" /><jb:value property="EShipperNam" decoder="String" data="#/eshippernam" /><jb:value property="consigneeNam" decoder="String" data="#/consigneenam" /><jb:value property="notifyNam" decoder="String" data="#/notifynam" /><jb:value property="notifyNam2" decoder="String" data="#/notifynam2" /><jb:wiring property="cargo" beanIdRef="cargoList" /><jb:wiring property="cntr" beanIdRef="cntrList" /></jb:bean><!--货物列表信息--><jb:bean beanId="cargoList" class="java.util.ArrayList"createOnElement="ShipBillConfirm"><jb:wiring beanIdRef="shipbillconfirmcargo" /></jb:bean><!-- 箱型列表信息 --><jb:bean beanId="cntrList" class="java.util.ArrayList"createOnElement="ShipBillConfirm"><jb:wiring beanIdRef="shipbillconfirmcntr" /></jb:bean><!-- 单个货物信息的配置bean --><jb:bean beanId="shipbillconfirmcargo" class="shhh.entity.ShipBillConfirmCargo"createOnElement="ShipBillConfirmCargo"><jb:value property="seqNo" decoder="Short" data="#/seqno" /><jb:value property="cargoCod" decoder="String" data="#/cargocod" /><jb:value property="pieceNum" decoder="Long" data="#/piecenum" /><jb:value property="grossWeight" decoder="Double" data="#/grossweight" /><jb:value property="netWeight" decoder="Double" data="#/netweight" /><jb:value property="volNum" decoder="Double" data="#/volnum" /><jb:value property="marksStr" decoder="String" data="#/marksstr" /><jb:value property="ECargoNam" decoder="String" data="#/ecargonam" /></jb:bean><!-- 单个箱型信息的配置bean --><jb:bean beanId="shipbillconfirmcntr" class="shhh.entity.ShipBillConfirmCntr"createOnElement="ShipBillConfirmCntr"><jb:value property="cntrNo" decoder="String" data="#/cntrno" /><jb:value property="cntrSizeCod" decoder="String" data="#/cntrsizecod" /><jb:value property="cntrTypeCod" decoder="String" data="#/cntrtypecod" /></jb:bean></smooks-resource-list>

然后下面就是关键的解析EDI报文格式的mapping文件了:

<?xml version="1.0" encoding="UTF-8"?><medi:edimap xmlns:medi="http://www.milyn.org/schema/edi-message-mapping-1.3.xsd"><medi:description name="Ship Bill Confirm" version="1.0" /><medi:delimiters segment="'" field=":" component="^" sub-component="~" escape="?" /><medi:segments xmltag="BillConfirm"><medi:segment segcode="00" xmltag="Total"><medi:field xmltag="edi-name" /><medi:field xmltag="edi-des" /><medi:field xmltag="edi-func" /><medi:field xmltag="edi-user" /><medi:field xmltag="edi-empty1" /><medi:field xmltag="edi-create" /></medi:segment><medi:segment segcode="10" xmltag="Ship"><medi:field xmltag="voyage-no" /><medi:field xmltag="line-nam" /><medi:field xmltag="line-cod" /><medi:field xmltag="leave-date" /><medi:field xmltag="edestportnam" /></medi:segment><medi:segmentGroup maxOccurs="-1" minOccurs="0"><medi:segment segcode="12" xmltag="ShipBillConfirm" maxOccurs="-1" minOccurs="1"><medi:field xmltag="carriercod" /><medi:field xmltag="billnbr" /><medi:field xmltag="drtypeid" /><medi:field xmltag="paywayid" /><medi:field xmltag="dischrgportcod" /><medi:field xmltag="dischrgportnam" /><medi:field xmltag="destportcod" /><medi:field xmltag="destportnam" /><medi:field xmltag="shipperid" /><medi:field xmltag="eshippernam" /><medi:field xmltag="consigneeid" /><medi:field xmltag="consigneenam" /><medi:field xmltag="notifyid" /><medi:field xmltag="notifynam" /><medi:field xmltag="notifyid2" /><medi:field xmltag="notifynam2" /></medi:segment><medi:segment segcode="41" xmltag="ShipBillConfirmCargo" maxOccurs="-1" minOccurs="0"><medi:field xmltag="seqno" /><medi:field xmltag="cargocod" /><medi:field xmltag="piecenum" /><medi:field xmltag="grossweight" /><medi:field xmltag="netweight" /><medi:field xmltag="volnum" /><medi:field xmltag="marksstr" /><medi:field xmltag="ecargonam" /></medi:segment><medi:segment segcode="51" xmltag="ShipBillConfirmCntr" maxOccurs="-1" minOccurs="0"><medi:field xmltag="cntrno" /><medi:field xmltag="cntrsizecod" /><medi:field xmltag="cntrtypecod" /></medi:segment></medi:segmentGroup><medi:segment segcode="99" xmltag="End" minOccurs="0"><medi:field xmltag="endNumLine" /></medi:segment></medi:segments></medi:edimap>

其中有几个关键的键我解释一下:

<medi:description> 这个就是一个名字定义,没有太大影响;

<medi:delimiterssegment="'" field=":" component="^" sub-component="~"escape="?" />

这个里面,segment表示的是每个数据(也可以理解为每个Bean信息)的分割符,

field表示的是每个字段的分隔符,也就是按顺序分割数据,然后保存到Bean中的属性中(按照你写配置解析EDI的顺序保存)

component 和 sub-component 暂时我还没用到,看示例里面好想是用来生成XML时的子节点。

然后还有一个关键的escape这个是转义字符


然后我的Myeclipse不知怎么的用不了自动提示,不知道有哪些节点哪些属性可以用,害我只好把mapping的结构定义文件给下过来了 ╮(╯▽╰)╭,然后就慢慢看吧 = =。

其中<medi:segments>只能有一个,表示就是解析这个EDI文件的东西。

然后<medi:segment>就是每个解析的Bean的配置,用的就是那个每个数据的分隔符给分割的。

segcode 就是表示字段的开始字符,表示这个segmen的开始标志就是这个segcode。

xmltag 前面说过了就是和引用有关的,不能有重复的。

<medi:field xmltag="edi-name" /> 这个就是每个field字段了,根据你写这个的顺序来解析EDI并匹配到引用的xmltag中去。


然后关键的难点就是有个<medi:segmentGroupmaxOccurs="-1" minOccurs="0"> 这个东西,这个在示例里面是没有的,在API里面我也没看到,愣是在结构文件里面看到的!

这个就是相当与一个循环结构,表示下面配置的segment在EDI报文可以循环匹配。其实就是我前面提到的,一个EDI可以有多个订单数据,每个订单数据下又可以有多个货物信息和多个箱型信息。

所以下面就是会循环的数据配置。

最后有两个关键的属性:

maxOccurs:这个表示的是有多少个数据匹配,-1表示可能有多个数据匹配,意思就是可以有循环的

minOccurs:这个表示的是这个segment的出现的最少次数,0表示这个segment可能是不会出现的。(如果有可能不会出现的数据,这个必须设置为0,不然在解析的时候是会报错的,因为这个好像默认是1)


最后还有一点,可能遇到EDI报文里面有一个格式字段,但是我们不需要,但是在mapping配置里面还是要配置的,你可以不引用,但是不能无视掉,所以我最后还配置了一个99 的segment,但是没有引用。

等于就是smooks执行的是严格匹配查询,不是什么你只需要哪一段数据。所以关键的就是要根据固定的EDI格式来写好配置文件。

其实写好后,就算遇到EDI格式更改,你也只需要修改一下两个配置文件就可以继续用了,扩展性还是不错的。


然后其实这个EDI解析还可以直接生成XML文件的,在示例里面有,简单的两句代码就可以了,由于我暂时还没用到这个功能所以也就没有介绍了,感兴趣的可以去下载示例源码看看。


大概就是这些,如果还有什么东西再分享吧。然后这个方面应该有相关的大神,但是不知道为什么就是没有找到相关的介绍,这个毕竟在商业交流中还是用得比较广泛的。如果有大神路过,希望不吝指教。

1 0
原创粉丝点击