For Tomcat Developers, Aspire Comes in a JAR

Aspire.jar is a free, open source, .jar file that can be used for declarative data access, configuration, logging, and factory services needs. For Java developers who are continuing to adopt Tomcat as their primary development platform, this .jar file could save lot of time, while providing a highly flexible data architecture. Aspire.jar is only about 500K and unintrusive in your applications. Installation is as simple as downloading and including the .jar file in the appropriate classpath. You can start putting the .jar file to good use as soon as you finish this article.

So what can you do with Aspire?

Use it for a very flexible data access library: Imagine retrieving and updating data without ever thinking about connections, databases, commits, rollbacks, etc. Imagine having all of your SQL externalized into config files so that it stays out of your pure Java code. Imagine swapping your data sources between flat files, SQL, stored procedures, CICS transactions, etc. Imagine also not changing your client code a bit while you are doing this. Imagine transactional capability across multiple data access components. Aspire provides all of this in a very small package.

Use it to dynamically instantiate run time objects: Aspire provides a mechanism that allows you to instantiate objects from your config files. The factory service allows you to have single-instance and multi-instance support for your instantiated objects. This is a good compromise between object pools and programmer convenience.

Use it for business components and their declarative composition: Data access components are a special case of an Aspire component/part called a business component. You can write specialized business components that are similar to session beans, but that can be run either on the Tomcat tier or the EJB tier (via a transactional bridge). You can combine business components, including the prebuilt ones, into higher-level composite components, allowing process flow orchestration.

Use it for configuration and logging: All of the above are accomplished via configuration files. Aspire supports multiple config files that get included in a master config file. This allows for multiple modules and multiple developers to keep configuration where it belongs instead of pouring all of the configuration into one file. You can include config files, the format of which can be either XML or classic properties. That means you can have one config file in properties format and one file in XML format, but programmers will access them using the same simplified API. Aspire provides a logging API with swappable logging implementations. Thus, If you use Log4J, for instance, you can plug Log4J into Aspire so that all of Aspire's logging will go through Log4J, providing better debugging capabilities.

Getting Started

aspire_r2.0_b16.3_jsdk21.jaraspire_r2.0_b16.3_src.zipaspire.properties
The original text of this article
Open free license to use the .jar file

You need two files to get started: Aspire's .jar file and a master properties file that goes with it. Both of these files are included in the .zip file. Cut and paste the aspire.properties file to create the needed properties file out of it.

Aspire's .jar file is typically named aspire_r2.0_b16.3_jsdk21.jar. This indicates that Aspire is in release 2.0, build 16.3, using the Servlets 2.1 standard, and is backward compatible with all future releases of the Servlets API.

Aspire's properties file is usually named aspire.properties and is typically placed under the
/properties subdirectory of webapp root. This properties file helps you to obtain configuration information, logging information, factory objects, etc.,
including the externalized data access such as SQL, stored procedures, and
others.

Initialize Aspire in Tomcat

To use Aspire's .jar in your Java code, you first initialize Aspire with its
properties file. Although this can be done programmatically, Aspire provides a startup servlet
called AppInitServlet1 for initializing Aspire under Tomcat.

Here is the portion of the web.xml file that will allow you to initialize Aspire under Tomcat environment:

AppInitServlet1 takes a parameter named AppConfigFilename. The value of this parameter points to a config filename. This filename can be absolute or relative to the webapp root directory. Follow the appropriate file-naming conventions for your platform (Unix or NT). Depending on the version of Tomcat that you are running, find a way to have this initialization servlet configured.

Aspire looks for aspire.properties in the properties subdirectory. A sample aspire.properties file is attached at the end of this document. For the initialization to complete successfully, you also need a log setting at the top of this aspire.properties.

You can point the log file anywhere in the system. Make sure the directory exists. Aspire will create the log file every time it runs. It will log an error to the console if this file is not found. Also beware of Unix/NT file-naming conventions.

You can use the selectiveFilters and the excludeFilters to choose which log messages you want to see and which you don't. SelectiveFilters is a comma-separated list of strings. If a log message starts with any one of these strings, that message will be printed; otherwise, not. Same thing with excludeFilters, except that the matching log messages will be excluded. Some sample patterns are mentioned above.

