Building a Java servlet framework using reflection

Summary
Using this platform-independent framework, it is easy to use object-oriented programming concepts, such as encapsulation, on the server side. Michael chose to use servlets as the example of this framework due to their easy availability and usability. Any other server-side platform (e.g., CORBA, RMI, Netscape Application Server) can be substituted into this framework with a minimal amount of change to the code base. Although Java servlets are an advanced tool used to create HTML presentation, many developers are not taking full advantage of their capabilities. This servlet framework allows developers to separate their Web systems into logical components with little overhead. (1,500 words)

Common Gateway Interface (CGI) scripting languages like Perl may offer as many features as the Java Servlet API, but servlets significantly outperform their CGI cousins when compared on any given functionality.

Many developers who have made the transition from CGI scripting to Java servlets have continued the CGI development style in Java, even though that style may not be the most efficient option available to them. Consider this example: programmers typically use servlets to interpret the input of a Web browser, determine an appropriate course of action, and then display results to the user. The process the servlet handles is thus fourfold: control, business logic, data access, and presentation. In the CGI framework, a single CGI script handled this entire process. Those who continue to program in a CGI style often create monolithic servlets to handle all four of these actions.

It doesn't need to be that way. This article will provide a tool which developers can use to create efficient, easy-to-maintain, server-side Java code.

Framework overview
The actions usually performed in a request-response model have four general characteristics:

 

  • Controller: Coordinates all of the other components

    Business logic: Executes business rules, like validation, in order to satisfy the request

    Data logic: Provides access to any necessary databases, CORBA services, EJBs, or other back-office persistence mechanisms

    Presentation logic: Displays the results to the client

This framework does not depend on the presence of Java servlets. All it needs is agreement among developers on the contracts among objects. Some developers should be responsible for the controller code, which can be servlets, CORBA or RMI servers, or Netscape Application Server AppLogics. The other development teams need not be bothered about the specific implementation of those controller components; they need only use the methods provided by the controller objects.

How to create a servlet framework
You can create a servlet framework by assigning responsibilities to various system components. This in turn requires establishing a coding interfaces. The boundary lines between component groups must be drawn so that no object of one group can be used by any object outside that group. For example, be sure that no platform-specific object (like HttpservletRequest) or method is used by any noncontroller component.

A segmented system can be completely self-contained. For example, the team developing the data components can work to make it satisfy the following interface (a simple case), or something similar:


public interface DataInterface
{
public void initializeDataObject();
public void beginTransaction();
public void commitTransaction();
public void rollbackTransaction();
public void executeSQL(String sqlString);
}

This is a simple interface for logging user actions or accepting user inputs to a database. Because all DataObjects must satisfy the DataInterface, it is possible for a single object to perform the duties associated with the database interaction. Again, no component outside of the data functionality should have to deal with connection pooling or database parameters.

Similarly, it is possible to create objects to handle presentation-related issues using an interface similar to the following:


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

Notice that this interface is implemented by any object responsible for presentation-related activities. Also notice the difference between the two interfaces. The PresentationInterface lists a method with a parameter called PresentationService. The PresentationServicesInterface is the key to the framework, as it is the controller in the framework.

The implementor of the ProxyInterface is the single point of contact for all platform-dependent functionality. The Presentation object does not need to understand the details of implementing the presentation layer. Whether it is through applets, servlets, or sockets, the methods of the interface must be satisfied.

The ProxyInterface shown below can obtain references for the other objects involved in the request-response pattern.


public interface ProxyInterface
{
public DataInterface getDataObject(String name);
public PresentationInterface getPresentationObject(String name);
public BusinessInterface getBusinessObject(String name);
}

Because this interface is quite generic, it may be used as a proxy for requests made to servlets or other server-side applications, such as Netscape Application Server's AppLogics. The intention is to create a structure such that all the information specific to the deployment platform is contained in the implementation of the ProxyInterface, or in a small subset of classes. This allows developers to write code independent of proprietary methods or variables, thereby increasing the efficiency of the development team.

The last component of our expandable framework is the BusinessInterface:


public interface BusinessInterface
{
public void init(ProxyInterface proxy, Hashtable parameters) throws Exception;
}

