SOAP技术与B2B应用集成(4)
SOAP消息中的类型/值的编序方法和示例
本文最初由 IBM developerWorks 中国网站发表,其网址是
http://www.ibm.com/developerWorks/cn/
在前面的文章SOAP的型系统和数据编码规则中,我们能了解到SOAP的类型和数据的编码是基于一个简单类型系统的,这个简单类型系统是基于程序语言、数据库和半结构数据中的类型系统的,是程序语言、数据库和半结构数据中类型系统的公共特性的一个泛化。在该简单类型系统中,一个类型要么是一个简单(可量化的)类型或是一个复合类型,这个复合类型由多个部分组成,每个部分是一个类型(包括简单类型或复合类型)。那么本文就分别针对简单类型和复合类型(数组和结构)来详细介绍类型和值的遍序方法,希望通过详尽的示例给读者以清晰的了解和掌握。
XML Schema提供了一种灵活性很高,具备良好可扩展性的类型的定义描述方式。SOAP规范主要是采用XML Schema作为其类型模式的定义语言,当然由于SOAP是开放的,灵活的,也可以使用其他任意的模式定义的XML Application来制订特定应用的类型和值的描述。
简单类型
对于简单类型,SOAP采用了在“XML Schema Part 2: Datatypes”的“内置数据类型Build-in datatypes”中定义的所有类型,包括值和词汇空间(lexical spaces)。例子包括:
Type
Example
Int
58502
Float
314159265358979E+1
negativeInteger
-32768
String
Louis "Satchmo" Armstrong
在XML Schema规范中声明的数据类型可以直接在元素模式中使用。而源于这些类型的类型也可以被使用。下面是一个模式片段和相应类型元素数据的例子:
<element name="quantity" type="int"/>
<element name="discount" type="float"/>
<element name="displacement" type="negativeInteger"/>
<element name="color">
<simpleType base="xsd:string">
<enumeration value="Green"/>
<enumeration value="Blue"/>
</simpleType>
</element>
<quantity >45</quantity >
<discount>5.9</discount>
<displacement>-450</displacement>
<color>Blue</color>
无论简单值类型是在“XML Schema Part 2: Datatypes”规范中定义,还是基于XML Schema规范所提供的类型定义机制,都必须被编码为元素的内容。
如果一个简单值被编码为一个独立元素或一个异构数组的元素,这就意味着有一个对应于数据类型的元素声明。因为“XML Schema Part 2: Datatypes”规范中包含了类型定义,但没有包含对应元素的声明,而SOAP-ENC模式和命名空间为每个简单数据类型声明了一个元素。这些定义都是可以被使用的。
<SOAP-ENC:int id="int1">45</SOAP-ENC:int>
字符串
“string”数据类型在“XML Schema Part 2: Datatypes”中被定义。值得注意的是在许多数据库或编程语言中,“string”类型并不是一致的,在某些特别的语言中,可能只允许一些字符能出现在“string”中。(这些值可能需要表示为xsd:string之外的一些数据类型)
一个字串可以被编码为一个单引用或多引用值。
包含string值的元素可以有一个“id”属性。额外的存取标识元素可以有匹配它的“href”属性。
例如,如果有两个对同一string的存取标识出现,则可以表现为:
<payment id="String-0">USD$5000</payment>
<cost href="#String-0"/>
无论如何,事实上对一个string(或者是string的子类型subtypr)的实例加以两个引用与将他们编码成两个单引用值并没有本质的区别:
<payment>Hello</payment>
<cost>Hello</cost>
对于这些例子的模式描述可能是:
<element name="payment" type="SOAP-ENC:string"/>
<element name="cost" type="SOAP-ENC:string"/>
(在这个例子中,用于描述元素类型的SOAP-ENC:string类型是一个方便使用的方法,用这样一个定义来描述一个元素的类型是“xsd:string”,并且它可以附带“id” 和“href”属性。大家可以参阅SOAP Encoding Schema来得到确切的定义。实例模式也可以使用这些SOAP Encoding模式中的声明,但这不是必须的。)
枚举
“XML Schema Part 2: Datatypes”规范定义了一种称为“玫举enumeration”的机制。SOAP数据模型直接采用了这个机制。可是,由于编程语言及其他语言在定义玫举上存在着一些细微的差别,因此我们在这里描述了更详细的概念,并描述了如何将一个成为玫举列表成员的值进行编码。具体的,它编码为该值的名。
在概念上,“玫举”表示了一组不同的名。一个具体的玫举是一个符合基本类型的不同值的具体列表。例如,颜色名(“Green”, “Blue”, “Brown”)的集合可以被定义为一个基于内置string类型的玫举, 值(“1”, “3”, “5”)则可能是一个基于integer的玫举,等等。“XML Schema Part 2: Datatypes”规范支持除boolean外所有简单类型的玫举。“XML Schema Part 2: Structures”规范语言可以用于定义玫举类型。如果一个模式是从另一种符号体系生成过来而没有具体的基本类型可应用,那么就使用“string”。在下面的模式例子“EyeColor”被定义为一个string的玫举,其可能的值包括“Green”、“Blue”、“Brown”,同时实例数据也对应地被给出了。
<element name="ProductColor" type="tns:ProductColor"/>
<simpleType name="ProductColor" base="xsd:string">
<enumeration value="Green"/>
<enumeration value="Blue"/>
<enumeration value="Brown"/>
</simpleType>
<Product>
<Name>Jaguar X-Type</Name>
<Price>240000.00</Age>
<ProductColor>Brown</ProductColor>
</Product>
Byte数组
一个Byte数组可以编码为单引用或多引用值。Byte数组的编码规则与string是类似的。
特别的,包含Byte数组值的元素可以有一个“id”属性。额外的存取标识元素可以有一个用于匹配的“href”属性。
对一个不透明的Byte数组的推荐表示是使用在XML Schema规范中定义的“base64”编码方式,具体编码算法是在RFC2045中定义。不过,MIME中base64编码数据的数据行长度限制在SOAP中将不存在。SOAP中应使用“SOAP-ENC:base64”子类型来定义base64编码。
<picture xsi:type="SOAP-ENC:base64">
aG93IG5vDyBicm73biBjb3cNCg==
</picture>
多态存取标识
许多语言允许存取标识可以是多态地访问数个类型的值,在运行时刻每个类型都是可使用的。一个多态存取标识实例必须包含一个“xsi:type”属性以描述类型的实际值。
例如,一个名为“cost”带有类型为“xsd:float”的值的多态存取标识可以编码为:
<cost xsi:type="xsd:float">29.95</cost>
与之相对的是一个值类型不变的cost存取标识。
<cost>29.95</cost>
多态的具体表示将仅在实例中出现,但多态的定义是在模式中出现,即在模式中应不绑定任意具体类型。
复合类型
SOAP依照在程序语言中常常看见的以下结构模式来定义复合类型:
结构
“struct结构”是一个复合类型值,其成员的存取标识名是相互区别的唯一标志,应彼此各不相同。
数组
“array数组”是一个复合类型值,其成员的顺序位置是相互区别的唯一标志。
复合值、结构和值的引用
复合值的成员被编码为存取标识元素。存取标识由他们的名字来相区别(例如在struct里面),而元素名就是存取标识名。存取标识名是局部的,作用域是包含他们的类型中,具备一个未修饰的元素名,而其他则有完全修饰名。
下面是一个“Book”结构的例子:
<e:Book>
<author>CHAI Xiaolu</author>
<preface>Prefatory text</preface>
<intro>This is a book.</intro>
</e:Book>
下面则是一个描述该结构的模式片段:
<element name="Book">
<complexType>
<element name="author" type="xsd:string"/>
<element name="preface" type="xsd:string"/>
<element name="intro" type="xsd:string"/>
</complexType>
</e:Book>
下面是一个即包含简单类型成员也包含复合类型成员的类型的例子。它显示了两层的引用。注意“Author”存取标识元素的“href”属性对匹配“id”值对应的值的引用。“Address”中的情况也是类似的。
<e:Book>
<title>InterOP Stack Technology</title>
<author href="#Person-1"/>
</e:Book>
<e:Person id="Person-1">
<name>CHAI Xiaolu</name>
<address href="#Address-2"/>
</e:Person>
<e:Address id="Address-2">
<email>mailto:fennivel@dealeasy.com</email>
<web>http://fennivel.xmlprobe.com</web>
</e:Address>
当“Person”和“Address”的值是需要多引用的时候,上述描述是合适的。如果使用单引用来描述,则应该是嵌入的,如下:
<e:Book>
<title>InterOP Stack Technology</title>
<author>
<name>CHAI Xiaolu</name>
<address>
<email>mailto:fennivel@dealeasy.com</email>
<web>http://fennivel.xmlprobe.com</web>
</address>
</author>
</e:Book>
如果这里存在着一个限制:在一个给出的实例中不允许有两个人有同样的地址,地址可以是一个街道地址(Street-address),也可以是一个电子地址(Electronic-address)。一本有两个作者的书可以编码为:
<e:Book>
<title>InterOP Stack Technology</title>
<firstauthor href="#Person-1"/>
<secondauthor href="#Person-2"/>
</e:Book>
<e:Person id="Person-1">
<name>CHAI Xiaolu</name>
<address xsi:type="m:Electronic-address">
<email>mailto:fennivel@dealeasy.com</email>
<web>http://fennivel.xmlprobe.com</web>
</address>
</e:Person>
<e:Person id="Person-2">
<name>Fennivel</name>
<address xsi:type="n:Street-address">
<street>Fennix 2000</street>
<city>Shanghai</city>
<state>Shanghai</state>
</address>
</e:Person>
编序也可以包含一些不在同一资源中的值的引用:
<e:Book>
<title>Paradise Lost</title>
<firstauthor href="http://www.dartmouth.edu/~milton/"/>
</e:Book>
同时下面是一个对上面结构的模式描述片段:
<element name="Book" type="tns:Book"/>
<complexType name="Book">
<!-- Either the following group must occur or else the
href attribute must appear, but not both. -->
<sequence minOccurs="0" maxOccurs="1">
<element name="title" type="xsd:string"/>
<element name="firstauthor" type="tns:Person"/>
<element name="secondauthor" type="tns:Person"/>
</sequence>
<attribute name="href" type="uriReference"/>
<attribute name="id" type="ID"/>
<anyAttribute namespace="##other"/>
</complexType>
<element name="Person" base="tns:Person"/>
<complexType name="Person">
<!-- Either the following group must occur or else the
href attribute must appear, but not both. -->
<sequence minOccurs="0" maxOccurs="1">
<element name="name" type="xsd:string"/>
<element name="address" type="tns:Address"/>
</sequence>
<attribute name="href" type="uriReference"/>
<attribute name="id" type="ID"/>
<anyAttribute namespace="##other"/>
</complexType>
<element name="Address" base="tns:Address"/>
<complexType name="Address">
<!-- Either the following group must occur or else the
href attribute must appear, but not both. -->
<sequence minOccurs="0" maxOccurs="1">
<element name="street" type="xsd:string"/>
<element name="city" type="xsd:string"/>
<element name="state" type="xsd:string"/>
</sequence>
<attribute name="href" type="uriReference"/>
<attribute name="id" type="ID"/>
<anyAttribute namespace="##other"/>
</complexType>
数组
SOAP数组被定义为类型为“SOAP-ENC:Array”或类型源于“SOAP-ENC:Array”[参阅规则8]。数组被表示为对包含其的元素的名无特殊约束的元素值(就象元素的值一般不会约束元素的名)。
数组可以包含任意类型的元素,包括嵌套数组。由SOAP-ENC:Array的约束建立的新类型也可以被创建并表示,例如,数组可以限于整数或一些用户自定义玫举型的数组。
数组值的表示是一个有序序列,该有序序列由该数组组成元素序列化组成。作为一个数组的值,元素名对于区分存取标识并非重要。元素可以有任意的名。实际上,这些元素的命名将按照模式中声明的建议,或由他们的类型所决定。就象在复合类型中通常情况下,如果数组中条目的值是单引用值,则该条目将包含它的值。否则,条目通过“href”属性引用它的值。
下面是一个模式的片段以及一个包含integer成员的数组:
<element name="myFavoriteNumbers"
type="SOAP-ENC:Array"/>
<myFavoriteNumbers
SOAP-ENC:arrayType="xsd:int[2]">
<number>3</number>
<number>4</number>
</myFavoriteNumbers>
在这个例子中,“myFavoriteNumber”包含了几个成员,每个成员的值的类型都是SOAP-ENC:int。而类型是由SOAP-ENC:arrayType属性决定的。注意SOAP-ENC:Array的类型允许不严格的未修饰的元素名。这些名只传输了非类型的信息,因此在具体使用的时候,要么有一个xsi:type属性,要么包含它的元素要包含一个SOAP-ENC:arrayType属性。自然地,源于SOAP-ENC:Array的类型可以声明带类型信息的局部元素。
就象先前指出的,SOAP-ENC模式包含了一些元素名的申明,而这些名是对应于 “XML Schema Part 2: Datatypes”规范中的每个简单类型的。这也包含一个“Array”的声明。使用这些定义,我们也许可以将显现先前的描述改写为:
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:int[2]">
<SOAP-ENC:int>3</SOAP-ENC:int>
<SOAP-ENC:int>4</SOAP-ENC:int>
</SOAP-ENC:Array>
数组可以包含任意指定arrayType的子类型的实例。也就是说,成员的类型可以是任何描述在arrayType属性中类型的可替代类型,这将依照于在模式中表示的可替代规则。因此,例如,一个整数数组可以包含任何源于integer的类型的值(例如 “int”或任何用户定义的源于integer的类型)。类似的,一个“address”数组可以包含一个严格的或扩展的类型,比如“internationalAddress”。因为提供的SOAP-ENC:Array类型允许包含任何类型或类型混合的成员,除非有对arrayType属性使用的特别限制。
成员元素类型在实例中可以使用xsi:type来描述,或则是在成员元素模式中声明,就象在下面两个数组中分别演示的那样。
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:ur-type[4]">
<thing xsi:type="xsd:int">12345</thing>
<thing xsi:type="xsd:decimal">6.789</thing>
<thing xsi:type="xsd:string">
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
</thing>
<thing xsi:type="xsd:uriReference">
http://fennivel.xmlprobe.com/collection/
</thing>
</SOAP-ENC:Array>
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:ur-type[4]">
<SOAP-ENC:int>12345</SOAP-ENC:int>
<SOAP-ENC:decimal>6.789</SOAP-ENC:decimal>
<xsd:string>
Of Mans First Disobedience, and the Fruit
Of that Forbidden Tree, whose mortal tast
Brought Death into the World, and all our woe,
</xsd:string>
<SOAP-ENC:uriReference>
http://fennivel.xmlprobe.com/collection/
</SOAP-ENC:uriReference >
</SOAP-ENC:Array>
数组值可以是结构或其他复合值。例如一个“xyz:Order”结构的数组:
<SOAP-ENC:Array SOAP-ENC:arrayType="xyz:Order[2]">
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</SOAP-ENC:Array>
数组也可以有一些成员值是数组。下面是一个有两个数组的数组的例子,而那两个数组都是string数组:
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[][2]">
<item href="#array-1"/>
<item href="#array-2"/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="array-1" SOAP-ENC:arrayType="xsd:string[2]">
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="array-2" SOAP-ENC:arrayType="xsd:string[2]">
<item>r2c1</item>
<item>r2c2</item>
</SOAP-ENC:Array>
包含一个数组值的元素并不需要一定被命名为“SOAP-ENC:Array”。它可以有任意的名,而提供的类型则要么是SOAP-ENC:Array,要么是受源于SOAP-ENC:Array的限制。例如,下面是一个模式片段以及与之一致的一个实例数组。
<simpleType name="phoneNumber" base="string"/>
<element name="ArrayOfPhoneNumbers">
<complexType base="SOAP-ENC:Array">
<element name="phoneNumber" type="tns:phoneNumber" maxOccurs="unbounded"/>
</complexType>
<anyAttribute/>
</element>
<xyz:ArrayOfPhoneNumbers SOAP-ENC:arrayType="xyz:phoneNumber[2]">
<phoneNumber>206-555-1212</phoneNumber>
<phoneNumber>1-888-123-4567</phoneNumber>
</xyz:ArrayOfPhoneNumbers>
数组可以是多维的。在这种情况下,就会有多个描述维数大小的值出现在arrayType属性的asize部分。
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[2,3]">
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
<item>r2c1</item>
<item>r2c2</item>
<item>r2c3</item>
</SOAP-ENC:Array>
上述例子中显示了一个数组如何被编码为独立元素,数组值可以以嵌套方式出现,如果并且他们是单引用的话,建议是以嵌套方式出现。
下面是一个模式片段的例子以及符合该模式的一个嵌套在“Person”结构中的电话号码数组,该数组可以从存取标识“phone-numbers”访问。
<simpleType name="phoneNumber" base="string"/>
<element name="ArrayOfPhoneNumbers">
<complexType base="SOAP-ENC:Array">
<element name="phoneNumber" type="tns:phoneNumber" maxOccurs="unbounded"/>
</complexType>
<anyAttribute/>
</element>
<element name="Person">
<complexType>
<element name="name" type="string"/>
<element name="phoneNumbers" type="tns:ArrayOfPhoneNumbers"/>
</complexType>
</element>
<xyz:Person>
<name>John Hancock</name>
<phoneNumbers SOAP-ENC:arrayType="xyz:phoneNumber[2]">
<phoneNumber>206-555-1212</phoneNumber>
<phoneNumber>1-888-123-4567</phoneNumber>
</phoneNumbers>
</xyz:Person>
下面是单引用数组值的另一个例子,数组值被编码为嵌入元素,这些元素都包含作为存取标识的元素名。
<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems SOAP-ENC:arrayType="Order[2]">
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>
部分传递数组
SOAP提供对部分传递数组的支持,这就象在一些场合下称作的“可变长”数组。一个部分传递数组应使用“SOAP-ENC:offset”属性标识,该属性的记数是以第一元素的位移为0开始的。如果省略该属性,则位移默认是0。
下面是一个大小为5的数组,同时在传递的时候仅传递第三和第四个元素:
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[5]" SOAP-ENC:offset="[2]">
<item>The third element</item>
<item>The fourth element</item>
</SOAP-ENC:Array>
稀疏数组
SOAP提供对稀疏数组的支持。每一个表示成员值的元素包含一个“SOAP-ENC:position”属性来表明它处于数组的位置。下面是一个二维字串数组的稀疏数组的例子。他的大小是4,但仅有位置2被使用:
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[,][4]">
<SOAP-ENC:Array href="#array-1" SOAP-ENC:position="[2]"/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id="array-1" SOAP-ENC:arrayType="xsd:string[10,10]">
<item SOAP-ENC:position="[2,2]">Third row, third col</item>
<item SOAP-ENC:position="[7,2]">Eighth row, third col</item>
</SOAP-ENC:Array>
如果在上述数组中对array-1仅有一个引用,那上述例子可以被编码为:
<SOAP-ENC:Array SOAP-ENC:arrayType="xsd:string[,][4]">
<SOAP-ENC:Array SOAP-ENC:position="[2]" SOAP-ENC:arrayType="xsd:string[10,10]>
<item SOAP-ENC:position="[2,2]">Third row, third col</item>
<item SOAP-ENC:position="[7,2]">Eighth row, third col</item>
</SOAP-ENC:Array>
</SOAP-ENC:Array>
复合类型的泛化规则
上面引用到的编码规则并不限于那些预先知道存取标识的情形。如果存取标识名仅当在编码过程中由值的出现才能决定的时候,同样的规则也可以应用,也就是一个存取标识被编码为同名的元素,并且该存取标识要么包含要么引用它的值。包含那些类型不能预先决定的值的存取标识必须包含一个合适的xsi:type属性来给出值的类型。
类似的,通过使用引用的编码规则对于包含一些混合的存取标识的复合类型的编序而言已经足够了,这些存取标识有的是以名来区分,有的是以名和位置来区分 (也就是说,有些存取标识会重复出现) 。这并不一定需要有包含这一类型的模式存在,但是如果一个类型模型模式没有该类型时,那么一个对应的XML句法模式和实例就应该被生成。
<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>
类似的,将一个复合类型值编序为类似数组结构的是合法的,但这不是SOAP-ENC:Array类型或其子类型。例如:
<PurchaseLineItems>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
默认值
一个使用省略格式的存取标识元素意味着有一个默认值或是尚不知道它的值。而该细节是基于存取标识、方法和上下文的。例如一个省略格式的存取标识典型地是表示一个多态存取标识的Null值(Null依赖存取标识的确切含义)。同样地,一个省略格式的Boolean存取标识典型地就意味着是一个False值或值未知,同时一个省略格式的数值存取标识典型地意味着它的值是0或值未知。
SOAP root属性
SOAP root属性可以被用来标注编序的根,当然他并不是对象图真正的根,所以对象图是可以解序的。该属性可以赋予“0”和“1”这两个值的任意一个。一个对象图的真正的根一般就有值“1”。那些非真正根的编序根也可以被标注为赋予值为“1”的编序根。一个元素也可以被明确地标注为赋予值为“0”的非编序根。
SOAP根属性可以出现在SOAP Header和SOAP Body元素中的任何子元素里,该属性并没有一个默认值。
该属性一般被用于使用了非嵌套方法(通过多引用来实现)的情形,由于存在同一层次的多个形式的根,因此需要标识真正的根。