Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Building a Java servlet framework using reflection, Part 2

Gain greater functionality with less code using these reflective code samples

  • Print
  • Feedback

Page 7 of 7

Data access

Articles about frameworks are often undermined by the numerous exceptions to every rule they put forward. In Part 1, I presented a simple logging interface for data access. It is difficult to present a framework that considers every case -- so I chose to show a simple data object that you can use to log a user's actions to the database. Should your application require a query for data presentation, you should use a different interface.

For this article, I'm considering a system in which a user's actions are logged in to the database, and the values entered on the forms are stored there. In order to gain access to the system, the user must be authenticated against the database. Therefore, the database interface must contain methods for running a query in addition those that govern to the logging and storage operations.

Developing data access

Here's the database interface I have chosen to perform the logging and query functionality:

public interface DataInterface
{
    // To connect and disconnect
    public void connect() throws DatabaseConnectivityException;
    public void disconnect() throws DatabaseConnectivityException;
    // For logging of user actions and performed transactions.
      public boolean createTransaction() throws DatabaseConnectivityException;
        public boolean beginTransaction() throws DatabaseConnectivityException;
    public boolean commitTransaction() throws DatabaseConnectivityException;
    public boolean rollbackTransaction() throws DatabaseConnectivityException;
    // For the logging and queries.
    public Vector execute(String sqlString) 
          throws DatabaseConnectivityException;
}


This interface is intended to satisfy the requirements for the example system traced throughout this article. It is necessary for the system to log user actions, process user requests, and run reports based on the values stored in the database. This interface could be further expanded should the requirements call for more detailed interaction with the database. It's important to note that you can load the database connection parameters using the action name as their key. In this way, the action name becomes the driving force behind this system.

As you've probably already noticed, the execute method returns a Vector object. This lets you use the execute method to run queries and perform inserts, updates, and deletes on the database as necessary. Why have I chosen to use a Vector instead of a JDBC ResultSet? I generally feel that the database-specific code should be contained in the classes closest to the database rather than classes removed from the SQL. There is more overhead involved in doing so, making this abstraction a prime candidate for optimization should your system performance slow down due to large queries.

Using a philosophy similar to that of the BusinessRules, you can use a DatabaseRules object to store the SQL needed to complete the transaction. In this case, I've chosen a vector to ensure that the SQL is performed in the order necessary to ensure relational integrity.

// initialize the vector to contain the data rules for the register user action
public static Vector RegisterUserRules = new Vector();


Here's a sample SQL statement with tokens:

public static final String TRANSACTION_STRING =
    "Insert into Transactions values ("                                 + TICK + BEGIN + BusinessRules.CONFIRMATION_NUMBER + END
    + TICK + COMMA + SPACE
    + BusinessRules.SYSDATE
    + COMMA + SPACE
    + TICK + BEGIN + BusinessRules.SESSION_ID + END
    + TICK + COMMA + SPACE
    + TICK + BEGIN + BusinessRules.ACTION_NAME + END
    + TICK + COMMA + SPACE
    + TICK + BEGIN + BusinessRules.SEQUENCE_NUMBER + END
    + TICK
    + ")";


The SQL statement is added to the vector, as shown above. It can then be retrieved using reflection in a similar manner to the loadBusinessRules method described in the development of the BusinessObject. I've chosen a simple means by which you can insert values into the SQL string for the greatest flexibility. This scheme does not require that you insert all fields in a particular order; thus, you are freed from a strict interface between business and data objects.

Having loaded the DataRules reflectively using the action name, you can perform the substitution followed by the execution of the SQL against the database. The code for the DefaultDataObject is fairly self-explanatory, with the following methods performing the bulk of the duties:

  • ReplaceTokens: Exchanges the tokenized values with the actual values passed in by the business object
  • BuildSQLString: Parses through the SQL statement and calls the ReplaceTokens method repeatedly
  • Execute: Executes a SQL statement and converts the result set into a vector of vectors


Using this reflective framework, SQL statements can be added, modified, or deleted from the property file from which all of these values should be loaded. This scheme allows the database code to be developed independently of the business objects -- further reducing the development interdependencies.

SUBHEAD_BREAK: Presentation

There are numerous ways to handle the presentation layer -- you can use anything from XML to a tool like HTMLKona. The development timeframe and skill of the development team must be considered before making this decision. In many cases, it is easier to find HTML designers than software developers. A good solution to this situation involves the use of HTML templates, which can include JavaServer Pages.

A graphic artist creates templates with placeholders for the variable data elements in the design. In this fashion, the most page creation will be static, with some dynamic data added in at runtime. The following section further develops this model with a simple example.

