Binding Schema Elements To Java Map
In this tutorial we will annotate a schema to bind its elements and types to java.lang.Map instances, its entries, keys and values.
In the tutorial, the Java object model consists entirely from maps and strings. Let's look at the schema first. Binding annotations it contains will be explained later.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://www.jboss.org/test/xml/maps" xmlns="http://www.jboss.org/test/xml/maps" xmlns:jbxb="http://www.jboss.org/xml/ns/jbxb" elementFormDefault="qualified" attributeFormDefault="unqualified" version="1.0"> <xsd:complexType name="mapType"> <xsd:annotation> <xsd:documentation> <![CDATA[ This type declares various elements that are bound to map entries. This type itself is also bound to a map entry. The value of the map entry is bound to java.util.Map (all the elements declared in this type are going to be bound to its entries) and the key of the map entry is represented by the attribute "key" declared below. \]\]\> </xsd:documentation> <xsd:appinfo> <jbxb:mapEntry valueType="java.util.HashMap" nonNullValue="false"></jbxb:mapEntry> </xsd:appinfo> </xsd:annotation> <xsd:sequence> <xsd:element name="entry1"> <xsd:annotation> <xsd:documentation> <![CDATA[ This element represents a map entry of the following style: <entry> <key>key_data</key> <value>value_data</value> </entry> \]\]\> </xsd:documentation> <!-- specifying putMethod is optional if the parent is an instance of java.util.Map <xsd:appinfo> <jbxb:putMethod name="put" keyType="java.lang.Object" valueType="java.lang.Object"></jbxb:putMethod> </xsd:appinfo> --> </xsd:annotation> <xsd:complexType> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntry getKeyMethod="getKey" getValueMethod="getValue"></jbxb:mapEntry> </xsd:appinfo> </xsd:annotation> <xsd:sequence> <xsd:element name="key1" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryKey></jbxb:mapEntryKey> </xsd:appinfo> </xsd:annotation> </xsd:element> <xsd:element name="value1" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryValue></jbxb:mapEntryValue> </xsd:appinfo> </xsd:annotation> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="entry2"> <xsd:annotation> <xsd:documentation> <![CDATA[ This element represents a map entry of the following style: <entry key="key_data"> <value>value_data</value> </entry> \]\]\> </xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntry></jbxb:mapEntry> </xsd:appinfo> </xsd:annotation> <xsd:sequence> <xsd:element name="value2" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryValue></jbxb:mapEntryValue> </xsd:appinfo> </xsd:annotation> </xsd:element> </xsd:sequence> <xsd:attribute name="key2" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryKey></jbxb:mapEntryKey> </xsd:appinfo> </xsd:annotation> </xsd:attribute> </xsd:complexType> </xsd:element> <xsd:element name="entry3"> <xsd:annotation> <xsd:documentation> <![CDATA[ This element represents a map entry of the following style: <entry key="key_data" value="value_data"></entry> \]\]\> </xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntry></jbxb:mapEntry> </xsd:appinfo> </xsd:annotation> <xsd:attribute name="key3" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryKey></jbxb:mapEntryKey> </xsd:appinfo> </xsd:annotation> </xsd:attribute> <xsd:attribute name="value3" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryValue></jbxb:mapEntryValue> </xsd:appinfo> </xsd:annotation> </xsd:attribute> </xsd:complexType> </xsd:element> <xsd:element name="entry4"> <xsd:annotation> <xsd:documentation> <![CDATA[ This element represents a map entry of the following style: <entry key="key_data">value_data</entry> \]\]\> </xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntry></jbxb:mapEntry> <jbxb:characters> <jbxb:mapEntryValue></jbxb:mapEntryValue> </jbxb:characters> </xsd:appinfo> </xsd:annotation> <xsd:simpleContent> <xsd:extension base="xsd:string"> <xsd:attribute name="key4" type="xsd:string"> <xsd:annotation> <xsd:appinfo> <jbxb:mapEntryKey></jbxb:mapEntryKey> </xsd:appinfo> </xsd:annotation> </xsd:attribute> </xsd:extension> </xsd:simpleContent> </xsd:complexType> </xsd:element> <xsd:element name="submap" type="mapType"> <xsd:annotation> <xsd:documentation> <![CDATA[ This element also represents a map entry. But while previous entries contained text values, this one has a value that is bound to java.util.Map as it is annotated in its complex type definition. \]\]\> </xsd:documentation> </xsd:annotation> </xsd:element> </xsd:sequence> <xsd:attribute name="key" type="xsd:string"> <xsd:annotation> <xsd:documentation> <![CDATA[This attribute is bound to a map entry key.\]\]\> </xsd:documentation> <xsd:appinfo> <jbxb:mapEntryKey></jbxb:mapEntryKey> </xsd:appinfo> </xsd:annotation> </xsd:attribute> </xsd:complexType> <xsd:element name="map" type="mapType"> <xsd:annotation> <xsd:documentation> <![CDATA[ Here, we declare an element of complex type mapType. Complex type mapType is bound to a map entry. Elements inherit bindings from their types. So, this element inherits map entry binding metadata. But that's not what we want for the root element in an XML content. So, we override binding here using class annotation and bind this element to java.util.Map. (See binding documentation: class and mapEntry are mutually exclusive) \]\]\> </xsd:documentation> <xsd:appinfo> <jbxb:class impl="java.util.HashMap"></jbxb:class> </xsd:appinfo> </xsd:annotation> </xsd:element> </xsd:schema>
Let's take a look at a possible document
<map xmlns="http://www.jboss.org/test/xml/maps" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <entry1> <key1>key1</key1> <value1>value1</value1> </entry1> <entry2 key2="key2"> <value2>value2</value2> </entry2> <entry3 key3="key3" value3="value3"></entry3> <entry4 key4="key4">value4</entry4> <submap key="submap"> <entry3 key3="submapKey" value3="submapValue"></entry3> </submap> </map>
The root element 'map' contains elements that wrap various key-value pairs, in other words - map entries. So, we want to bind 'map' element to java.util.Map. For this, we have jbxb:class annotation. But what should be annotated with jbxb:class? 'map' element or 'mapType' complex type? Let's see.
Let's forget about the element 'map' for now. Complex type 'mapType' declares various elements that are bound to map entries. Note, it also declares an element with name 'submap' of complex type mapType which also should be bound to a map entry with its attribute 'key' bound to the key and its content bound to the value. So, one on hand, type 'mapType' is a map, on the other one, it is a map entry.
Let's look carefully at the 'mapType' type definition. It actually models a map entry: attribute 'key' is the key and the content is the value. So, we annotate 'mapType' with jbxb:mapEntry and bind it to what it actually models - a map entry. We also bind the attribute 'key' to entry key with jbxb:mapEntryKey. We bound complex type to a map entry and an attribute to its key. The content is automatically bound to the entry value. But the value of the entry should also be bound to a map, so we add 'valueType' attribute to mapEntry with the fully-qualified class name of the value. Now, complex type mapType is bound to a map entry, the key is the attribute 'key' and the content is the value of type java.util.Map.
Now, we have to bind each element declared in complex type mapType to a map entry. For this, we annotate all the entry elements (actually, their complex types) with jbxb:mapEntry. Then, depending on the style of map entry representation, we annotate their elements, attributes and character data with mapEntryKey and mapEntryValue. Note how we bind character data in entry4 to a map entry value. We wrap mapEntryValue in characters element to specify that we bind character data to the value and not the type itself.
We do not annotate 'submap' element declared in mapType since it inherits map entry binding from its type.
Now, let's recall the global and the root element 'map'. It inherits bindings from its type, which means it is bound to a map entry. That's not what we want. So, we override bindings inherited from its type and annotate the element with jbxb:class to bind it to java.util.HashMap.
That's it. This schema is used in the org.jboss.test.xml.MapsUnitTestCase.
Comments