JRF Version 2.0 Release Notes

Overview

This version includes modifications that overlap four broad categories: functionality improvements, speed enhancements, architectural changes, and code generation. The architectural changes, although extensive, do not violate the original design paradigm of JRF, which centers around PersistentObject instances and the AbstractDomain sub-classes that manipulate these instances. However, the architectural changes included in this release will require application code modifications. The most significant change to JRF architecturally is the elimination of the JDBCHelper class from the framework. The functionality and much of the code of JDBCHelper still exists in smaller, task-specific classes. If an application does not make extensive use of direct JDBCHelper calls in AbstractDomain contexts, code modifications will be fairly minimal. See Modifying Existing Code with JDBCHelper Arguments for complete details on how to modify existing JRF applications.

Code generation is no longer an ancillary piece of JRF, it is now a central part of the framework. While it is certainly possible to hand-write most of your PersistentObjects and AbstractDomains, you no longer have to do this. Two code generators are provided, one for generating base direct table-to-object mappings and one for generating composite objects that may include detail tables, join columns or both. These generators are extensible in several ways. First, since the code generators read XML files for the information, you can add more values in the DTD (Document Type Definition) files to expand the functionality. You can also sub-class the code generation entity handling classes for table, column and so forth. Finally, you can sub-class the generators themselves if you want even more control.

A short term "todo" is to write a program that will read the database metadata and generate a skeletal XML file that may be used by the base generator.

Functionality Improvements

Speed Enhancements

Considerable effort was undertaken to make JRF an effective server utility for high speed transaction processing. The author of these enhancements gained considerable experience in the past by suffering through the task of forcing server applications to meet sub-second response times. The ideas from that experience have been applied to the JRF framework.

Changes to enhance speed include:

Architectural Changes

The Architectural changes fall into two areas: connection management overhaul and the introduction of embedded PersistentObject support in AbtractDomain.

Connection Management Overhaul

The management of connections in the JRF framework is considerably different from previous versions. The most dramatic change is the elimination of the JDBCHelper class. JDBCHelper is bit of a catch-all, non-object-oriented utility class. It handled connections, pooling (with JDBCHelperPool), statement execution and result set management. While useful for general utilities, it becomes up a bit unwieldy when used with AbstractDomain leads to such limitations as lack of handling multiple result sets.

The SQL management and database connection functionality of JDBCHelper has been broken into smaller classes and pooling now is reserved only to implementations of javax.sql.DataSource or javax.sql.ConnectionPoolDataSource. The JRF framework now contains a default implementation of javax.sql.ConnectionPoolDataSource. Applications may use this implementation or one of the many commercial or open source versions that exist. The design approach for the new connection management was to center on the needs of AbstractDomain. A class called JRFConnection encapsulates connection information in JRF in this version. An AbstractDomain sub-class will contain a single instance of this class. Unlike previous versions, applications have no direct control over the setting the connection handling for AbstractDomains. Rather, applications have indirect control over the connection mechanism through one of three ways (this text is replicated in the javadocs for AbstractDomain):

  1. An application does not call any method on the domain to set up connection handling. When a connection to the database is required by the domain, the no-argument version JRFConnectionFactory.create() will instantiate a JRFConnection instance based on the JRF system Properties instance. This way of handling connections should suit the majority of applications.
  2. An application explicitly sets the connection Properties instance to use for creating a JRFConnection instance via a call to AbstractDomain.setProperties(). The domain will then use the JRFConnectionFactory.create(Properties) version to obtain a JRFConnection instance. This factory method will use whatever properties exist in the supplied argument and fallback on the JRF system Properties for any property value it does not find. See the documentation for JRFConnectionFactory for more details on this behavior.
  3. An application may set a DataSource instance to use via a call to setDataSource. This methodology will suit applications where properties to instantiate a DataSource are not known at program start up. For example, an application server may receive a message with the requisite parameters for obtaining a DataSource through JNDI.

Transaction Management