An implementation of this interface is spawned for every request made to the framework. This interface contains a method through which the input parameters pass from the servlet to the business object. In addition, the proxy is passed to the business object in order to provide it with the tools necessary to perform its task.

We have created a framework through which we can launch business objects to perform the individual actions of the users -- including all data and presentation issues -- without any knowledge of the individual platform upon which they reside. Now, how do we know which business object to load?

 

Dynamic business objects loading
The Java Reflection API provides an easy mechanism with which to execute this framework. In this first part of our series, Reflection itself is not used. For now, we will use the class.forName(...) method to load the appropriate business object.


private static BusinessInterface loadBusinessObject(String actionName)
{
BusinessInterface bi = null;

try
{
// attempt to retrieve the class from the class loader
Class c = Class.forName(actionName + BUSINESS_OBJECT);

// Initialize a class array with no elements (used in the constructor)
Class[] parameterTypes = new Class[0];

// Locate the constructor with no arguments
Constructor constructor = c.getConstructor(parameterTypes);

// Initialize an object array with no elements (used in the newInstance method)
Object[] initargs = new Object[0];

// Load the new instance of the business interface implementor
bi = (BusinessInterface) constructor.newInstance(initargs);

}
catch (Exception e)
{
// If there is an error, create an ErrorBusinessObject (for now)
bi = (BusinessInterface) new ErrorBusinessObject();

e.printStackTrace();
}

return bi;
}

This method is contained in the BusinessObjectFactory class and dynamically loads the business object based upon the action name provided by the proxy. This method attempts to load the business object and, if any exceptions occur, will create an object to handle the error conditions. It assumes a default constructor for this example, but can be tailored to suit any development efforts as needed.

The two main benefits to loading business objects dynamically with this scheme are that you can avoid a registration step and can add functionality to the application on the fly. Both of these advantages stem from the fact that there is no need to specifically hardcode the possible actions contained in the servlet.

In Part 2 of this series, I'll expand on the benefits of the Reflection API and show some more benefits to this framework. Reflection will make it possible to further reduce the amount of code while greatly increasing the functionality contained within.

A framework example
To complete the explanation of this framework, I have constructed a simple example using the interfaces and Factory class discussed above. In this example, the servlet is used as the controller, data interface, and presentation interface. The example uses extremely simple business objects that show the dynamic behavior contained in the servlet, but not the complete functionality of the system, as that is beyond the scope of the article.

The Proxyservlet service method is the heart of the framework:


public void service(HttpservletRequest req, HttpservletResponse res)
  throws servletException, IOException
{
// convert the request into a hashtable
Hashtable requestHT = getHashtableFromRequest(req);


// retrieve the actionName from the hashtable
actionName = getCurrentAction(requestHT);

// Create a business Object
BusinessInterface bi = getBusinessObject(actionName);

try
{
// Initialize the business object
bi.init( (ProxyInterface) this, requestHT);
} catch (Exception e)
{
// Any exception handling code goes here
}
}

In the code above, the servlet converts the HttpservletRequest into a simple hashtable containing the name-value pairs. It then retrieves the actionName from the hashtable and creates the BusinessInterface implementation. The last step is to initialize the BusinessInterface and let it perform any necessary processing.

This simple example ignores some of the session handling that can be in the service method. This processing can either be handled by the servlet, or in the business object through methods in the proxy interface. It is up to the developer to make the design decision as necessary.

Error handling
One key thing to notice in the above code snippet is the try-catch block surrounding the init method of the BusinessInterface object. This try-catch actually ensures that all error handling can be performed in the same location within the controller. This allows the developers to handle minor exceptions within their business objects and to pass major exceptions up to a single point in the code to ensure that all errors are handled together.

In the second of part this series, I'll expand on some other error-handling additions we can make to this framework to increase its power and expandability.

Conclusions
This article has presented the benefits of using a framework to develop server-side code that supports the request-response pattern associated with the servlet API. In doing so, I have presented the importance of a platform-independent framework and a single location for error handling. I have also alluded to the benefits -- which I will expand upon in Part 2 -- available through true Reflection.

