Creating a Jakarta Commons SQL datamodel using XmlBeans and Velocity

Philip Mark Donaghy

1 About Vxsdb

Vxsdb is a sample XmlBeans application which derives a Jakarta Commons SQL data model from an xml schema. It was conceived from things I have learned from using XmlBeans and talking to people at the ASF conference.

Vxsdb uses Jakarta Velocity as its templating engine. At runtime a SchemaTypeSystem XmlBeans object is created from compiling an xml schema. This object is placed in a Velocity context. A template processes the objects global elements, global types and generates a Jakarta Commons SQL xml datamodel descriptor of tables and columns.

1.1 Requirements

Java 1.5

1.2 Getting Started

The basic utilization compiles the sample and produces a database schema for a given xml schema.

Execute the command “ant”

This compiles the sample.

Execute the command “ant -f run.xml”

This creates the Jakarta Commons SQL datamodel.xml and creates the create.sql ddl.

1.3 Configuration

Vxsdb can be configured by modifying the command line parameters to specify another template, output or xml schema.

ant -f run.xml -Dtemplate=datamodel.vm -Doutput=database.xml -Dschema=easypo.xsd

1.4 Generating database schema

Included in the sample run.xml is an Ant target which generates the ddl used to create the database tables. The default database is PostgreSQL.

ant -f run.xml

To change the target database use,

ant -f run.xml -DtargetDatabase=oracle

1.5 Determining Tables, Columns, and Table relationships

Tables are created from all global elements and most global types.