The handling of transactions in this version is considerably different from previous versions. For a single AbstractDomain instance the transaction handling is automatic (much in the way it was before except that now transactions including embedded objects are supported - see Embedded Persistent Objects). For applications that wish to support transactions using multiple domains, the approach has been streamlined. Previously, a multiple domain manual transaction was implemented rather indirectly:

	JDBCHelper helper;
	ADomain domainA;
	BDomain domainB;
	.
	.
	domainA.setJDBCHelper(helper);
	domainB.setJDBCHelper(helper);
	try {
		domainA.beginTransaction(); // Both use same JDBCHelper
		.
		// set some values.
		domainA.save(...);
		//etc.
	}
	catch ( ....
	//etc.
The approach for multiple domains for this version is to use a class called JRFWriteTransacton:
	ADomain domainA ;
	BDomain domainB;
	JRFWriteTransaction wt = new JRFWriteTransaction();
	.
	.
	MyConstructor() {
		wt.addDomain(domainA);
		wt.addDomain(domainB);
		.
		.
	}
	.
	.
	.
	wt.beginTransaction();
	try {
		// set some values.
		domainA.save(...);
		//etc.
	}
	catch (Exception ex) {
		wt.abortTransaction();
	}
	wt.endTransaction();		
In the previous version both domains had to use the same connection. In this version connection management is of no concern to the application; domainA could point to Sybase and domainB could point to Oracle.

Embedded Persistent Objects

In previous versions, support for embedded PersistentObjects in AbstractDomain was implicit. A user could populate embedded fields and objects by implementing the suite of protected methods that have no implementation in the base class ( (e.g. preSave(),postValidate(),postFind()).

This version now explicitly handles the "pieces". The protected methods are still supported, but an application can move away from these methods to handle embedded PersistentObjects by implementing a class called EmbeddedPersistentObjectHandler. Since a single save operation may update many embedded objects, a new PersistentState of DeletedPersistentState was added. By using this state, an application can pass a complex object graph back to the domain to have only portions of the object deleted. See EmbeddedPersistentObjectHandler and AbstractDomain javadocs for more information.

Use the code generator EmbeddedGenerator to generate the embedded objects.

Code Generation

Complete documentation for the code generators is forthcoming. The following preliminary guide is provided until a formal document can be written.

Basic Overview

Setting Up

Abstract Base Generator

All generators sub-class org.vmguys.appgen.Generator. Examine the protected constructor in this file which does the following: There is a static class name variable in this base file so that only one "main()" method is required for any generator (pattern of evocation is always "java X propertiesfile xmlfile")

Template Basics

One of the primitive concepts of the code generator is the use of templates with markers tokens to distinguish the variables. For example:
private static final String classTemplate = 
	"public class $classNameObj$ extends PersistentObject $interfaces$ {\n"+
	.
	.

The two tokens enclosed in the "$" would be replaced in the generated code might be:

	public class Name extends PersistentObject implements PersonEntity {
	.
	.

Generator Basic Concepts

For each XML element defined in the DTD, there is a corresponding java class that subclasses the abstract class org.vmguys.appgen.SourceGenXMLEntity. At a minimum, this class contains a hash map of all element attributes. Sub-classes may also have some setter methods if desired. To handle these classes, a meta data class exists called org.vmguys.appgen.SourceGenXMLEntityMetaData. It is in this class the all the magic happens. The generators build a hierarchy of these meta classes and call a single static method to map the contents of the parsed XML document into a tree of SourceGenXMLEntity nodes. Examine the static method createEntityTreeFromXML(). For each element, the hash map of element attributes is filled, any setter methods are invoked (XMLReadHandler.elementToObject()) and finally, an abstract method implementation (resolveImpliedKeys()) is called to allow the SourceGenXMLEntity sub-class to sort out any issues with "implied" elements.

To see a simple example of the tree creation process, examine the constructor of BaseGenerator.java. For a complex example, examine the constructor of EmbeddedGenerator.java. One of the key concepts here is that the constructors will instantiate the SourceGenXMLEntity sub-classes for table, column and so forth from names specified in the properties files. If no names are specified the default base versions are used. You can sub-class these base versions to perform all sorts of tricks specific to an application.

Sub-classing the Generators

In combination with sub-classing the XMLEntity classes, you sub-class both generators and implement the protected methods to provide additional functionality. Examine the protected methods and their context in both BaseGenerator.java and EmbeddedGenerator.java.

Common Techiques

One of the more common techniques to use in the code generator is to generate implementations of methods based on whether the objects implement certain interfaces. Comma-separated lists of interfaces are part of the base DTD. If you sub-class the generator, you may accomplish this generation with some like:

	/** Allows a sub-class to add additional code for embedded object AbstractDomain.
	 * @param current current CompositeObjectXMLEntity.
	* @return any additional code to place at the end of generated AbstractDomain.
	*/
	public String getAdditionalDomainCode(CompositeObjectXMLEntity current) {
		String interfaces = current.getTransientKeys().get("interfaces");
		if (interfaces.indexOf("PersonEntity") != -1) { 
			return "	public void preValidate( ... 
		}
	}
The above example suggests a different approach providing implementations of the protected AbstractDomain methods. Rather than manually implementing these methods, let the code generator handle this task.

Modifying Existing Code with JDBCHelper Arguments

  1. postFind(). Modify all implementations to the following signature:
    		protected void postFind(PersistentObject aPO,JRFResultSet baseFindResultSet)
    	
    JRFResultSet has all the methods that JDBCHelper has to obtain result set data so all you need to do is change the signature. If you have to execute a query here use:
    		JRFResultSet other = this.getJRFConnection().getStatementExecuter().executeQuery(" ....");
    	
  2. preValidatate(),preSave() etc. Modify all with the following signatures:
    	protected void preValidate(PersistentObject aPO, StatementExecuter stmtExecuter)
    	protected void preSave(PersistentObject aPO, StatementExecuter stmtExecuter)
    	protected void postSave(PersistentObject aPO, StatementExecuter stmtExecuter)
    	protected void preDelete(PersistentObject aPO, StatementExecuter stmtExecuter)
    	protected void postDelete(PersistentObject aPO, StatementExecuter stmtExecuter)
    	
    You can use StatementExecuter to process any SQL statement. Furthermore, you many process as many result sets as you choose.
  3. Eliminate all calls to getJDBCHelper and setJDBCHelper; they will generate runtime exceptions.
  4. protected int executeSQLUpdate(String sql, JDBCHelper aJDBCHelper) will still work but the helper argument will be ignored.
  5. protected void executeSQLQuery(String sql, RowHandler aRowHandler, JDBCHelper aJDBCHelper) will work but the helper argument will be ignored.
  6. All of the find...(X, JDBCHelper) methods will work but the helper argument will be ignored.
  7. All of overloads of these methods with JDBCHelper will work but the JDBCHelper argument will be ignored: save(),delete(),validate(),findLong(),findInteger(),findTimestamp().
  8. All versions of beginTransaction and endTransaction now do nothing.
  9. If you have implemented the protected RowHandler interface, the signature for the single method is now:
    	public void handleRow(JRFResultSet resultSet)
    	

Miscellaneous Code Changes

  1. Modify all timestamped optimistic lock columns from TimestampColumnSpec to TimestampColumnSpecDbGen.
  2. AbstractStaticDomain is now deprecated. The same functionality exists by using AbstractDomain and calling setCacheAll(true).
  3. Since there is no longer a single instance of DatabasePolicy, the method public static DatabasePolicy getDatabasePolicy() is no longer supported. If you need to obtain the default DatabasePolicy instance, do the following:
    	JRFConnection conn = JRFConnectionFactory.create();
    	DatabasePolicy policy = conn.getDatabasePolicy();
    	
  4. Encoding compound primary keys now uses StreamTokenizer instead of StringTokenizer to format the encoded string. If your code manually examines this key, you will have to make changes:
    	Old version:  1|MFG
    	New version:  1|"MFG"
    	
    StreamTokenizer is used so that quotes and bars ('|') may be embedded in a String key value.

Short-term TO DO List

  1. Fix incorrect and incomplete DatabasePolicy implementation methods.
  2. Create new DBtype-Readme (e.g. postgres-readMe.txt) files for each database type supported.
  3. Expand CompositeTEST.java with more tests.
  4. Test EJB example directory.
  5. Clean up documentation in jrf.properties.
  6. Update existing user guide documentation.
  7. Generate a skeletal XML file from reading the database schema.
  8. Expand CacheTEST.java with more tests.
  9. Full testing and potential changes to BLOB and CLOB column specifications.
  10. Clean-up of all javadoc. Minimally, the following needs to be done:
  11. GenerateDatabase has been successfully run on only Postgres and MySql. Other databases need to be tested. DatabasePolicy methods for obtaining create index formats may need to be abandoned.
  12. Documentation for XML creation.
  13. Add policy method to get SQL for dropping sequences.