Skip navigation.
Arch2Arch Tab BEA.com

Leveraging Complex Schema Features in Java the XMLBeans Way

by Raju Subramanian and Raj Alagumalai
05/04/2004

An XML schema is an XML document that defines a set of rules that other XML documents must conform to in order to be valid. XML schema has several advantages over earlier XML schema languages, such as document type definition (DTD) or simple object XML (SOX) and provides a rich collection of features, which can be used in different ways. It is essential to have complete support for schemeas, especially when they were written by another user or aren't under the control of the user.

Download the author's files associated with this article

XMLBeans is a tool that allows you to access the full power of XML in a Java-friendly way. It is a 100% schema compliant XML-Java binding tool.

The XMLBean solution is unique because it provides a dual view of the XML data. XMLBean maintains the original XML document with no change in information and structure and also provides a Java-based view of the XML data.

In this article we will discuss these aspects of XMLBean:

  • How XMLBean handles some schema types
  • How XMLBean provides access to the entire XML infoset
  • How XMLBean provides access to the schema object model

How XMLBeans Handles Some of the Complex Schema Types

Let's look at how XMLBeans handles some schema types. We will also briefly discuss how to use the generated Java classes to create XML documents.

Choice

The choice group element allows only one of its children to appear in an instance. Consider the following schema. In can be inferred that complexType SizeTypes can contain only one of the following choices NormalSizeType, SmallSizeType or LargeSizetype.

Choice.xsd

  ...
  &ltxs:element name="sizechoice" type="tns:SizeTypes"/>
        &ltxs:complexType name="SizeTypes">    
          &ltxs:choice maxOccurs="unbounded">      
          &ltxs:element name="normalsize" type="tns:NormalSizeType"/>      
          &ltxs:element name="smallsize" type="tns:SmallSizeType"/>    
          &ltxs:element name="largesize" type="tns:LargeSizeType"/>  
        </xs:choice>
  </xs:complexType>

  &ltxs:simpleType name="NormalSizeType">        
        &ltxs:restriction base="xs:token">       
          &ltxs:enumeration value="small"/>     
          &ltxs:enumeration value="medium"/>   
          &ltxs:enumeration value="large"/>    
        </xs:restriction>
  </xs:simpleType>

  &ltxs:simpleType name="SmallSizeType">   
        &ltxs:restriction base="xs:token">      
          &ltxs:enumeration value="XSmall"/>   
          &ltxs:enumeration value="XXSmall"/>   
        </xs:restriction>
  </xs:simpleType>

  &ltxs:simpleType name="LargeSizeType">    
        &ltxs:restriction base="xs:token">    
          &ltxs:enumeration value="XLarge"/>        
          &ltxs:enumeration value="XXLarge"/>   
        </xs:restriction> 
  </xs:simpleType>
  ...

An XML document that conforms to this schema can be created using the generated Java types as listed below.


        SizechoiceDocument sDoc = SizechoiceDocument.Factory.newInstance();          


SizeTypes sizeTypes = sDoc.addNewSizechoice(); sizeTypes.addNormalsize(NormalSizeType.LARGE);

XMLBeans also provides the fundamental validate() method which validates an instance document against the XML schema .

Union

The union group element defines a simple type as a collection (union) of values from specified simple data types. Consider the following schema. SimpleType SizeTypes can have any of the values from either NormalSizeType or a NumberSizes.

Union.xsd

  ...

  &ltxsd:element name="sizeunion" type="tns:SizeTypes"/> 

        &ltxsd:simpleType name="SizeTypes">   
          &ltxsd:union memberTypes="tns:NormalSizeType tns:NumberSizes"/> 
        &ltxsd:simpleType> 

  &ltxsd:simpleType name="NormalSizeType">              
        &ltxsd:restriction base="xsd:token">     
          &ltxsd:enumeration value="small"/>      
          &ltxsd:enumeration value="medium"/>            
          &ltxsd:enumeration value="large"/>     
        </xsd:restriction>
  </xsd:simpleType>

  &ltxsd:simpleType name="NumberSizes" >                        
                &ltxsd:restriction base="xsd:int">      
                &ltxsd:minInclusive value="28"></xsd:minInclusive>                    
                &ltxsd:maxInclusive value="48"></xsd:maxInclusive>    
                </xsd:restriction>
  </xsd:simpleType>
  ...

An XML document that conforms to this schema can be created as follows:


  SizeunionDocument sDoc = SizeunionDocument.Factory.newInstance();
  sDoc.setSizeunion(new Integer(28));

The setSizeUnion() method accepts object if the union is of simpletypes which restrict or extent different base types. In our sample NormalSizeType is a token and NumberSize is an integer. Consider the following schema:

