Software Reality
Programming with
a dose of satire.

Site Map Search


Agile Development
 
Extreme Programming
 
Code Generation


Articles
Lifecycle
Design
Programming
Soapbox
Reviews
Cthulhu

Java Swing
Swing's greatest threat isn't SWT, it's Flash
Swing Survival Guide


 
Check out our ageing Reviews Section


Use Case Driven
Use Case Driven Object Modeling with UML: Theory and Practice
Get from use cases to working, maintainable source code. Examples use Spring Framework, JUnit and Enterprise Architect

Agile UML
Agile Development with ICONIX Process
A practical subset of agile development techniques, illustrated by example

Get Controversial!
Extreme Programming Refactored
Extreme Programming with a dose of satire
Available now:



Programming

Generate Enterprise Applications With JGenerator

By Dino Fancellu
December 7, 2003

Automated code generation has found its way into the mainstream programmer consciousness. In most enterprise projects these days, using a code generator can be a life-saver - though many teams are still unaware of the benefits that code generation offers.

This tutorial provides a practical, working example showing how a code generator can be used to generate a web application with a database back-end. For the purposes of this example, we're limiting ourselves to three database tables - hopefully this should be just enough to give you some of the flavour of what code generators can achieve.

Before we get started, you might find it useful to have a quick read through this article (introduces basic code generator concepts and principles).

The generator we're using for this example is JGenerator, by Javelin Software. This is a commercial product, but there's also a free version available for download, if you want to try out the example for yourself. The free version provides all the features of the full version, except it's limited to 5 database tables.

 

What is JGenerator?

JGenerator is a model driven code generator. It's "model driven" because the problem space is described in a business description language (BDL), and out comes code, be it source code, HTML, JSP, whatever. It’s as generic as that.

JGenerator comes with many code generation modules (called ‘writers’) and the ability to configure them as you will, as well as adding your own. The fact that all the source is available means that there are no black boxes, no jumped up wizards to deal with, you never have to fight with the generator to get it to cooperate, as you are always in charge.

Basically ‘readers’ pull in meta data, be it from the BDL or an existing database, to create an in memory dictionary, which is then output by various writers.

JGenerator process

This tutorial shows you how to get started with JGenerator in order to build your first domain specific API and framework. For further reading, perhaps in parallel to this article, go to www.javelinsoft.com/jgenerator or www.javelinsoft.com/jgenerator/guide/

 

Our Example Web-Based Message Forum

In this article we’ll build a simple message forum modelled loosely on the www.SoftwareReality.com forums. The basic premise behind the SR forums is that each article can have a simple guestbook-style forum attached to the end. Simplicity is key - this isn't a "community" website like JavaLobby, TheServerSide or Slashdot; but it's also useful for people to be able to post feedback to individual articles.

This example is kept simple to aid understanding, although in reality most systems that need a code generator would have at least 6 tables.

Here's the ER diagram for our "dog-simple" message forum example:

Message forum ER diagram

 

Getting Started

Assuming you’ve got JGen installed, the first thing to do is create a directory for this project, say c:\forum.

Under that we’ll create a directory called 'web', which is the root for this project when mounted by the web container.

Under web, we’ll create:

web-inf/classes

which is a good place for our classes to live.

Then we’ll create a package for this project. For this project we’ll be using com.softwarereality.forum

Then we create a BDL (Business Definition Language) file for the project in c:\forum\web-inf\classes and set the package and the root directory of the package hierarchy, as well as the context name (name of the project as a whole) so that the writers know where to output things.

We’ll call the BDL file forum.properties.

There's a link to the finished BDL/forum.properties file at the end of this article. In the course of the article, though, we'll go through the process of creating the BDL file step by step. Here's the first part:

$PACKAGE=com.softwarereality.forum
$CONTEXTNAME=Forum
$ROOT=c:/forum/web

default.pathName=$ROOT/web-inf/classes
classes.pathName=$ROOT/web-inf/classes

The dollar signs are for macros we create that can be used elsewhere. For example, we've defined a macro called $ROOT, with the value c:/forum/web. Now wherever we put $ROOT, it'll be replaced with c:/forum/web at generation time.

 

The defaultdbl File

