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)

);