Using this framework allows developers to efficiently design an object-oriented, server-side solution without tying it into the platform upon which it was developed. This provides key benefits if you want to add new features to the system, or if you later decide to port it to another server product.

As new features are added to the system, there is nothing hardcoded that needs to be kept in sync with the code base. The servlet will pick up any additional business logic upon its next execution due to the dynamic class loading that I have demonstrated.

A common error-handling framework is essential to servlet development because it ensures that a user's experience on a Web site is consistent and common to all functionality contained on the site. A single error-handling component is also a benefit to developers because it allows them to avoid building error-handling into every object.

This framework, while simple, does suffer from some overhead due to the isolation of the components, but this also ensures that there are no cross dependencies. The second part of this series will further demonstrate the utility of this framework by expanding on the use of reflective code to enable server-side validation, error handling, data access, and output.

Gain greater functionality with less code using these reflective code samples

 

Summary
In the final installment of this two-part series, Michael Cymerman presents a handy set of code fragments and abstraction fundamentals for creating a Java servlet framework. Building on the concepts discussed in Part 1, he provides a concrete representation of this framework to help you translate the information into a practical, expandable code base. While the title suggests that this is only applicable to server-side programming, most of the insights can be applied to the development of client-side or enterprise-level code as well. (4,700 words)


Advertisement
 

When most developers see the word framework in this article's title, they will immediately become skeptical. I have read numerous articles that purport to describe the one-and-only best solution for Java programming -- especially on the server side. I'm not going to claim that the method outlined here is the only way to go; I merely want to show that it has been successful.

In Part 1, I outlined a simple scheme through which you could use reflection in the development life cycle to reduce coding time and interdependencies. In this article, I hope to bring that high-level discussion down to specific code examples that you can use in your development projects.

It is important to note that not all applications will benefit from the same level of framework development. Because some applications require more enhanced data access or accept user input differently, some of this information will not be relevant to all of them -- especially those apps whose underlying business processes are in a constant state of flux.

The examples in this article will concern the development of:

 

  • Dynamically loaded business objects containing developed validation schemes

     

  • An error-handling hierarchy that will let you concentrate on your specific cases rather than the protection of an entire system

     

  • Database code that will let you interact with a database simply and efficiently

     

  • Presentation objects that enable you to concentrate on the logic behind the code rather than presentation mechanics

I hope you will find this article helpful. These principles have enabled my teams to develop complex systems in a short timeframe.

Recap
In Part 1, I divided a system into the three logical layers: business, data, and presentation. I will expand on each of these layers in the following sections. In addition, I'll present some error-handling code that fits into this framework.

Business objects
A business object is associated with each behavior of an application. For example, all objects built to handle forms in an application will have the behaviors of validating input, processing input, interfacing with the data access layer, and presenting output to the user. In a similar fashion, all business objects for pages reached through direct HTML links could have the behavior of analyzing the path taken by the user, data access, and output presentation.

The business object lets you concentrate on the functionality associated with each business use case, such as registering a user or buying an airline ticket. Using this scheme, you can develop business objects with decreased dependencies on the presentation layer, allowing them to undergo strenuous unit testing that is independent of the graphical interface.

Developing business objects and validation
You can load a business object reflectively, based upon the action type passed to the proxy servlet. The action type can be passed as an input parameter, a directory within the URL, or through some alias. This is left to the reader for further investigation.

Once the action type is identified, the business object can be instantiated. The proxy servlet can initialize the business object as follows:


try{
businessObject.init( ... );
} catch (ExampleException e){
exceptionHandler.handleException( e);
}

In Part 1 of this series, I demonstrated that you could create these business objects by searching for classes related to the action name that is attached to the page using reflection. Let's assume I've already created a business object for this example.

The BusinessObject interface defines an init method. This method takes a Hashtable of the input parameters passed to the servlet. Using this Hashtable, the business object can retrieve the data from the servlet without relying on the HttpServletRequest object, thereby reducing the dependency on the deployment platform.

You can develop a hierarchy of business objects to create greater functionality with less code. Because the proxy servlet uses the Class object's forName method to find the business object, no logic is needed to keep track of which business object to load. In other words, you don't need to write code that analyzes the action name and determines whether or not that name is associated to a form or a page.

