Jun Qian (钱骏) 's Weblog

Tuesday Jul 22, 2008

XAM AutoGen: a tool to automate XAM model generation

XAM (Extensible Abstract Model) is an extensible framework for building domain-specific object model on top of any base models. Many NetBeans modules (such as WSDL Model, Schema Model, XSLT Model, BPEL Model) are built on top of XAM. A XAM-based model is best suited for an IDE like NetBeans because of its IDE-friendly features (unlimited undo/redo, automatic synchronization, document fidelity, etc.), but you are not tied to NetBeans by using XAM.

To create a XAM-based model, one typically starts from the schema describing the domain. It's a very tedious process to generate all the Java interfaces and implementation classes, along with the all plumbing classes (factories, visitors, etc.). 

To ease the pain of manual class creation, a tool is needed to auto generate a XAM-based model from a XML schema. This blog entry describes a tool that takes a domain schema as input, and uses Velocity templates to generate a XAM-based model.

This tool doesn't use any popular schema language (such as XML Schema (W3C), or Relax NG.)  There are a couple of reasons:

  1. A formal XML schema language is a little bit too heavy. I want to clearly define what is supported and what is not.
  2. In addition to domain typing, I also want to add extra semantics. For example, I want to express an element FOO has an attribute BAR of type URI or QName which refers to another element BAZ in this or even a foreign domain.
The following is the meta schema (the schema for your domain schema) used by this tool:
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://xml.netbeans.org/schema/xamgen"
            xmlns:tns="http://xml.netbeans.org/schema/xamgen"
            elementFormDefault="qualified"> 
               
    <xsd:element name="Content">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="Element"  minOccurs="0" maxOccurs="unbounded"/>
                <xsd:element name="EnumType" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>    

    <xsd:element name="Element">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="Attribute" minOccurs="0" maxOccurs="unbounded"/>
                <xsd:element name="ChildElement" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
            <xsd:attribute name="name" type="xsd:string" use="required"/>
            <xsd:attribute name="className" type="xsd:string"/>
            <xsd:attribute name="root" type="xsd:boolean" default="false"/>
            <xsd:attribute name="virtual" type="xsd:boolean" default="false"/>
            <xsd:attribute name="base" type="xsd:string"/>
            <xsd:attribute name="anyAttribute" type="xsd:boolean" default="false"/>
            <xsd:attribute name="anyElement" type="xsd:boolean" default="false"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Attribute">
        <xsd:complexType>
            <xsd:attribute name="name" type="xsd:string" use="required"/>
            <xsd:attribute name="type" type="xsd:string" use="required"/>
            <xsd:attribute name="reference" type="xsd:string"/>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="ChildElement">
        <xsd:complexType>
            <xsd:attribute name="name" type="xsd:string" use="required"/>
            <xsd:attribute name="multiplicity" type="xsd:string" default="n"/>
            <xsd:attribute name="ordered" type="xsd:boolean" default="false"/>
        </xsd:complexType>
    </xsd:element>    

    <xsd:element name="EnumType">
        <xsd:complexType>
            <xsd:sequence>
                <xsd:element name="Enumeration" minOccurs="0" maxOccurs="unbounded"/>
            </xsd:sequence>
        </xsd:complexType>
    </xsd:element>
    <xsd:element name="Enumeration">
        <xsd:complexType>
            <xsd:attribute name="value" type="xsd:string" use="required"/>
        </xsd:complexType>
    </xsd:element>
</xsd:schema>

Most of it should be pretty straightforward. Here are a few notes:

  • Element.root: whether this element is a root element in an instance document in your domain. Multiple root elements in a domain are allowed.
  • Element.className: you can use this attribute to overwrite the default component class name derived from the element name.
  • Element.abstract: whether the corresponding component class is abstract.
  • Element.base: the name of the element whose corresponding domain component class serves as a base class.
  • Attribute.type: Java type, for example, "String", "boolean", "java.util.List<javax.xml.namespace.QName>".
  • Attribute.reference: use this if the attribute value references another component in the model.
    • If you want to reference a component that can be globally identified by QName, use "QName" for the corresponding Attribute.type;
    • For local reference, you can use "String" or "URI" as Attribute.type. Note that you can define the reference to be something like "elementA/elementB" for a multi-level reference.