The BDL files aren’t very big, as when we parse them we first pull in a global defaultdbl.properties file which defines most of the useful defaults for a project.

For example in the defaultdbl we have $GENERATOR set up to point to the generator root package, as used below:

$GENERATOR=com.javelin.generator

 

Setting up the Readers and Writers

As you might expect, the readers read in meta-data (e.g. BDL files), and the writers write these out in various formats (Java files, XML, SQL, JSP and so on). JGenerator's modular nature means that readers and writers can be used interchangeably. Want to generate EJBs instead of 2-tier beans? Just swap in a different set of writers. This is achieved via an intermediate data dictionary which JGenerator holds in memory whilst generating.

Anyway, here's how we define which readers and writers we want in our forum.properties BDL file:

#READ FROM DATABASE
reader.0=$GENERATOR.properties.PropertiesReader

#GENERATE THESE FILES

writer.0=$GENERATOR.AggregateWriter beans
writer.1=$GENERATOR.properties.SortedPropertiesWriter
writer.2=$GENERATOR.AggregateWriter jdbc

The PropertiesReader simply reads properties from the BDL to create the dictionary.

The 'AggregateWriter beans' line calls the following group of writers in the defaultdbl:

beans.writer.0=$GENERATOR.beans.BeanInterfaceWriter
beans.writer.1=$GENERATOR.beans.BeanClassWriter
beans.writer.2=$GENERATOR.beans.LazyBeanClassWriter
beans.writer.3=$GENERATOR.beans.KeyClassWriter
beans.writer.4=$GENERATOR.beans.ObjectClassWriter
beans.writer.5=$GENERATOR.beans.HomeInterfaceWriter
beans.writer.6=$GENERATOR.beans.SessionInterfaceWriter
beans.writer.7=$GENERATOR.beans.EnumeratedTypeWriter
beans.writer.8=$GENERATOR.beans.ValidatorClassWriter
beans.writer.9=$GENERATOR.beans.BeanInfoWriter
beans.writer.10=$GENERATOR.beans.ClassContextWriter

You can create your own groups of writers easily, just say:

xxx.writer.0=blahblahWriter
xxx.writer.1=etcetcWriter

Then call

writer.x=$GENERATOR.AggregateWriter xxx

SortedPropertiesWriter (which is declared in the above snippet from forum.properties) is simply a little debug writer that emits the parsed properties in alphabetical order. It isn't needed for runtime, but it's nice to see what is actually acted upon, as complex macro substitution and defaultdbl inheritance can get a little confusing.

In fact, it’s a tiny little writer, and even though this tutorial doesn’t focus on how to create your own writers, here’s an example of how small, yet useful they can be:

public class SortedPropertiesWriter extends AbstractWriter
{
    public void write() throws Exception
    {
        TreeMap sortedMap = new TreeMap( properties );
        try
        {
            openWriter( properties.getPathName( "properties" ),
                "", source+ "-sorted", "properties");
            Map.Entry entry = null;
            for( Iterator entries = sortedMap.entrySet().iterator(); entries.hasNext(); )
            {
                entry = (Map.Entry)entries.next();
                print( entry.getKey().toString() );
                print( "=" );
                print( entry.getValue().toString() );
                print( "\n" );
            }
        }
        finally { flushAndClose(); }
    }
}

 

JDBC Writer - Persistence

The AggregateWriter called 'jdbc' does as you expect, calls the JDBC group of writers:

jdbc.writer.0=$GENERATOR.jdbc.JdbcHomeClassWriter
jdbc.writer.1=$GENERATOR.jdbc.JdbcSessionClassWriter
jdbc.writer.2=$GENERATOR.sql.SQLWriter
jdbc.writer.3=$GENERATOR.sql.SQLDeleteDataWriter
jdbc.writer.4=$GENERATOR.sql.SQLDropObjectsWriter
jdbc.writer.5=$GENERATOR.sql.SQLKeyWriter
jdbc.writer.6=$GENERATOR.jdbc.JdbcContextPropertiesWriter

Because of this process, if you suddenly decided that you needed another class emitted when processing JDBC then you could easily say:

jdbc.writer.7=SomeOtherJdbcWriter