So, you can invoke the RegisterUser business logic through the action RegisterUser. Your application need not understand that this was achieved through a link -- it is performed implicitly through reflection. In fact, a one-to-one correlation between business logic and action is not necessary, depending on the required business rules.

My example application will process user input through a form and display report output to the user. I've developed some simple objects to present this simple framework. Their names and behaviors are shown below:

 

  • DefaultBusinessObject:
    •  

    • Validates user input
    • Processes input
    • Present output

     

  • ReportBusinessObjectextends DefaultBusinessObject
    •  

    • Queries database
    • Displays output to the user

For now, I'll assume that BusinessServices has loaded the proper object. I'll elaborate on how to load these objects when I develop the exception handling below. For now, the key is to understand BusinessRules and how you can use them to achieve a high degree of functionality without a lot of coding.

I'll use a registration page to illustrate the benefits of using this framework to handle business rules. Let's assume that the registration page has six fields with the following properties:

 

Field NameField TypeMandatory?
First NameTextYes
Last NameTextNo
Email AddressEmailYes
CityTextYes
StateStateNo
CountryTextYes

 

You can write a business object that validates the fields on the page and ensures that they satisfy the business rules associated with the registration action. In this case, three different objects can handle the validation: the email validator, the text validator, and the state validator. These three validation objects are linked into the framework using reflection. The business object will reflectively load each validator as called for in the property table above. This will ensure that the fields are validated properly without requiring the business object developer to hardcode the fields present on a given form page. The power of this reflective approach lies in the fact that a single business object can perform the validation for many, if not all, of the pages in the application. This reduces the development and testing time because the validation code is effectively isolated from the application.

This is a simple example that demonstrates how the framework can handle business rules reflectively. A more complex example would include the validation of groups of interrelated fields, such as a Credit Card group with card number and expiration date fields. These group validation objects can also fit into the proposed framework by adding additional fields to the property tables.

You can use different mechanisms to load the business rules into the framework. One simple scheme involves the use of a static class called BusinessRules. This class will load and store all the business rules so that they can be referenced by the application as static variables. Here's an example:

 


// initialization of the hashtable
public static Hashtable hRegisterUserRules = new Hashtable();

// a lookup value to enter/find the field names
// - makes the interface cleaner to use.
public static final String sFirstName = "FirstName";

static {
// create a rule object
Rule rule = new Rule();

// set the validation
Rule.setValidation(Rule.TEXT);

      // indicate whether or not the field is required
      Rule.setMandatory(true);

// add the rule to the hashtable with the fieldname as the key
      hRegisterUserRules.put(sFirstName, rule);
}

This is one example of loading the rules from a static class. A more developed framework could load the rules from a properties file or database, thereby providing the development team with a more flexible means to modify the behavior of the application.

Let's assume that the other fields from the RegisterUser action have been entered into the BusinessRules class as shown above. We can now go on to discuss the reusable validation mechanism proposed for this framework. That validation would be contained in the BusinessObject, which contains a protected method called isValid. This method is responsible for coordinating the loading of the BusinessRules associated with the action being performed and ensuring that the fields passed by the user conform to those rules.

At this point, the development team must choose how it will handle multiple-paged input forms. Should a single action for each page be assumed, or should actions be able to span across pages? In the example I've described here, there is no limitation on the location of fields on one form or another. The data object will catch any missing fields when attempting to write the results to the database. This gives the team developing the business objects complete flexibility in the user interface design.

Having made that design decision, I'll illustrate how you can find the BusinessRules object using reflection. What follows is a simple code fragment that illustrates the reflective loading of a class variable as an object rather than a set of values.

 


private Hashtable loadBusinessRules(String sActionRules) throws UnexpectedException
{
// Initialize the rules table to null
Hashtable rulesTable = null;

try {
// Load the BusinessRules Class using the static variable
            // containing the fully qualified path value.
Class c = Class.forName( BusinessRules.BUSINESSRULES);

// --- Get the hashtable based upon the action name ---//
// --- (using reflection) --- //
// The loaded class (BusinessRules) has a field such as
            // RegisterUserRules that we try to load.

// If the load is successful,
            // we call get(null) to load the actual object rather than the value
// We cast the value as a Hashtable and return it to the user.
rulesTable = (Hashtable) c.getField(sActionRules).get(null);

} catch (Exception e) {

// The hashtable was not found.  All actions will have some form of
// rule interface.

throw new UnexpectedException(
                 "No business rules exist for this Action," + sActionRules);
}

return rulesTable;
}