Union.xsd

  &ltxsd:simpleType name="NumberSizeTypes">  
        &ltxsd:union memberTypes="tns:SmallNumberSizes tns:NumberSizes"/>   
  </xsd:simpleType> 

  &ltxsd:simpleType name="NumberSizes" >        
                &ltxsd:restriction base="xsd:int">                      
                &ltxsd:minInclusive value="28"></xsd:minInclusive>
                &ltxsd:maxInclusive value="48">                      
                </xsd:restriction>
  </xsd:simpleType>

  &ltxsd:simpleType name="SmallNumberSizes" >   
                &ltxsd:restriction base="xsd:int">              
                &ltxsd:minInclusive value="20"></xsd:minInclusive>
                &ltxsd:maxInclusive value="28"></xsd:maxInclusive>    
                </xsd:restriction>
  </xsd:simpleType>

In this case the setSizeUnion() method will accept an int, because the only possible values is an int value.

In section three we will talk about the schema object model and show how programmatically to obtain the list of possible schema types, which are part of a given union type.

Import

The import element allows a user to add multiple schemas with different target namespace to a document. If the user has an XML schema that imports other schemas, XMLBeans provides a convenient mechanism to access the types declared. XMLBeans generates Java types for each imported schemas in a package structure, which is based on the namespace of the imported schemas. This ensures that even if multiple schemas define the same element, the user can manipulate it in Java without conflicts.
Consider the following schema:

ImportingSchema.xsd

  ...
  &LTxs:schema 
  targetNamespace="http://sample.openuri.info/importingSchema"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"   
  xmlns:tns="http://sample.openuri.info/importingSchema" 
  xmlns:first="http://sample.openuri.info/firstimport"   
  xmlns:second="http://sample.openuri.info/secondimport">

  &LTxs:import namespace="http://sample.openuri.info/firstimport"schemaLocation="firstimport.xsd"/>
  &LTxs:import namespace="http://sample.openuri.info/secondimport"schemaLocation="secondimport.xsd"/>

  &LTxs:element name="order" type="tns:OrderType"/>

  &LTxs:complexType name="OrderType">           
        &LTxs:sequence>         
          &LTxs:element name="name" type="xs:string"/>  
          &LTxs:element name="size" type="first:DressSize"/> 
        &LTxs:element name="color" type="second:DressSize"/>
  &LT/xs:sequence>
  ...

Firstimport.xsd

  ...
  &LTxs:schema targetNamespace="http://sample.openuri.info/firstimport"xmlns:xs="http://www.w3.org/2001/XMLSchema">
  &LTxs:simpleType name="DressSize">    
        &LTxs:restriction base="xs:token">                        
          &LTxs:enumeration value="small"/>             
          &LTxs:enumeration value="medium"/>    
          &LTxs:enumeration value="large"/>             
        &LT/xs:restriction>
  &LT/xs:simpleType>
  ...

Secondimport.xsd

  ...
  &LTxs:schema targetNamespace="http://sample.openuri.info/secondimport"xmlns:xs="http://www.w3.org/2001/XMLSchema">
  &LTxs:simpleType name="DressSize">
        &LTxs:restriction base="xs:token">                        
                &LTxs:enumeration value="black"/>       
                &LTxs:enumeration value="white"/>       
                &LTxs:enumeration value="red"/>         
        &LT/xs:restriction>
  &LT/xs:simpleType>
  ...

When inspecting the Java classes generated by XMLBeans, notice that there are two DressSize classes in different packages and that the package name is generated based on the namespace value.

1


The XML document can be constructed as follows:


  ...
        OrderDocument orderDoc = OrderDocument.Factory.newInstance();
        OrderType ordertype = orderDoc.addNewOrder();          

        ordertype.setName("Shirt");  
        ordertype.setSize(info.openuri.sample.firstimport.DressSize.MEDIUM);
        ordertype.setColor(info.openuri.sample.secondimport.DressSize.RED);
  ...

Include

The include element allows a user to add multiple schemas with the same target namespace to a document. If the included schema does not have a target namespace defined, the target namespace of the including schema will be used for elements declared in the included schema. The Java types generated will all be in the package structure, which is based on the including namespace value.

Consider the following schema:

firstinclude.xsd

  ...
  &LTxs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  
  &LTxs:simpleType name="CommonSizeType">       
        &LTxs:restriction base="xs:token">      
          &LTxs:enumeration value="small"/>      
          &LTxs:enumeration value="medium"/>              
          &LTxs:enumeration value="large"/>      
        &LT/xs:restriction>
  &LT/xs:simpleType>
  &LT/xs:schema>
  ...