in your forum.properties, and the jdbc group would now be altered to include this new member.

We also need to define the database vendor, so that the generated SQL code is correct:

target=forum
forum.vendor=ms

We could similarly tell the generator to output to oracle|ms|Sybase|db2|interbase, for example:

forum.vendor=oracle

Next we need to tell the JDBC layer where to pick up the database info:

JdbcContextProperties.code.0=com.javelin.util.jdbc.JPool.setRelativePath( "jdbc.properties" );
JdbcContextProperties.code.1=com.javelin.util.jdbc.JPool.setRelativeClass( $PACKAGE.jdbc.JdbcContextProperties.class );

This tells the generator to look for the file jdbc.properties. This file would contain something like:

forum.driver=com.inet.tds.TdsDriver
forum.url=jdbc:inetdae:localhost:6129?database=forum&sql7=true
forum.user=forum
forum.password=forum
forum.transactionIsolation=TRANSACTION_READ_COMMITTED
forum.readOnly=false
forum.autoCommit=false
forum.maxConnections=2
forum.minConnections=1
forum.waitTimeout=5000
forum.keyBatchSize=10
forum.usageLogged=false
forum.debugTransactions=false
forum.debugConnections=false
forum.dateFormat=dd/MM/yyyy
forum.timeFormat=kk:mm:ss
forum.timeStampFormat=yyyy-MM-dd-kk.mm.ss.SSS
forum.currentDate=GetDate()
forum.currentTime=GetDate()
forum.currentTimeStamp=GetDate()
#forum.debugDriver=true

Notice that each line starts with "forum". This is because we define a database via a source (in this case "forum"), which is detailed in the jdbc.properties file. This allows us to easily switch database details without always having to recompile.

 

Running The Generator

Now we're ready to run JGenerator on the BDL file that we've created so far. There are several ways that JGenerator can be run, but the easiest way is simply to create a class like the following, and run that from within your IDE:

import com.javelin.generator.*;

/**
* Generates the Forum JSP/Servlet app.
*/
public class GenerateForum
{
    public static void main(String[] args)
    {
        JGenerator.run( "c:/forum/web/WEB-INF/classes/forum.properties" );
    }
}

JGenerator also has its own GUI for generating code and analyzing the results. To run this, run the com.javelin.generator.JGeneratorIDE class. Follow the on-screen prompts after that - it's pretty straightforward.

 

When JGenerator is invoked using our example BDL file, it cranks out classes in the com.softwarereality.forum package. In the same process, JGen creates the SQL in the sql directory C:/forum/web/WEB-INF/classes/sql

Like most things this can be changed by defining an sql.pathName property. As we have none, JGen uses the default.pathName, defined above.

The SQL files are used to create a suitable database, delete all data, drop all tables, etc.

e.g for SQL Server, the following SQL is generated:

IF( NOT EXISTS( SELECT * FROM sysobjects WHERE NAME='Forum' AND type='U' ) )
CREATE TABLE Forum(
forumID INTEGER PRIMARY KEY,
forumName VARCHAR(50) NULL,
forumSection VARCHAR(50) NULL,
forumDescription VARCHAR(255) NULL,
url VARCHAR(255) NULL,
createDateTime DATETIME NOT NULL)
GO

IF( NOT EXISTS( SELECT * FROM sysobjects WHERE NAME='Message' AND type='U' ) )
CREATE TABLE Message(
msgID INTEGER PRIMARY KEY,
forumID INTEGER NOT NULL REFERENCES Forum,
forumUser VARCHAR(255) NULL,
location VARCHAR(100) NULL,
msgTitle VARCHAR(255) NULL,
msgBody VARCHAR(1024) NULL,
ip VARCHAR(16) NULL,
createDateTime DATETIME NOT NULL)
GO

IF( NOT EXISTS( SELECT * FROM sysindexes SI, sysobjects SO WHERE SI.NAME='forum' AND SI.id=SO.id AND SO.name = 'Message' and SO.type='U' ) )
CREATE INDEX forum ON Message (forumID)
GO

CREATE VIEW LatestMessageForum AS
SELECT top 100 percent forumID, MAX(createDateTime) as lastMessageDateTime FROM Message GROUP BY forumID ORDER BY 2 DESC
GO

