ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


For Tomcat Developers, Aspire Comes in a JAR

by Satya Komatineni
10/30/2002

Aspire.jar: Utility for Data Access, Configuration, Logging, and Factory Needs

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?

Getting Started

Source Files

Download the needed files as a single .zip file. This file contains the following:

aspire_r2.0_b16.3_jsdk21.jar
aspire_r2.0_b16.3_src.zip
aspire.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:

<servlet>
   <servlet-name>
      AppInitServlet
   </servlet-name>
   <servlet-class>
      com.ai.servlets.AppInitServlet1
   </servlet-class>
   <init-param>
      <param-name>AppConfigFilename</param-name>
      <param-value>aspire.properties</param-value>
   </init-param>
   <load-on-startup>
      1
   </load-on-startup>
</servlet>

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.

Logging.logfile=c:\\webapp-root\\log\\ai.log
Logging.msgLevel = 0
Logging.trace=yes
# Logging.selectiveFilters=cp:,sc:,db:
# Logging.excludeFilters=vectorMetadata:,dbrs:,

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).

Directories.aspire=c:\\your-webapp-root
application.includeFiles=aspire:\\properties\\myconfig.properties,
     aspire:\\another-config-file

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.

import com.ai.application.interfaces.*;
import com.ai.application.utils.*;

public void somefunction()
{
   try 
   {
      String default = "67";
      String val1 = AppObjects.getValue("Module1.key1",default);
      String val2 = AppObjects.getValue("Module2.key2");
   }
   catch(com.ai.application.interfaces.ConfigException x)
   {
      AppObjects.log("Error: Mandatoty configuration entry missing",x);
   }
}

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.

Here is the log implementation object entry in the properties file:

request.ApplicationObjects.Log.className=com.ai.application.defaultpkg.Clog

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:

import com.ai.application.interfaces.*;
import com.ai.application.utils.*;
import com.ai.data.*;
    
public void someFunction()
{
   IDataCollection col;
   try
   {
      Hashtable args = null;
      col = (IDataCollection)AppObjects.getObject("MYDATA",args);
      IIterator rowItr = col.getIIterator();
      for(rowItr.moveToFirst();!rowItr.IsAtTheEnd();rowItr.moveToNext())
      {  
         IDataRow dr = rowItr.getCurrentElement();
         String value = dr.getValue("col-one");
      }
   }
   catch(com.ai.application.interfaces.RequestExecutionException x)
   {
      AppObjects.log("Error: Could not get data collection. Factory error",x);
   }
   catch(com.ai.data.DataException x)
   {
      AppObjects.log("Error: Data related error",x);
   }
   finally
   {
      col.closeCollection();
   }
}

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.

Request.MYDATA.classname=com.ai.data.RowFileReader
Request.MYDATA.filename=aspire:\\datafiles\\mydata.txt

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.

import com.ai.application.interfaces.*;
import com.ai.application.utils.*;
import com.ai.data.*;
         
public void someFunction()
{
   IDataCollection col;
   try
   {
      Hashtable args = null;
      col = (IDataCollection)AppObjects.getObject("MYDATA",args);
      IIterator rowItr = col.getIIterator();
      for(rowItr.moveToFirst();!rowItr.IsAtTheEnd();rowItr.moveToNext())
      {  
         IDataRow dr = rowItr.getCurrentElement();
         String value = dr.getValue("col-one");
      }
   }
   catch(com.ai.application.interfaces.RequestExecutionException x)
   {
      AppObjects.log("Error: Could not get data collection. Factory error",x);
   }
   catch(com.ai.data.DataException x)
   {
      AppObjects.log("Error: Data related error",x);
   }
   finally
   {
      col.close();
   }
}

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:

Request.MYDATA.classname=com.ai.db.DBRequestExecutor2
Request.MYDATA.db=MyDataBaseAlias
Request.MYDATA.stmt=\
   Select * from table1

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.

Request.MYDATA.classname=com.ai.db.DBRequestExecutor2
Request.MYDATA.db=MyDataBaseAlias
Request.MYDATA.stmt=\
   Select * from table1 where col1-value={col1-key}

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:

Request.MYDATA.classname=com.ai.db.DBRequestExecutor2
Request.MYDATA.db=MyDataBaseAlias
Request.MYDATA.stmt=\
   Select * from table1 where col1-value={col1-key.quote}

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.