In this case, you're loading the entire Hashtable from the BusinessRules class. This will allow you to use the methods contained within the Hashtable object to perform validation.

Here's a code sample from the isValid method, illustrating how the validation might be performed:

 


// Load the rules table from the business rules object
// this method may be moved to a utility class later!
hRulesTable = loadBusinessRules(sActionName);

// Retrieve the keys from the businessData hashtable passed
            // into the isValid method.
Enumeration enumKeys = hBusinessData.keys();

// Initialize the rule object
Rule rule = null;

// While there are still keys remaining in the business data
while (enumKeys.hasMoreElements() ) {
// Store the key name
String fieldName = (String) enumKeys.nextElement();

// Store the user-entered value
String userValue = (String) hBusinessData.get( fieldName);

// Retrieve the rule from the rules table
Rule rule = (Rule) hRulesTable.get(fieldName);

// the field is in the mandatory hash table
if ( rule != null) {
                  // the field is mandatory, either because it is required

// retrieve rules
String fieldType = rule.getValidation();
boolean bFieldIsRequired = rule.isMandatory();

                        // trim the extra spaces from the field
userValue = userValue.trim();

// check if the field is blank
boolean fieldNotBlank = (userValue.length() > 0);

if ( fieldNotBlank) {
// if not blank, perform field-level validation
isFieldValid =
                                validateField( fieldName, fieldType, userValue);
} else if ( fieldIsRequired) {
// if the field is blank, it may or may
                              // not be valid, depending on
// whether or not it is required.

// If the field is required, it is not valid
// If the field is not required, it is valid
isFieldValid = false;
}

// If the field is not valid, a missing tag will appear
if ( !isFieldValid) {
setError( fieldName, businessData);
}

} else { // the field is not contained in the hashtable

// If this is not a field we care about
if ( ignoreField( fieldName) )
{
// just ignore here
} else {
// the value is missing from the table
// there's a BIG problem!!!

throw new DataProcessingException(
                                 "missing key value for key "
                                 + fieldName
                                 + " in rules table" );
}
}

// if there's an error, add the error to the businessData

// The return value is the previous value AND the current
                  // field-level value
returnValue = returnValue && isFieldValid;

// reset the field-level validation for the next iteration
isFieldValid = true;
}  
return returnValue;
}

That's a lot of code, but it is completely independent of the individual forms it validates. Let's step through it quickly. Initially, the business rules corresponding to the form are loaded using the previously described loadBusinessRules method. Having loaded the Hashtable containing the rules, you retrieve the keys corresponding to the business data that the form supplies. You'll loop over all the keys in the Enumeration and retrieve the name-value pairs for validation. Once the name is retrieved, you can use it to find the corresponding rule in the Hashtable. If you find the rule in the Hashtable, you can determine whether it is blank, or required. If the field is not blank, the validateField method is called to perform validation.

The validateField method also uses reflection to load the validation object associated with the field being validated. For example, the email field has an EmailValidation class. This class would be responsible for using some basic rules to determine whether the field entered resembles an email address -- if the at symbol (@) is present, for instance. I have not elaborated on this validation in this article because it is fairly self-explanatory.

This validation framework reduces the interdependencies of the teams developing the business objects and those developing the presentation layer. The development teams must agree upon a naming convention for all the fields used in the HTML in order to ensure proper application behavior. It is important that your development team agree on the field names early in order to proceed with development. As more actions are added to the system, you would only need to develop additional rules (which will likely be contained in property files anyway) and any additional validation rules, if necessary. In other words, little development would be necessary.

Error handling
Perhaps the most important aspect of this servlet framework is its support for a unified error-handling scheme. With such a scheme, it is possible to build a sophisticated scheme to handle errors around the main proxy.

Development of error handling
As described above, the proxy servlet calls the business objects. The business objects are then initialized and begin performing their associated processes. The call to initialize the business object is present within a try-catch block that will catch the ExampleException. This exception will result in a call to the exception handler to perform the necessary processes associated with the exception.