As for the Java classes we get bean interfaces, implementation classes, JDBC sessions/homes, lazy (caching) classes, etc.

All the generated forum code is automatically Javadoc'd.

You might also be interested in the Javadoc for our FPML project (a huge project with loads of generated classes).

 

Generating JSP

Next, we'll show how to add some writers for creating JSP and JSight files. The JSP writers emit JSP fragments for UI use. These are particularly easy to drag/drop into DreamWeaver, because the writers create DreamWeaver library elements.

JSight is an automated Admin web application that comes with JGenerator. It allows you to create, edit, delete, sort and query data. Here's an example for Northwind.


In defaultdbl:

jsp.writer.0=$GENERATOR.jsp.JspFragmentWriter
jsp.writer.1=$GENERATOR.jsp.HolderClassWriter
jsp.writer.2=$GENERATOR.jsp.JSightWriter

And in forum.properties:

writer.3=$GENERATOR.AggregateWriter jsp

For example to show the http://www.softwarereality.com/forums.jsp page:

<%!
    SimpleDateFormat sdf=new SimpleDateFormat("MMM dd, yyyy 'at' hh:mm:ss");
%>
<%
    ForumContext context=ForumContext.getDefault();
    LatestMessageForumSession lmfs=context.getLatestMessageForumSession();
    Iterator forums=lmfs.findByAll();

    while (forums.hasNext())
    {
        LatestMessageForum lmf=(LatestMessageForum)forums.next();
        Forum forum=lmf.getForum();
%>

<TR>
<TD width="23%"><A class=bodyEmpLink href="<%=forum.getUrl()%>"><%=forum.getForumName()%></A><BR>
    <SPAN class=bodyFootnote><%=forum.getForumSection()%></SPAN></TD>
        <TD width=*><SPAN class=bodyQuote2>
        <%=forum.getForumDescription()%>
    </SPAN>
</TD>
<TD width=70>
     <SPAN class=whatsNewDate>
    <%=sdf.format(lmf.getLastMessageDateTime())%></SPAN></TD>
</TR>
<%}%>

Lots more info in the JGenerator guide.

An important point about JGenerator is that it doesn't have to emit just Java. That's its current focus; however, there is nothing to stop users from creating their own writers, but using the same readers and dictionary.

JGenerator also has a set of writers for generating EJBs. However, EJB sucks, so we tend to avoid wherever possible. With JGen it's easy to create your own engines instead of relying on other peoples' black boxes. However if you want to do EJB, go ahead.

Finally, here's the finished BDL for our dog-simple forum example.

 

Conclusion

With this tutorial, I've aimed to provide some insight into how useful code generators can be - and how they can generate reams of source code (and supporting files, e.g. Javadoc) with very little effort.

This was primarily a tutorial about using JGenerator, so as you'd expect, I talked mostly about JGenerator. Matt's article gives some links to other code generators, but my feeling (having written part of JGenerator myself) is that JGen provides a number of advantages. In particular, we give you all the source code (even the free version includes some source to play around with). We've also encapsulated many years of consultancy experience in the code that the product generates, i.e. it doesn't generate inefficient or "beginner" code.

We also use JGenerator on our own consultancy projects at Javelinsoft, so it wasn't created inside an ivory tower to an idealistic design (unlike the EJB spec, for example). While I'm at it, we don't default to generating EJBs just because that's what everybody else is doing (ride that bandwagon!) Instead, we generate whatever is most appropriate for getting the job done.


About the author:
Dino Fancellu is the Lead Technologist at Javelin Software Ltd., a UK-based Java consultancy firm. He has worked with Java since it was in Beta, and has been a commercial programmer since 1986, specialising in complex enterprise/financial systems. More recently at Javelin, his work has taken him to dot-coms such as LastMinute.com, Sapient/AssertaHome and PensionsBusiness.com.

<< Back to Programming

<< Back to Software Reality



All trademarks and copyrights on this page are owned by their respective owners.
Stories and articles are owned by the original author.
All the rest Copyright © 1998-2008 Matt Stephens. ALL RIGHTS RESERVED.