includingSchema.xsd

  ...
  &ltxs:schema xmlns:xs=http://www.w3.org/2001/XMLSchema
  targetNamespace="http://sample.openuri.info/includingSchema"     
  xmlns:tns="http://sample.openuri.info/includingSchema">
  
  &ltxs:include schemaLocation="firstinclude.xsd"/>     

  &ltxs:element name="order" type="tns:OrderType"/>     

  &ltxs:complexType name="OrderType">  
        &ltxs:sequence>    
          &ltxs:element name="name" type="xs:string"/>     
          &ltxs:element name="commonSize" type="tns:CommonSizeType"/>

        </xs:sequence>
  </xs:complexType>
  ...

If the including schema had a target namespace specified the Java classes generated for this schema would all be in the same package as shown below:1


If the including schema does not have a target namespace specified then the Java classes generated would all be in the noNamespace package as shown below.

1

Any

The any element enables the author to extend the XML document with elements not specified by the schema. Let us see how XMLBeans handles a schema with an any type. For the following schema:

  ...

  &ltxs:element name="product" type="tns:ProductType"/>
 
  &ltxs:complexType name="ProductType"> 
        &ltxs:sequence maxOccurs="unbounded">     
          &ltxs:element name="something" type="xs:anyType"/>            
        </xs:sequence>
  </xs:complexType>

  &ltxs:simpleType name="id" >  
        &ltxs:restriction base="xs:int">                        
        &ltxs:minInclusive value="28"></xs:minInclusive>      
        &ltxs:maxInclusive value="48"></xs:maxInclusive>                      
        </xs:restriction>
  </xs:simpleType>
  ...

XMLBeans provides multiple ways to populate an any type. One of the options is to parse an XML fragment and use the XMLobject's set method to set the value. The other option is to use XML cursor API to obtain a cursor and navigate to the correct position with the document and insert the text.