You will know that your initialization is successful when you see a series of Aspire-related messages on your Tomcat console. These messages will start with the version of Aspire, what log file it is using, and from which properties files it is loading. If you don't see these messages, check your log file path. For any issues, email me at
.

Sample Code for Reading Configuration

Before diving into the data access code of Aspire, let us spend a couple of
minutes with the configuration API. Let's use Aspire to read a couple of config file entries. Create a config file called myconfig.properties with the following text:

Module1.key1=20
Module2.key2=14

Include this myconfig.properties file in the master aspire.properties file as follows (the second and third lines here are actually one line with no spaces).

The symbolic name aspire is a directory alias pointing to your webapp root, but it can point to any directory on your system. Restart Tomcat so that this file gets picked up. Here is the sample Java code to demonstrate the reading configuration.

AppObjects is a static wrapper utility class. The function getValue() will take a hierarchical key and returns its value. If the value is not found, a ConfigException will be thrown. If you use a getValue() that takes a default, then instead of throwing an exception, the default value will be returned. You can use the exception version of the call to read in mandatory config entries.

In addition to handling config exceptions, the code above also demonstrates how to use the log facility available in Aspire. Almost all of Aspire's exceptions are nested exceptions. And Aspire's logging facility will automatically log them to the log file, including the nested exceptions. You can prevent seeing these exceptions in the log file using the Select and Exclude filters. Aspire uses a configuration entry for controlling logging adapters. For example, you can change the logging adapter to one of your choice, as long as you write an implementation for it.

where Clog is an implementation of com.ai.application.interfaces.ILog. You can take a look at the source code for both classes to implement your own.

Reading Relational Data From Files

Although configuration and logging are essential aspects of an application, you may or may not consider them compelling enough to adapt them for your application. With that in mind, let me present Aspire's
data abstraction layer, which I believe is complete (comprehensive), easy,
practical, fluid, and stays out of your way. Let us start by reading a flat file
called data.txt with the following entries:

col-one|col-two|col-three
10|44|44
33|44|33

Furthermore, assume that the rows are subdivided into columns using the pipe (|) character, giving the appearance of a database call.

Here is sample code to read the collection of rows and columns from the
above data file:

Aspire's idea of abstract data and all the relevent interfaces are defined in the package com.ai.data. IDataCollection is the central interface representing the idea of a collection of rows, where each row can be any object. One such object is represented by IDataRow. You would use an IIterator to walk through this set of rows. If there were any errors in the data abstraction layer, you would receive an exception of type com.ai.data.DataException.

The mysterious looking call AppObjects.getObject("MYDATA",args) requires explanation. MYDATA is a symbolic request name that Aspire will resolve to a file reader (in this case). The output of that file reader will be returned to the caller as an IDataCollection. args is a map (hashtable) of arguments that could be used by the file-reader. Aspire uses the Configuration abstraction to translate the symbolic name MYDATA to a Java class. Following is the property file section that is required for this translation.

Aspire maps the MYDATA symbolic request name to a Java class called com.ai.data.RowFileReader and invokes its well-known method with the hashtable of arguments passed in. It is the responsibility of RowFileReader to return an IDataCollection, which gets cast to that type by the client. com.ai.data.RowFileReader is one of Aspire's prebuilt parts. Aspire has a few of these pre-built parts that you can make use of. See also how the directory alias is again used here in specifying the filename.

The point to note is that the true implementation of data access (file opens, reads, closes) is hidden (or abstracted out) and externalized (through config files), easing maintenance. Let us look now into the "fluidity" aspect of the abstraction by looking at how we can read a similar set of rows and columns using SQL.

Reading Data Using SQL

We have come to a place in Aspire where the practical benefits of this .jar file are most obvious. Let us see how difficult is it to read the same set of rows from a database instead. Sample code follows first.

As I was getting ready to explain this code, I realized it is identical to the previous file version, including imports. Nothing has changed. That is true. No code needs to be changed to switch between reading a file to reading an SQL statement. That means you can switch your datasources easily and uninvasively. This sense of declarative fluidity permeates the architecture of this .jar file. Following the file example, Aspire should translate the symbolic name of MYDATA to an SQL statement. Here is the config file permitting this transaction:

I have used here an Aspire component called Com.ai.db.DBRequestExecutor2 instead of the FileReader component. DBRequestExecutor2 takes two arguments in the config file -- db and stmt. db points to the database alias. stmt points to the SQL statement. The select statement is trivial. The immediate question is, how do we deal with arguments? Here is an example of a SQL statement that uses arguments.

where col1-key is a key from the hashtable of arguments that are passed in. The string representation of the value of col1-key will be literally substititued into the SQL statement; the resulting SQL statement will be the one that gets executed. If the column happens to be a string, you can do the following:

Notice the .quote after your key. There is a whole science behind the .quote, but for now it is sufficient to say that for strings we use the .quote. This also ensures that if the string value has embedded quotes, they will be doubled or escaped to suit the database. Also, if the key happens to be null, the .quote will place the database null value there without the quotes.

Just like in the FileReader, the programmer is completely unaware of the database connections, JDBC, etc. As a result, this approach is less error-prone. So far there is one thing
left unsaid. MyDataBaseAlias is a symbolic name for a database reference. These symbolic names are called database aliases and are mentioned in config files.

Defining Databases

It has become a common practice now to define database connection details in config files. So the following data source definition should come as no surprise.

You can define any number of databases this way. Aspire goes one step further and can selectively associate a datasource (or database reference) to a database alias. Ultimately, it is this alias that is
used throughout. This allows us to swap databases between test, development,
production, etc. with a single change. Here is how we can define the database
alias MyDatabaseAlias.

Database.alias.MyDatabaseAlias = MyOracleDB

Collecting Data Using Stored Procedures

Let us continue with the fluidity of external declarative approach for data access. Let us replace the SQL calls with a stored procedure for Oracle. Again, clients stay intact; no code change. The config file will change for MYDATA, pointing to a stored procedure executor. The config file follows:

The component StoredProcedureExecutor2 is specific to Oracle. Oracle has special requirements for calling stored procedures. Inside of the Oracle stored procedure you will have to use REFCURSOR as your first argument to the procedure. This component supports only result sets that come back, and not individual variables. Read Oracle's JDBC documentation for writing stored procedures with REFCURSORS. You can also refer to "A JSP Architecture for Oracle Stored Procedures" in JavaReport (View code from this issue). For Microsoft SQL Server, the previous component DBRequestExecutor2 should work.

The similarities include coming up with a symbolic name, in this case MyUpdate, and passing in a hashtable of arguments. The AppObjects.getObject(...) in this case will return a RequestExecutorResponse instead of IDataCollection.
Here is how you specify the properties file for the MyUpdate symbolic name.

Note that the stored procedure has lost the?, as this is an update. Also, for testing your hashtable of arguments, you can use a FileWriter part in place of the DBRequestExecutor2 or StoredProcedureExecutor2. Here is an example:

The open-mode defaults to rewrite, where the file gets created every time.

Executing Multiple SQL Statements

So far, I have been pointing at and alluding to the "declarative" nature of data access and business logic. This declarative nature comes into its own once we start providing components the sole goal of which is composition or orchestration. Let us see this by examining a MultiExecutor, the only purpose of which is to execute a series of other functional executors such as FileReader, DBRequestExecutor, StoredProcedureExecutor, etc., including itself recursively.

For this, let's define two updates in the config file that we can call: MyUpdate1 and MyUpdate2.

You have just made your MyUpdate call MyUpdate1 and MyUpdate2, without writing any Java code. Moreover, you have transactionally bound both updates as a single update. In essence, we now have access to three updates: MyUpdate1, MyUpdate2, and MyUpdate. Clients can call any of these in any order and still guarantee the
transactional features. Which means MyUpdate1 will commit or roll back if it
is called by itself, but will restrain from such activity when it is called as part of a composition.

Let us continue the magic a bit further by defining another select statement:

Now you know why this MultiRequestExecutor part is called "pretranslate" -- because it has just introduced a new key called key2 into the hashtables that could be used by the downstream updates or selects. In a sense, we have translated key2 to its value. Here is how this key2 is used by MyUpdate3:

Tool for Testing Data Access

Another benefit of declaring these database access requirements in a config file is that we can write a test driver that can test these data access procedures. One such test driver is available in the .jar file itself. The following batch file demonstrates this utility driver:

where classes12_817.jar is the 8.1.7 release of the Oracle's JDBC driver. Once you have the batch file ready, you can execute the command as follows:

RunTestCollection.cmd <your properties file>

When prompted, use the following syntax for executing the symbolic names (commands):

Command,arg1=value,arg2=value
Use "quit" command to exit

where command is same as your symbolic name. See how you are passing
arguments. For any output or errors, check the log file. You can use both query
commands and update commands. In case of an update command, the output is
ignored.

Factory Services

All of the flexibility we have seen so far in the data access layer is made possible by an underlying service called FactoryService, which is directly accessible to your Java applications. This is exposed through a single method as follows:

Object AppObjects.getObject(string symname, object args);

The idea here is that Aspire will instantiate a class name identified by the symname and call its well-known method, called executeRequest(..), and return whatever that method returns. Aspire uses your config files for this name translation. Here is an example:

Aspire, by default, will create your MyClass only once but will call the executeRequest every time a client issues getObject. So in essence, MyClass is a singleton and its semantics are those of a factory that can generate objects. One way to implement a singleton is as follows:

Aspire will instantiate a new object every time it sees a ISingleThreaded implemented. This programming model is somewhat similar to session beans but is in-process and lightweight. When combined with multirequest executors, you can also achieve limited transactional programming facilities
that are quite practical and sufficient for a single database application.

Using XML Config Files

So far you have seen the case where you are using properties files for your configuration. Aspire.jar supports XML config files as well, without ever changing your client code. Consider the following XML config file:

Defining Connection Pools

While executing these SQLs, Aspire has to get a connection from the database. Aspire uses two homebuilt connection pool managers for this purpose. You can easily implement your own by implementing the IConnectionPool interface (the following lines with an = sign are actually one line with no spaces):

#********** SimpleConnectionManager definition
#request.AppObjects.connectionManager.className=
com.ai.db.SimpleConnectionManager
# multiple connection pool manager
# There is no cap on the number of connections
# Connections will even out based on your demand
request.AppObjects.connectionManager.className=
com.ai.db.ConnectionPoolConnectionManager1
request.AppObjects.scheduler.className=
com.ai.scheduler.BasicScheduler
AppObjects.scheduler.timer_interval_in_milli_secs=60000

By default, the connection pool manager is set to the multiple connection pool manager. If you want the simple connection pool manager, comment out these three lines and uncomment the one line corresponding to the SimpleConnectionPoolManager. SimplePoolConnectionManager will close the collection every time it is returned and open a new one when requested. The ConnectionPoolConnectionManager1 will cache the connections and will create a new connection if all of the connections that are in the pool are being used. The timer wakes up every five minutes and closes unused connections and does some other housekeeping.

Additional Features of the Aspire.jar File

In this section, let me briefly discuss the advanced facilities of this small .jar file. In an article in XML Journal two years ago, I presented the idea of retrieving infosets (I am using this term loosely, compared to the W3C InfoSet, but it shares many conceptual similarities) by generating and consuming XML messages from and to relational databases. In short, an infoset is a hierarchical set of nodes where each node is tied to a collection (like a RowSet).

This binding happens declaratively in a config file. Aspire will execute this infoset, execute all the database calls, and create a hierarchical data set called infoset. The infoset can be presented as:

Non-typed navigable Java tree (non-typed Java infoset)

Typed Java tree (Typed Java infoset)

XML (XML infoset).

The resulting infoset then can be transformed using either Java or XSLT. More recently, this idea was published at O'Reilly as "Transparent Data Pipelines for JSP." You can use flat files, SQL, stored procedures, and Java components to declaratively construct
these infosets.

Have Aspire authenticate users with a couple of default authenticators.

Have Aspire expose your business components (both procedural and declarative) as SOAP services.

Import and export data into and out of the database.

Use a simplified Aspire transformation in conjuction with tools like Dreamweaver and FrontPage to supplement JSP programming.

Paint JavaScript-based trees on your screen where each node on your tree is tied to a symbolic name pointing to a stored procedure, SQL statement, or Java code. You can save viewstate of your page using JavaScript.

If you would like to make use of this advanced functionality, email me at
so that
I can point to you additional documentation.

Help

For any questions regarding the usage of this .jar file, email me at
.