Database.name=MyOracleDB
Database.MyOracleDB.jdbc_driver=oracle.jdbc.driver.OracleDriver
Database.MyOracleDB.connection_string=jdbc:oracle:thin:@host:port:oracle_instance
Database.MyOracleDB.userid=user
Database.MyOracleDB.password=passwd

See how we have defined a database reference to an Oracle database. Let us proceed to define a database reference for an Access database.

Database.name=accessDB
Database.accessDB.jdbc_driver=sun.jdbc.odbc.JdbcOdbcDriver
Database.accessDB.connection_string=jdbc:odbc:ptr.mdb
Database.accessDB.userid=access_user
Database.accessDB.password=access_pw

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:

Request.MYDATA.classname=com.ai.db.StoredProcedureExecutor2
Request.MYDATA.db=MyDataBaseAlias
Request.MYDATA.stmt=\
   Call sp_get_table_data(?,{col1-key.quote})

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.

Java Programming with Oracle JDBC

Related Reading

Java Programming with Oracle JDBC
By Donald Bales

Performing Database Updates

So far so good. How do we work with updates? It is quite similar and just as easy.

import com.ai.application.interfaces.*;
import com.ai.application.utils.*;
import com.ai.data.*;
          
public void someUpdateFunction()
{
   try
   {
      Hashtable args = new Hashtable();
                
      string key1 = "col1-key";
      args.put(key1.toLowerCase(),"68");
                
      RequestExecutorResponse r = 
         (RequestExecutorResponse)AppObjects.getObject("MyUpdate",args);
          
      AppObjects.log("Info: Successfully executed");
   }
   catch(com.ai.application.interfaces.RequestExecutionException x)
   {
      AppObjects.log("Error: Could not get data collection. Factory error",x);
   }
   catch(com.ai.data.DataException x)
   {
      AppObjects.log("Error: Data related error",x);
   }
}

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.

Request.MyUpdate.classname=com.ai.db.DBRequestExecutor2
Request.MyUpdate.db=MyDataBaseAlias
Request.MyUpdate.query_type=update
Request.MyUpdate.stmt=\
   Insert into table1 values({col1-key.quote})

Notice the extra query_type. Both stored procedures and SQL statements can be used. If you use a stored procedure, you have to specify the StoredProcedureExecutor2. An example follows:

Request.MyUpdate.classname=com.ai.db.StoredProcedureExecutor2
Request.MyUpdate.db=MyDataBaseAlias
Request.MyUpdate.query_type=update
Request. MyUpdate.stmt=\
   Call oraclepkg.sp_insert({col1-key.quote})

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:

Request.MyUpdate.classname=com.ai.db.FileWriter
Request.MyUpdate.filename=aspire:\\dataout\\out.txt
Request.MyUpdate.openmode=append

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.

Request.MyUpdate1.classname=com.ai.db.StoredProcedureExecutor2
Request.MyUpdate1.db=MyDataBaseAlias
Request.MyUpdate1.query_type=update
Request.MyUpdate1.stmt=\
   Call oraclepkg.sp_insert({col1-key.quote})

Request.MyUpdate2.classname=com.ai.db.StoredProcedureExecutor2
Request.MyUpdate2.db=MyDataBaseAlias
Request.MyUpdate2.query_type=update
Request.MyUpdate2.stmt=\
   Call oraclepkg.sp_insert2({col1-key.quote})

Now let's see how we can compose a third update by combining the two updates that we have defined earlier.

Request.MyUpdate.classname=com.ai.db.PreTranslateArgsMultiRequestExecutor
Request.MyUpdate1.db=MyDataBaseAlias
Request.MyUpdate1.request.1=MyUpdate1
Request.MyUpdate1.request.2=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:

Request.MyUpdateQuery.classname=com.ai.db.DBRequestExecutor2
Request.MyUpdateQuery.db=MyDataBaseAlias
Request.MyUpdateQuery.stmt=\
   Select "3500" as key2 from dual

The above section is retrieving a row with a single column called key2 with a value of 3500. See how this value can be used in your update:

Request.MyUpdate.classname=com.ai.db.PreTranslateArgsMultiRequestExecutor
Request.MyUpdate1.db=MyDataBaseAlias
Request.MyUpdate1.request.1=MyUpdateQuery
Request.MyUpdate1.request.2=MyUpdate1
Request.MyUpdate1.request.3=MyUpdate2

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:

Request.MyUpdate3.classname=com.ai.db.StoredProcedureExecutor2
Request.MyUpdate3.db=MyDataBaseAlias
Request.MyUpdate3.query_type=update
Request.MyUpdate3.stmt=\
   Call oraclepkg.sp_insert3({col1-key.quote},{key2})

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:

set classpath=aspire_r2.0_b16.3_jsdk21.jar;q:\jars\classes12_817.zip;
p:\jdk13\bin\java com.ai.test.TestCollection %1

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:

Request.MYSYMNAME.classname=MyClass

where MyClass needs to implement the ICreator interface as follows:

public MyClass implements Icreator
{
   Object executeRequest(String reqName, Object args)
   {
      // implement your specific funtionality
   }
}

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:

public MyClass implements Icreator, IMySpecialInterface
{
   Object executeRequest(String reqName, Object args)
   {
      // implement your specific funtionality
      return this;
   }
}

Then you can use your class as follows:

IMySpecialInterface  myInterface = 
   (IMySpecialInterface)AppObjects.getObject(IMySpecialInterface.NAME,null);
myInterface.myMethod();

See how you have hidden the implementation completely. You can have some config arguments:

Request.MYSYMNAME.classname=MyClass
Request.MYSYMNAME.arg1=333
Request.MYSYMNAME.arg1=444

And read those arguments as follows in your code:

public MyClass implements Icreator, IMySpecialInterface
{
   Object executeRequest(String reqName, Object args)
   {
      string arg1 = AppObjects.getValue("request." + reqName + ".arg1");         
   }
}

Sometimes you don't want your class to be singleton but rather a true business object that is stateful. Then you can alter your class as following:

public MyClass implements Icreator, IMySpecialInterface, ISingleThreaded
{
   Object executeRequest(String reqName, Object args)
   {
      string arg1 = AppObjects.getValue("request." + reqName + ".arg1");         
   }
}

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:

<xml version='1.0' encoding='utf-8' >
<config>
   <keys>
      <key1>val1</key1>
      <key2>val2</key2>
   </keys>
    
   <request name="GetMultiTable">
      <classname>com.ai.db.DBPreTranslateArgsMultiRequestExecutor</classname>
      <Request.1>GetSeries1</Request.1>
      <Request>GetSeries2</Request>
   </request>
       
   <request name="GetTable1">
      <classname>com.ai.data.RowFileReader</classname>
      <filename>aspire:\\samples\\xml-config-files\\sample-data-1.txt</filename>
   </request>
    
   <request name="GetTable2">
      <classname>com.ai.data.RowFileReader</classname>
      <filename>aspire:\\samples\\xml-config-files\\sample-data-1.txt</filename>
   </request>
</config>

Include the XML Config File in aspire.properties

To read both properties files and XML config files, you need the following line pointing to an XML config reader (the following code is actually two lines with no spaces after the = sign):

#request.ApplicationObjects.Config.className=
    com.ai.application.defaultpkg.CConfigWithIncludes
request.ApplicationObjects.Config.className=
    com.ai.extensions.xmlconfig.ConfigWithXMLIncludes

See the commented out section and the new line. And you can include your XML config files in the following line:

application.xmlIncludeFiles=
   aspire:\\samples\\xml-config-files\\xml-sample-file.xml

Accessing Your XML Config Entries

import com.ai.application.interfaces.*;
import com.ai.application.utils.*;

public void readYourKeys()
{
   .. usual try and catch stuff if you need
   String key1Value =   AppObjects.getValue("keys.key1");
   String key2Value =   AppObjects.getValue("keys.key2");
}

See the similarity to accessing your properties files. In essence, you can choose to keep your configuration either in XML or properties and your code will still work.

An Equivalent Properties File

Here is an equivalent properties file matching the XML config above.

keys.key1=val1
keys.key2=val2
    
request.GetMultiTable.classname=com.ai.db.DBPreTranslateArgsMultiRequestExecutor
request.GetMultiTable.request.1=GetTable1
request.GetMultiTable.request=GetTable2
request.GetMultiTable.db=<dbalias>

request.GetTable1.classname=com.ai.data.RowFileReader
request.GetTable1.filename=aspire:\\samples\\xml-config-files\\sample-data-1.txt
request.GetTable2.classname=com.ai.data.RowFileReader
request.GetTable2.filename=aspire:\\samples\\xml-config-files\\sample-data-2.txt

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:

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.

In addition, you can:

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 .

Additional References

Satya Komatineni is the CTO at Indent, Inc. and the author of Aspire, an open source web development RAD tool for J2EE/XML.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.