Both the options are listed below:


  ...        
        ProductDocument pDoc = ProductDocument.Factory.newInstance();
        ProductType ptype = pDoc.addNewProduct();  
        
        XmlObject xobj = ptype.addNewSomething();
        xobj.set( XmlObject.Factory.parse("<xml-fragment>BEA Systems,
        Inc</xml-fragment>") );
  ...


  ...
        ProductDocument pDoc = ProductDocument.Factory.newInstance();
        ProductType ptype = pDoc.addNewProduct();              

        XmlObject xobj = ptype.addNewSomething();                      

        XmlCursor xcursor = xobj.newCursor();           
        xcursor.toFirstContentToken();           
        xcursor.insertChars("BEA Systems Inc");
  ...

Extension and Restriction

Let's now see how XMLBeans handles extension and restriction. The following is a part of a schema that has a complexType (ShirtType) that is an extension of ProductType.


  ...
  &ltxs:complexType name="ProductType">    
        &ltxs:sequence>       
          &ltxs:element name="number" type="xs:int"/>     
          &ltxs:element name="name" type="xs:string"/>   
        </xs:sequence>
  </xs:complexType>
  
  &ltxs:complexType name="ShirtType"> 
        &ltxs:complexContent>             
          &ltxs:extension base="tns:ProductType">       
            &ltxs:sequence>         
              &ltxs:element name="size" type="xs:int"/>                
            </xs:sequence>      
          </xs:extension> 
        </xs:complexContent>
  </xs:complexType>

  &ltxs:element name="product"type="tns:ProductType"/>
  ...

When this schema is compiled, XMLBeans generates Java classes for the ProductType and ShirtType.

ProductType.java

  ...
  public interface ProductType extends com.bea.xml.XmlObject
  {
  ...
  }

ShirtType.java

  ...
  public interface ShirtType extends ProductType
  {
  ...
  }

The relation between the schema-types is translated into an inheritance relation between the Java classes. Consequently, ShirtType can be used wherever ProductType is specified.


  ...
        ProductDocument pDoc = ProductDocument.Factory.newInstance();                
        ShirtType shirt = ShirtType.Factory.newInstance();        
        shirt.setName("Shirt");         
        shirt.setNumber(25);        
        shirt.setSize(40);                

        pDoc.setProduct(shirt);
  ...

XMLBeans will add the xsi:type attribute to the XML to indicate that ShirtType was used for ProductType.


  &ltext:Product <xsi:type="ext:ShirtType"                    
        xmlns:ext="http://sample.openuri.info/extension"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">    
    &ltext:number>25</ext:number>    
    &ltext:name>Shirt</ext:name>        
    &ltext:size>40</ext:size>
  </ext:Product>

If instead of extension, if we were to use restriction.


  ...
  &ltxs:complexType name="ProductType">  
        &ltxs:sequence>        
          &ltxs:element name="number" type="xs:int"/>    
          &ltxs:element name="name" type="xs:string" minOccurs="0"/>   
        </xs:sequence>
  </xs:complexType>

  &ltxs:complexType name="ShirtType">    
        &ltxs:complexContent>        
          &ltxs:restriction base="tns:ProductType">    
            &ltxs:sequence>         
              &ltxs:element name="number" type="xs:int"/>                        
            </xs:sequence>                  
          </xs:restriction>           
        </xs:complexContent>
  </xs:complexType>

  &ltxs:element name="product" type="tns:ProductType"/>
  ...

Now ShirtType has only one child - number. The generated Java classes will look similar to the extension case. But the inheritance has a side effect that ShirtType has a setter for name, which it inherits from ProductType.java.


  ...        
        ShirtType shirt = ShirtType.Factory.newInstance();        
        shirt.setName("Shirt");        
        shirt.setNumber(25);            
        shirt.validate();
  ...

The last line will return false. If shirt.setName() is removed then validate() will return true.

Accessing the XML Infoset

XMLBeans keeps the underlying XML's infoset intact. The API that can be used to access this infoset is the XML cursor API. (link to docs). Using XML cursors we can easily write a function like getComments(), which returns an array of all comments in the XMLdocument.


  public String[] getComments(XmlObject xml) throws Exception    
  {        
        XmlCursor cur = xml.newCursor();                
        cur.toStartDoc();   // Move the cursor to the start of the document      

        Vector v = new Vector();
                        
        while (cur.hasNextToken())        
        {            
          cur.toNextToken();            
          XmlCursor.TokenType tokType = cur.currentTokenType();
          if (tokType.isComment()) v.addElement(cur.xmlText());        
        }        
        // Always a good idea to dispose the cursor after we are done using it 
        cur.dispose();
        String[] comments = new String[v.size()];        
        v.copyInto(comments);        

        return comments;    
  }

The isComment() function tells if the token at the current position of the cursor is a comment or not. Other useful token types that you can test for are attribute, processing instruction, and namespace declaration.

The XML cursor API also lets us add comments or processing instructions to the infoset .


  ...
        XmlObject xml = XmlObject.Factory.parse("<root><text>Some text</text>");                          
        XmlCursor cur = xml.newCursor();        
        cur.toStartDoc();        
        cur.toFirstChild(); // Moves to &ltroot>        
        cur.toNextToken();  // Moves to &lttext>        

        // Add before &lttext>        
        cur.insertProcInst("pi", "Adding using XmlCursor");
  ...

This will add a processing instruction into the XML just before the <text> token. The XMLwill look like this:


  &ltroot>  
        <?pi Adding using XmlCursor?>  
        &lttext>Some text</text>
  </root>

Accessing the Schema Object Model

XMLBeans also provides a very rich schema object model (SOM) that lets you access the underlying schema meta-data and understand the content model programmatically. This can be used to do among other things generate sample XML for a given schema.

Here's some code that for example gets a list of all member types of the union type size types that we have seen earlier:


  ...
        SchemaType schemaType = SizeTypes.type;                    

        // We know this is a SchemaType.UNION 
        SchemaType[] members = schemaType.getUnionMemberTypes();            

        for (int i = 0; i < members.length; i++)    
        {                  
           QName name = members[i].getName();        
           System.out.println(name);       
        }
  ...

We can also get the SchemaType using XmlObject.schemaType(). The above code snippet will give the following output in the format: {namespaceURI}member-QName


  {http://sample.openuri.info/union}NormalSizeType
  {http://sample.openuri.info/union}NumberSizes

Another useful example is to get a list of the enumeration values for a type like the NormalSizeType that's a member of SizeTypes.


  ...
        schemaType = NormalSizeType.type;    
        XmlAnySimpleType[] tokens = schemaType.getEnumerationValues();    
        for (int i = 0; i < tokens.length; i++)        
        System.out.println( tokens[i].getStringValue() );
  ...

Conclusion

This article has discussed

  • XMLBeans handling of some of schema types
  • Getting to the underlying xml infoset using XML cursor
  • Accessing the underlying schema meta-date using XMLBeans' schema object model


This article's intent is to give a glimpse of what can be achieved with XMLBeans. To learn more about this technology, please visit http://dev2dev.bea.com/technologies/xmlbeans/index.jsp.

Article Tools

Email E-mail
Print Print
Blog Blog

Related Technologies

Bookmark Article

del.icio.us del.icio.us
Digg Digg
DZone DZone
Furl Furl
Reddit Reddit