Example:

Here is a schema instance describing an example domain:

<Content xmlns="http://xml.netbeans.org/schema/xamgen" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xml.netbeans.org/schema/xamgen xamgen.xsd">
    
    <Element name="composite" root="true">
        <Attribute name="name" type="String"/>
        <Attribute name="targetNamespace" type="java.net.URI"/>        
        <ChildElement name="component"/>
        <ChildElement name="connection"/>
    </Element>
    
    <Element name="component">
        <Attribute name="name" type="String"/>        
        <ChildElement name="provides"/>
        <ChildElement name="consumes"/>
        <ChildElement name="property"/>
    </Element>
    
    <Element name="endpoint" virtual="true">
        <Attribute name="name" type="String"/>        
        <ChildElement name="interface" multiplicity="1"/>
    </Element>
    
    <Element name="provides" base="endpoint"/>
    
    <Element name="consumes" base="endpoint"/>
    
    <Element name="property">
        <Attribute name="name" type="String"/>
        <Attribute name="value" type="String"/>
        <Attribute name="override" type="OverrideOptions"/>        
    </Element>
    
    <Element name="connection">
        <Attribute name="source" type="javax.xml.namespace.QName" reference="consumes"/>
        <Attribute name="target" type="javax.xml.namespace.QName" reference="provides"/>
    </Element>
              
    <Element name="interface" virtual="true"/>
    
    <Element name="interface.wsdl" className="InterfaceWSDL" base="interface">
        <Attribute name="interface" type="java.net.URI"/>
    </Element>
            
    <EnumType name="OverrideOptions">
        <Enumeration value="no"/>
        <Enumeration value="may"/>
        <Enumeration value="must"/>
    </EnumType>        
</Content>

Here is a little configuration file:

# XML file describing the domain data model.
domainDataModelFile=DemoDataModel.xml

# Namespace URI for the domain model
namespaceURI=http://www.yourorg.org/xmlns/foo/1.0

# Name of the domain model, such as "WSDL", "Schema", etc.
domainModelName=Foo

# Package name for the base model classes.
modelPackage=org.yourorg.foo.model

# Header file to be included in all the generated Java files.
headerFile=Header.txt

# Your name
author=jqian

The following shows the list of classes this XAM AutoGen Tool spits out when fed with the above domain schema and configuration file:


Usage:

Here is the steps you need to take to use this tool:

  1. Unzip this zip file
  2. Define your domain data model under the config directory.
  3. Configure a few properties in config/config.properties
  4. Run "java  -jar  XAMGenerator.jar  <output_dir>". (The output_dir could be your Java project or NetBeans module project's source directory.)
  5. In your project, add dependency to platform8/lib/org-openide-util.jar and ide9/modules/org-netbeans-modules-xml-xam.jar under your NetBeans directory.

That's it.

I am currently using this tool to auto generate the data model for SCA (Service Component Architecture). Here is the domain schema for the SCA Assembly Model. You can compare it with the original XSD Schemas. Based on my usage, I am going to keep improving this XAM AutoGen Tool . If you have any comments or suggestions, I am all ears.

P.S. I found this NetBeans Velocity plugin along the way. Very nice as a Velocity template viewer, but once you start editing the templates, it's getting a little crazy sometimes.

Comments:

I'm wondering if the XAM models and your generator can be used on stuff that is not completely described by xsd, such as the apache maven's pom.xml (http://maven.apache.org/xsd/maven-4.0.0.xsd)
elements under <configuration> are not defined and are specific to the maven plugin being used.

Posted by Milos Kleint on July 22, 2008 at 05:04 AM PDT #

Sure. The tool generates a class called xxxExtensibilityElementBase under model.spi which you can extend to define any extensibility elements not defined in your domain. You then need to define an ElementFactory for your extensibility elements and register it using META-INF/Services. I will consider adding this feature into a future version. Thanks!

Posted by Jun Qian on July 22, 2008 at 12:13 PM PDT #

The zip file in the original post has been updated for a bug fix.

Posted by Jun Qian on July 22, 2008 at 12:59 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed

Archives
Links
Referrers