For example, should a database exception occur, an email could be sent to the party responsible for database support. In fact, exceptions could be given classifications ranging from warning to critical, with behaviors attached to each category.

Let's assume that the database failure category is classified as critical. The DatabaseFailureException would extend the CriticalException, which would then extend the ExampleException. In this manner, the more specific behavior could build upon the generic error handling.

The following code samples show the progression from generic error handling to the specific handling of the database failure.


Public abstract class ExampleExceptionHandler
{
public abstract void handleException
         throws ExampleException(ExampleException e);
}

The ExampleExceptionHandler class is an abstract class; it is used as a base exception handler that you can extend. This class may contain functionality, should your application require it.


public class CriticalExceptionHandler extends ExampleExceptionHandler {
public void handleException throws ExampleException(CriticalException e) {
// Send email to the application support team
sendMail("mcymerman", "error", e.getMessage() );
}

public void sendMail(String to, String subject, String message) {
// normal commands to send email
...
}
}

The critical exception handler by default sends an exception to the application support team to ensure that the application is monitored.


public class DatabaseFailureExceptionHandler extends CriticalExceptionHandler{
  public void handleException(DatabaseFailureException e)
    throws ExampleException
  {
// send mail to the database support team
sendMail("dba", "error", "fix the database please "+ e.getMessage() );
  }
}

Should the application encounter a database failure, the database failure exception handler will be instantiated, as I'll explain. The simple handler developed for this article will send email to the database support personnel to ensure that they are monitoring the application database.

Now that I've developed the simple error-handling framework, I'll explain how the exception handlers are instantiated and invoked. As I've mentioned earlier, the exception service will retrieve the class name of the specific exception that has been thrown using the getName() method of the Class object.

This method will return a string with the fully qualified name. The ExceptionService will use this name to dynamically load the exception handler associated with this exception, as follows:

 


try {
ExceptionHandler exceptionHandler = loadExceptionHandler( exception);
...
} catch ( ClassNotFoundException cnfe){
// load default exception handler
...
}

The ExceptionHandler is loaded dynamically, based on the objects' naming convention. It's true that this scheme limits your naming choices; however, the resulting design is greatly simplified. The code in the try-catch block shown above uses a private method in the exception service to load the corresponding exception-handling code. This method can be developed as shown below:


private ExceptionHandler loadExceptionHandler( String sExceptionName, Exception exception)
{
      String HANDLER = "Handler"; // Should come from a properties file
String sClassName = null;

if (sExceptionName == null && exception == null) {
// load the default exception handler and return
...
}

      if ( sExceptionName == null) {
      // the sExceptionName is passed in from the parent class
Class classException = exception.getClass();

sClassName = classException.getName();
} else {

sClassName = sExceptionName;
      }

// The string to load is the name of the exception plus the string
      // identifying it as a handler
      String sExceptionHandler =  classException.getName()+ HANDLER;

ExceptionHandler ehExceptionHandler = null;

try{
// attempt to load the exception handler using the class.forName
      } catch (ClassNotFoundException exClassNotFound){

  // Get the name of the parent class of the exception
  sExceptionHandler = classException.getSuperClass().getName();

  if ( sExceptionHandler != DEFAULTEXCEPTIONHANDLER ){
    //  load the parent class exception handler          
          //  (this is a recursive method,
          //  until the DefaultExceptionHandler is returned)
    loadExceptionHandler( sExceptionHandler, exception);

        }else{
    // load the default error handler
        }
      }

      return ehExceptionHandler;
}

This exception-handling framework lets you build up your exception handling to the extent necessary to achieve your required functionality. The exception handler will work its way up the exception handler hierarchy and load the most specific exception handler. Should it traverse the entire tree up to the DefaultExceptionHandler, it will load that handler. What have you gained through this framework? You now have the confidence that an exception handler will handle the exception. You also have the ability to handle specific errors, such as database failures, differently than presentation failures, through custom ExceptionHandlers. The processing required to load the different exception handling routines is handled through centralized code, thereby reducing the project's coding effort.

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.

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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值