Developing presentation

The presentation layer interacts closely with the majority of the application servers present on the market. In the case of the Netscape Application Server, there is a method through which templates can be evaluated and HTML can be output to the user.

If you consider that, due to its proximity to the user interface, a platform change is likely to have an impact on this layer, you should attempt to isolate the specific functionality from the majority of your developers. You can accomplish this through the PresentationServices interface. Once you implement this interface, you will be responsible for the platform-specific code required to interface with the application servers, Web servers, and template engines as needed. The methods exposed by this interface will allow the other development teams to use this functionality without being fully trained in the deployment platform tool. Here's the basic PresentationServices interface:

public interface PresentationServices{
    public void initializePresentation(ProxyInterface proxy) 
         throws IOException;
    public void output(String text) throws IOException;
    public void finalizePresentation() throws IOException;
}


Upon instantiation, the PresentationObject is passed a reference to the PresentationServices. Using this reference, the PresentationObject can delegate its behavior to the platform-specific object while maintaining a loose coupling between the object and the deployment platform. In this manner, the PresentationObject is isolated from the deployment platform code, but can still discharge the duties required by the business action.

The interaction between the business objects and presentation objects is managed through the PresentationInterface. This interface in turn provides methods that allow the business objects to interact with the presentation layer. The presentation object exposes methods, but does not give up its position as the expert presentation-handling object. This encapsulation allows each team to focus on its main development tasks without being too concerned that another team will misuse its object.

public interface PresentationInterface
{
    public boolean isLastPage();
    public void showNextPage(Hashtable pageData);
    public void showPage(Hashtable pageData);
    public void showConfirmation(Hashtable confData);
    public void showReference( Hashtable referenceData);
    public void showErrorPage(String errorMessage); 
}


To continue with my example, here are descriptions of the two pages I've developed for this article.

  • Registration page: Captures user information for use in reporting
  • Report page: Outputs usage information detailing the registered users and their city and country of origin


The PresentationRules are loaded in a similar fashion to the BusinessRules and DataRules, with the action name providing the key to the rules' location. In this case, a vector contains all of the pages for a given action. In the case of the multiple page action, more than one page will be in the vector.

public static Vector RegisterUserRules = new Vector();


The values can then be added to the vector to ensure that the pages are traversed properly, as follows:

RegisterUserRules.addElement(LOCATION + "RegisterUser.html");


The PresentationServices object does not contain any procedural code. It is driven by the business object to ensure that the presentation flows with the business logic. Having loaded the PresentationRules object, the PresentationObject can interact with the business objects as they perform their functionality.

As an example, the RegisterUser action will have a single page associated with it. When the BusinessObject interacts with the PresentationObject, it will be determined that there is only one page associated with this action, thereby triggering the transaction that will indicate that the action has been completed. Should an action have multiple pages, the business object will advance to the next page until the last page has been reached. This allows the business object to rely on the presentation object for the number of pages in an action further reducing the dependencies.

Additional pages can be added to the presentation simply by inserting additional references into the Vector. Because the business objects are not dependent on the specific pages on which fields reside, it is possible to make large changes to the presentation without necessitating changes in the business layer.

Conclusion

As I mentioned earlier, it is not possible to develop a singular all-encompassing framework. That is not the intent of this article; instead, the ideas presented here are intended to demonstrate some fairly simple methods through which you can use the power of Java to increase functionality exponentially while making sure that your code base grows only linearly.

Throughout the design and development of any system, the development team must make assumptions and decisions based on resources and time constraints. These decisions may make some of this framework infeasible, but I feel quite confident that a most of the ideas presented here can be applied across multiple development efforts. The following aspects of the framework should be widely applicable:

  • The separation of the business, data, and presentation layers into components, allowing development teams to work independently by reducing the required interactions
  • The creation of a reusable code base that performs such operations as validation, error handling, and database interaction
  • The use of interfaces to enforce the contracts agreed upon during the segmentation of layers
  • The use of reflection to reduce the if-then clauses that sometimes sneak into code
  • The isolation of product-specific features through the concept of services, which isolate the platform-specific code from the majority of the developers.


If you can spend the time on design and have a skilled development team, this is a good methodology. It requires a disciplined and detail-oriented team for the benefits to be fully realized. In addition, the underlying systems upon which the code is being developed must have some stability through the application design.

About the author

Michael Cymerman is a consultant specializing in Java/Internet software solutions. He provides Java/Internet-based architecture, design, and development solutions to Fortune 500 companies.

Read more about Enterprise Java in JavaWorld's Enterprise Java section.

  • Print
  • Feedback

Resources