For each schema property the sample application determines if it is a column, an exported key (foreign key to another table), or an imported foreign key (another entity who's table contains a foreign back to this entity).

If a schema property (attribute or element) is a simple type without restriction a column is created and one of the following Commons SQL values is assigned to the property. DATE, DECIMAL, FLOAT, INTEGER, TIME, TIMESTAMP, or VARCHAR.

If a schema property is a simple type with a restriction then a column is created and one of the above values is assigned in addition to a size attribute.

If a schema property is a complex type with no maxOccurs or minOccurs (meaning that it is required) then a foreign key column is entered into the database table descriptor and the required attribute is set to true.

If a schema property is a complex type with maxOccurs equal to one then a foreign key column is entered into the database table.

If a schema property is a complex type with maxOccurs greater than one or unbounded then a imported key (foreign key from this type's table) is required. This presents a problem. The Velocity template may have already encountered this complex type and created its table descriptor without even knowing about this relationship. This is known as the Imported Key Question.

1.6 Solving the Imported Key Question

Iterate the SchemaTypeSystem in the Java class to determine imported keys.

Cliff told me that ms has a solution to this which may be included in the xml schema.

1.7 Additional ideas

Combining XmlBeans and Velocity templates can result in other interesting xml schema related technologies.

Using a similar approach to this sample XmlBeans and Velocity can harness the xml schema and generate documentation, a relational database model, or even data access objects.

1.8 Sample datamodel.xml

<?xml version="1.0" encoding="UTF-8"?>


<database name="sample">


<!-- Global Elements -->


<table name="PURCHASE_ORDER">


<column name="PK" type="INTEGER" required="true" primaryKey="true"/>

<!-- T=customer@http://openuri.org/easypo -->

<column name="FK_CUSTOMER" required="true" type="INTEGER" />

<foreign-key foreignTable="CUSTOMER">

<reference local="FK_CUSTOMER" foreign="PK"/>

</foreign-key>

<!-- T=dateTime@http://www.w3.org/2001/XMLSchema -->

<column name="DATE" type="TIMESTAMP" />

<!-- Foreign key imported from table named LINE_ITEM -->

<!-- T=shipper@http://openuri.org/easypo -->

<column name="FK_SHIPPER" type="INTEGER" />

<foreign-key foreignTable="SHIPPER">

<reference local="FK_SHIPPER" foreign="PK"/>

</foreign-key>

</table>


<!-- Global Types -->


<table name="CUSTOMER">


<column name="PK" type="INTEGER" required="true" primaryKey="true"/>

<!-- T=string@http://www.w3.org/2001/XMLSchema -->

<column name="NAME" type="VARCHAR" />

<!-- T=string@http://www.w3.org/2001/XMLSchema -->

<column name="ADDRESS" type="VARCHAR" />

<!-- T=int@http://www.w3.org/2001/XMLSchema -->

<column name="AGE" type="TINYINT" />

<!-- T=int@http://www.w3.org/2001/XMLSchema -->

<column name="MOO" type="TINYINT" />

<!-- T=int@http://www.w3.org/2001/XMLSchema -->

<column name="POO" type="TINYINT" />

</table>


<table name="LINE_ITEM">


<column name="PK" type="INTEGER" required="true" primaryKey="true"/>

<!-- T=string@http://www.w3.org/2001/XMLSchema -->

<column name="DESCRIPTION" type="VARCHAR" />

<!-- T=decimal@http://www.w3.org/2001/XMLSchema -->

<column name="PER_UNIT_OUNCES" type="DECIMAL" />

<!-- T=decimal@http://www.w3.org/2001/XMLSchema -->

<column name="PRICE" type="DECIMAL" />

<!-- T=integer@http://www.w3.org/2001/XMLSchema -->

<column name="QUANTITY" type="INTEGER" />

<!-- Exported key -->

<column name="FK_PURCHASE_ORDER" required="true" type="INTEGER" />

<foreign-key foreignTable="PURCHASE_ORDER">

<reference local="FK_PURCHASE_ORDER" foreign="PK"/>

</foreign-key>

</table>


<table name="SHIPPER">


<column name="PK" type="INTEGER" required="true" primaryKey="true"/>

<!-- T=string@http://www.w3.org/2001/XMLSchema -->

<column name="NAME" type="VARCHAR" />

<!-- T=decimal@http://www.w3.org/2001/XMLSchema -->

<column name="PER_OUNCE_RATE" type="DECIMAL" />

</table>


</database>

1.9 Sample create.sql

drop table SHIPPER;


drop table LINE_ITEM;


drop table CUSTOMER;


drop table PURCHASE_ORDER;


-- -----------------------------------------------------------------------

-- PURCHASE_ORDER

-- -----------------------------------------------------------------------


create table PURCHASE_ORDER

(

PK INTEGER NOT NULL ,

FK_CUSTOMER INTEGER NOT NULL ,

DATE TIMESTAMP NULL ,

FK_SHIPPER INTEGER NULL ,

PRIMARY KEY (PK)

);


ALTER TABLE PURCHASE_ORDER

ADD CONSTRAINT PURCHASE_ORDER_FK_1 FOREIGN KEY (FK_CUSTOMER)

REFERENCES CUSTOMER (PK)

;


ALTER TABLE PURCHASE_ORDER

ADD CONSTRAINT PURCHASE_ORDER_FK_2 FOREIGN KEY (FK_SHIPPER)

REFERENCES SHIPPER (PK)

;


-- -----------------------------------------------------------------------

-- CUSTOMER

-- -----------------------------------------------------------------------


create table CUSTOMER

(

PK INTEGER NOT NULL ,

NAME VARCHAR NULL ,

ADDRESS VARCHAR NULL ,

AGE TINYINT NULL ,

MOO TINYINT NULL ,

POO TINYINT NULL ,

PRIMARY KEY (PK)

);


-- -----------------------------------------------------------------------

-- LINE_ITEM

-- -----------------------------------------------------------------------


create table LINE_ITEM

(

PK INTEGER NOT NULL ,

DESCRIPTION VARCHAR NULL ,

PER_UNIT_OUNCES DECIMAL NULL ,

PRICE DECIMAL NULL ,

QUANTITY INTEGER NULL ,

FK_PURCHASE_ORDER INTEGER NOT NULL ,

PRIMARY KEY (PK)

);


ALTER TABLE LINE_ITEM

ADD CONSTRAINT LINE_ITEM_FK_1 FOREIGN KEY (FK_PURCHASE_ORDER)

REFERENCES PURCHASE_ORDER (PK)

;


-- -----------------------------------------------------------------------

-- SHIPPER

-- -----------------------------------------------------------------------


create table SHIPPER

(

PK INTEGER NOT NULL ,

NAME VARCHAR NULL ,

PER_OUNCE_RATE DECIMAL NULL ,

PRIMARY KEY (PK)

);