Ask a question
-
font size
decrease font size
increase font size
4.1 Reuse in Web Applications
Reusability is a driving factor in object-oriented languages, including Java. Java is full of built-in frameworks meant to promote reuse. JavaBeans provides an explicit component model, while Swing and AWT both provide reusable widget libraries. Throughout the software industry, the adoption of object-oriented programming languages in the 90s led to across-the-board development of a number of frameworks for reuse.
Sadly, there is no standard component framework in the relatively young field of J2EE web applications. While the JavaBeans component model is often used to communicate model data, there is no universal mechanism for reusing parts of views or controllers. Until quite recently, web application developers have had to build their own framework or settle for applications that were not intrinsically reusable. This problem has been recently recognized, and frameworks such as Struts[2] have gained popularity as a solution (see the sidebar "Frameworks and Patterns"). These frameworks allow controllers and views to be developed using standard interfaces and connected in standard ways.
Frameworks and Patterns
Most web application frameworks implement at least the MVC pattern, and many even claim to be "pattern-based." In general, frameworks force applications written to them to conform to certain patterns. Recognizing these patterns can be a big help in designing applications that work well in a given framework. Here's a look at two popular frameworks and the patterns they implement:
· Jakarta Struts is probably the best-known web application framework. It is based on what they call a "Model 2" approach, an implementation of MVC in which you write the model and view components, and Struts provides the controller. The Struts controller is based on the Service to Worker pattern, featuring a front controller, a dispatcher configured via XML files, and a code framework for embedding application logic into actions. Struts also provides a number of utility classes and tag libraries, including Tiles, an implementation of the Composite View pattern.
· JavaServer Faces (JSF) is a new web application framework from Sun. The primary goal of JSF is to provide a UI framework for web applications, allowing the development of custom, reusable UI widgets that maintain state across requests. As such, JSF is centered around the Composite View pattern, and includes an extensive tag library for developing and using these components. JSF also provides a simple implementation of the Service to Worker pattern, including a front controller, an XML-configured navigation handler, and support for actions.
4.2 Extending the Controller
In the last chapter, we discussed how to best add features to a front controller. The main problem was that adding new features directly to the controller specializes it. A specialized controller quickly becomes unwieldy, with lots of code for each specific page. Every time we wanted new functionality, we had to rebuild, retest, and redeploy the entire controller. To solve this problem, we looked at the Decorator pattern, which allowed us to dynamically add functionality to the front controller.
With decorators in place, common functions such as security are separated from the front controller. But the controller is still responsible for managing page navigation as well as instantiating actions. Since the set of pages and the set of available actions are likely to change, a tight coupling between them and the front controller is a bad idea. Similar to decorators, we would like to separate the specialized bits of the code for actions and navigation into their own classes and leave the major objects—like the front controller—to act as frameworks.
Here's an example. Imagine a simple, multistep form, such as an online mortgage application.[3] If each page in the form contained embedded references to the next page, it would be very difficult to change the page order. Each affected page, as well as those that came before and after it, would need to be modified to reflect the change. Even worse, these pages could not be reused. If the first page of our mortgage application took basic name and address information, we might want to use the same view in a credit card application. Unfortunately, we can't, since the first mortgage page contains a reference to the second mortgage page: there's no way to direct it to the credit card application instead. This inflexibility stems from the tight coupling of views to controllers.
Yes, we know that in reality these are anything but simple!
To build a flexible application, we need to decouple the pages and controllers. We need the flexibility to add, remove, and reorder pages, reusing them at will. Ideally, we want to do all of this at runtime, while preserving the view-controller separation. The Service to Worker pattern can help us.
4.2.1 The Service to Worker Pattern
The Service to Worker pattern is based on both the Model-View-Controller pattern and the Front Controller pattern. The goal of Service to Worker is to maintain separation between actions, views, and controllers. The service, in this case is the front controller, a central point for handling requests. Like in the Front Controller pattern, the service delegates updating the model to a page-specific action called the worker. So far, Service to Worker is the same as the Front Controller pattern.
In the Service to Worker pattern, an object called the dispatcher performs the task of managing workers and views. The dispatcher encapsulates page selection, and consequently, worker selection. It decouples the behavior of the application from the front controller. To change the order in which pages are shown, for example, only the dispatcher needs to be modified.
The Service to Worker pattern is shown in Figure 4-1. It looks like the Front Controller pattern, with the addition of the dispatcher.

The controller provides an initial point of entry for every request (just like in the Front Controller pattern). It allows common functionality to be added and removed easily, and it can be enhanced using decorators.
The dispatcher encapsulates page selection. In its simplest form, the dispatcher takes some parameters from the request and uses them to select actions and a view. This type of simple dispatcher may be implemented as a method of the front controller. In addition to the requested page, the dispatcher often takes several other factors into account when choosing the next view, the current page, the user permissions, and the validity of the entered information. This type of dispatcher is implemented as a separate class, and ideally, it is configurable via a file at runtime.
The dispatcher uses a set of actions to perform model updates. Each action encapsulates a single, specific update to the model, and only that update. An action might be something as simple as adding a row to a database table, or as complex as coordinating transactions across multiple business objects. Because all navigation is encapsulated in the dispatcher, the actions are not responsible for view selection. As in the Front Controller pattern, actions are usually implemented as an instance of the GoF Command pattern.
The end result of the Service to Worker pattern is an extensible front controller. The controller itself is simply a framework: decorators perform common functions, actions actually update the model, and the dispatcher chooses the resulting view. Because these pieces are loosely coupled, each can operate independently, allowing extension and reuse.
4.2.2 Service to Worker in J2EE
As an example of Service to Worker, let's think about a workflow.[4] A workflow is a sequence of tasks that must be performed in a specific order. Many environments have workflows, such as the mortgage application we discussed earlier and the "wizards" that guide a user through installing software.
This example is based loosely on a proposed workflow language for the Struts framework, part of the Apache project. More information is available from http://jakarta.apache.org/struts/proposal-workflow.html.
Our workflow is very simple, and it is based on our previous examples. It consists of three web pages: one asking the user to log in, another setting a language preference, and a third displaying a summary page. This workflow is shown in Figure 4-2.

While it would be easy enough to build this workflow based on the Front Controller pattern, we would like the result to be extensible. We want to be able to add pages and actions without changing the front controller. Ideally, we would like to specify our workflow in XML, so we could change the order of pages without even modifying the dispatcher. Our XML might look like:
<?xml version="1.0" encoding="UTF-8"?>
<workflow>
<state name="login" action="LoginAction"
viewURI="login.jsp" />
<state name="language" action="LanguageAction"
viewURI="language.html" />
<state name="display" action="RestartAction"
viewURI="display.jsp" />
</workflow>
The dispatcher, then, is a simple state machine. When a request is submitted, the dispatcher determines the current state of the session and runs the corresponding action. If the action succeeds, there is a transition to the next state. Once the state is determined, control is forwarded to the associated view.
The interactions in a J2EE Service to Worker implementation are shown in Figure 4-3. To explain all these pieces, we'll work backward, starting with models and views and ending with the front controller.

4.2.2.1 Models and views
The models and views in the Service to Worker pattern remain more or less unchanged from the MVC and Front Controller patterns. As in Chapter 3, our model is the UserBean. The interface has been extended slightly from earlier examples to include a "language" attribute:
package s2wexample.model;
public interface UserBean {
// the username field
public String getUsername( );
public void setUsername(String username);
// the password field
public String getPassword( );
public void setPassword(String password);
// the language field
public String getLanguage( );
public void setLanguage(String language);
// business methods to perform login
public boolean doLogin( );
public boolean isLoggedIn( );
}
The views are simple JSP pages that read the model data from our UserBean. Our login page is virtually unchanged from the earlier example. Example 4-1 shows the login page.
Example 4-1. login.jsp
<%@page contentType="text/html"%>
<jsp:useBean id="userbean" scope="session"
class="s2wexample.model.UserBean" />
<html>
<head><title>Login</title></head>
<body>
<br><br>
<form action="/pages/workflow" method="post">
Username:<input type="text" name="username"
value=<jsp:getProperty name="userbean" property="username"/>>
<br>
Password: <input type="password" name="password">
<br>
<input type="submit" value="Log In">
</form>
</body>
</html>
The only thing to notice here is that our form action is now /pages/workflow. This will be the target of all our links, such as those in the second page, language.html. Since this page does not require any dynamic data, it is stored as a plain HTML file, shown in Example 4-2.
Example 4-2. language.html
<html>
<head><title>Language Selection</title></head>
<body>
<br><br>
<form action="/pages/workflow" method="get">
Language: <select name="language">
<option value="En">English</option>
<option value="Fr">French</option>
</select>
<br>
<input type="submit" value="Continue">
</form>
</body>
</html>
4.2.2.2 Actions
Actions, as we mentioned earlier, are implemented as instances of the command pattern. All of our actions will share a simple interface:
public interface Action {
public boolean performAction(HttpServletRequest req,
ServletContext context);
}
This interface gives each action full access to the request, as well as the servlet context, which includes the HTTP session. Each action reads a different set of parameters from the request. The action then updates the model data, which is accessible through either the request or the session. If the performAction( ) method returns true, the dispatcher knows to move on to the next state. If not, the same state is repeated.
Example 4-3 shows the LoginAction, which is called with input from the login page. This action is also responsible for creating the initial UserBean if it does not exist.
Example 4-3. LoginAction.java
package s2wexample.controller.actions;
import s2wexample.controller.*;
import s2wexample.model.*;
import javax.servlet.http.*;
import javax.servlet.*;
public class LoginAction implements Action {
public static final String USERBEAN_ATTR = "userbean";
private static final String NAME_PARAM = "username";
private static final String PASSWORD_PARAM = "password";
public boolean performAction(HttpServletRequest req,
ServletContext context) {
// read request parameters
String username = req.getParameter(NAME_PARAM);
String password = req.getParameter(PASSWORD_PARAM);
// find the UserBean, create if necessary
HttpSession session = req.getSession( );
UserBean ub = (UserBean)session.getAttribute(USERBEAN_ATTR);
if (ub == null) {
ub = UserBeanFactory.newInstance( );
session.setAttribute(USERBEAN_ATTR, ub);
}
// try to login, return the result
ub.setUsername(username);
ub.setPassword(password);
return ub.doLogin( );
}
}
4.2.2.3 The dispatcher
Dispatchers, like actions, come in many flavors. There may be a single master dispatcher or different dispatchers for different parts of an application. Usually, there is one default dispatcher and a few more specialized ones to handle special cases like wizards. In any case, we will define a simple Dispatcher interface to allow uniform access to all the possible dispatchers:
public interface Dispatcher {
// called after initialization
public void setContext(ServletContext context)
throws IOException;
// called for each request
public String getNextPage(HttpServletRequest req,
ServletContext context);
}
The dispatcher must build and store a simple state machine for each user. As requests come in, the dispatcher retrieves the current state and uses it to determine the correct action to run. If the action succeeds, it displays the view associated with the next state.
Our dispatcher must also process the XML state data. When we see an action attribute, we will convert it to a Java class by loading the class named nameAction, where name is the value of the action attribute.
Internally, we will use simple Java objects to model the states of the workflow. The WorkflowDispatcher is shown in Example 4-4.
Example 4-4. WorkflowDispatcher
package s2wexample.controller;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
public class WorkflowDispatcher implements Dispatcher {
// tags expected in the XML
private static final String WORKFLOW_TAG = "workflow";
private static final String STATE_TAG = "state";
private static final String NAME_ATTR = "name";
private static final String ACTION_ATTR = "action";
private static final String VIEW_ATTR = "viewURI";
// where to find action classes
private static final String ACTION_PREFIX =
"s2wexample.controller.actions.";
// the internal model of a workflow state
class State {
protected String name;
protected Action action;
protected String viewUri;
}
// the current state and state list
private State[] states;
private int currentState;
// called by the controller after initialization
public void setContext(ServletContext context)
throws IOException {
InputStream is =
context.getResourceAsStream("/LanguageWorkflow.xml");
try {
states = parseXML(is);
} catch(Exception ex) {
throw new IOException(ex.getMessage( ));
}
currentState = 0;
}
// choose the next state
public String getNextPage(HttpServletRequest req,
ServletContext context) {
State s = states[currentState];
// increment the state only if the action suceeds
if ((s.action == null) ||
s.action.performAction(req, context)) {
if (currentState < states.length - 1) {
s = states[++currentState];
} else {
currentState = 0;
s = states[currentState];
}
}
return s.viewUri;
}
// parse a state XML file
private State[] parseXML(InputStream is) throws Exception {
DocumentBuilderFactory factory =
DocumentBuilderFactory.newInstance( );
DocumentBuilder builder = factory.newDocumentBuilder( );
Document doc = builder.parse(is);
// find the workflow element
NodeList workflows = doc.getElementsByTagName(WORKFLOW_TAG);
Element workflow = (Element)workflows.item(0);
// find all the states
NodeList states = doc.getElementsByTagName(STATE_TAG);
State[] stateList = new State[states.getLength( )];
// read state information
for(int i = 0; i < states.getLength( ); i++) {
stateList[i] = new State( );
Element curState = (Element)states.item(i);
stateList[i].name = curState.getAttribute(NAME_ATTR);
stateList[i].viewUri = curState.getAttribute(VIEW_ATTR);
// convert actions names into class instances
String action = curState.getAttribute(ACTION_ATTR);
if (action != null && action.length( ) > 0) {
Class c = Class.forName(ACTION_PREFIX + action);
stateList[i].action = (Action)c.newInstance( );
}
}
return stateList;
}
}
4.2.2.4 The front controller
Last but not least is the front controller, which is more of a framework than a container for actual application logic at this point. The controller's job right now is to manage dispatchers, using them to choose the next view. Once the view is chosen, the front controller passes control to the view, and the front controller's job is finished. Example 4-5 shows our front controller servlet.
Example 4-5. FrontController.java
package s2wexample.controller;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import s2wexample.model.*;
public class FrontController extends HttpServlet {
private static final String DISPATCHER_ATTR = "Dispatcher";
private static final String DISPATCHER_PREFIX =
"s2wexample.controller.";
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
// process get requests
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
process(request, response);
}
// process post requests
public void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
process(request, response);
}
// common processing routine
public void process(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession( );
ServletContext context = getServletContext( );
// get the last element of the request in lower case
String reqPath = request.getPathInfo( );
reqPath = Character.toUpperCase(reqPath.charAt(1)) +
reqPath.substring(2).toLowerCase( );
// find the dispatcher in the session
Dispatcher dispatcher =
(Dispatcher)session.getAttribute(reqPath +
DISPATCHER_ATTR);
// if no dispatcher was found, create one
if (dispatcher == null) {
String className = reqPath + "Dispatcher";
try {
Class c = Class.forName(DISPATCHER_PREFIX +
className);
dispatcher = (Dispatcher)c.newInstance( );
} catch(Exception ex) {
throw new ServletException("Can't find class " +
className, ex);
}
// store the dispatcher in the session
dispatcher.setContext(context);
session.setAttribute(reqPath + DISPATCHER_ATTR,
dispatcher);
}
// use the dispatcher to find the next page
String nextPage = dispatcher.getNextPage(request, context);
// make sure we don't cache dynamic data
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
// forward control to the view
RequestDispatcher forwarder =
request.getRequestDispatcher("/" + nextPage);
forwarder.forward(request, response);
}
}
Notice that the front controller manages dispatchers in the same way that the dispatchers manage actions. Dispatchers are mapped based on the requested URL. Since all our views used the path /pages/workflow in their requests, the front controller maps these requests to an instance of the WorkflowDispatcher class. As a result, the same application could use many different dispatchers for different parts of the web site. In practice, the mapping between URLs and dispatchers is usually done with an XML file, just like for actions.
Using the Service to Worker pattern, the controller has been divided up into a set of reusable components: a front controller, dispatchers, and actions. Adding a new page is dynamic: you simply create the view JSP and corresponding action class and add it all to an XML file. To add, remove, or reorder pages, we only need to change the XML.
The Service to Worker pattern, as we have presented it, is quite flexible. The simple navigation model of our example, however, is only really appropriate for workflows. Even branches to the linear flow of pages are not covered. We won't cover all the possible dispatchers and actions here. Both the Jakarta Struts project and Sun's J2EE sample applications contain more advanced implementations of the Service to Worker patterns, and they are a good place to look for more information.
4.3 Advanced Views
Until now, we have treated the view as a black box, assuming that a single JSP page will convert our model data into HTML to send to the user. In reality, this is a tall order. Large JSP pages with lots of embedded code are just as unwieldy as a large front controller. Like the controller, we would like to break the view into a generic framework with the specialized pieces separated out.
The challenge is to break up the view within the restricted programming model of JSP. Remember that one of our goals was to minimize embedding code in our JSP pages, since it blurs the line between the view and the controller. Thus, we will avoid the kinds of classes and interfaces we used to solve the same problem in the controller.
Fortunately, JSP gives us a different set of tools to work with: JSP directives and custom tags. We will use both of these extensively in the next two patterns to help separate the view into reusable components.
4.3.1 The View Helper Pattern
One mechanism for reducing specialization in views is a view helper. A view helper acts as an intermediary between the model and the view. It reads specific business data and translates it, sometimes directly into HTML, and sometimes into an intermediate data model. Instead of the view containing specialized code to deal with a particular model, the view includes more generic calls to the helper. Figure 4-4 shows how a view uses helpers.

View helpers increase reusability in two ways: by reducing the amount of specialized code in a view, helpers make views more reusable; and, since a helper encapsulates a specific kind of interaction with the model, helpers can be reused themselves.
4.3.2 Implementing a View Helper
When you think about view helpers in JSP, custom tags should immediately pop to mind. Conceptually, custom tags fit the bill—they adapt Java objects into JSP markup. Moving code embedded in JSP into custom tag classes reduces coupling, since the tag defines a clear interface independent of the underlying objects. And since tags are grouped into libraries by function, they are inherently quite reusable themselves. While it is easy to think of all custom tags (or even all tags) as view helpers, they are not the same thing. A view helper is a tag, or set of tags, that translates model data into a convenient form for the view.
A view helper may read business data in many forms, including JavaBeans, Enterprise JavaBeans, direct database access, or access to remote web services. For our example, let's look at the last of these: accessing a remote web service.
Really Simple Syndication (RSS)[5] is an XML format that is the de facto standard for web sites to exchange headline information. RSS files are generally available via public HTTP from most news sites, at the very least. Example 4-6 shows a slightly simplified RSS file for a made-up news page, PatternsNews. (For simplicity, we have stuck to the 0.91 version of RSS. The current version, 2.0, is far more complicated.)
RSS is an actively evolving standard. For a good introduction and history, see http://backend.userland.com/rss.
Example 4-6. PatternsNews.xml
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE rss PUBLIC "-//Netscape Communications//DTD RSS 0.91//EN"
"http://www.scripting.com/dtd/rss-0_91.dtd">
<rss version="0.91">
<channel>
<title>Patterns news</title>
<link>http://www.patternsnews.com</link>
<item>
<title>Local pattern-creators honored</title>
<link>http://www.patternsnews.com/stories?id=0001</link>
</item>
<item>
<title>Patterns solve business problem</title>
<link>http://www.patternsnews.com/stories?id=0002</link>
</item>
<item>
<title>New patterns discovered!</title>
<link>http://www.patternsnews.com/stories?id=0003</link>
</item>
</channel>
</rss>
We would like to read RSS files and include their headlines on our own web site. In order to do this, we will build a view helper that makes the RSS data available as a JSP custom tag. Figure 4-5 shows the pieces we will build.

4.3.2.1 Parsing the RSS
For starters, we need to parse the RSS into a Java format that we can use. To do this, we create an RSSInfo class that parses RSS files and stores them as Java objects. We won't go into the specifics of how the parsing works, since the innards look a lot like the DOM-based XML parser we built for the last example. Example 4-7 shows the interface to the RSSInfo class.
Example 4-7. The RSSInfo interface
public interface RSSInfo {
// channel information
public String getChannelTitle( );
public String getChannelLink( );
// item information
public String getTitleAt(int index);
public String getLinkAt(int index);
public int getItemCount( );
// parse the RSS file at the given URL
public void parse(String url) throws Exception;
}
The RSSInfo object represents an intermediate data structure: it is not exactly the underlying data, but also not exactly what we are using in the application. Building an intermediate data model may seem inefficient, but it can help significantly in reusability. Since the RSSInfo class is independent of any type of display mechanism, it can be used in many contexts: JSP custom tags, servlets, web services, etc. If the parsing was implemented directly as custom JSP tags, the logic would have to be duplicated.
4.3.2.2 Using the RSS: Custom tags
Now that we have a generic method for parsing RSS, we would like to use it in a JSP environment. We could store the RSSInfo object in the session and access it directly using JSP directives. While this option is quite flexible, it embeds a fair amount of logic for iteration and such in the JSP.[6] This logic would have to be rewritten for each page that used the RSS parser. As another option, we could create a custom tag that read the RSS and returned a preformatted table. This method has the advantage of being easy, since only one line of JSP is needed to include all the headlines. Unfortunately, it would mean including our page styles in the custom tag, and we would be unable to reuse the tag on a different page.
Version 2.0 of the JSP spec adds an expression language and standard tag library, which greatly reduce the need for embedded scripts in these situations. This does not, however, provide an excuse for manipulating ill-suited business objects directly in JSP.
The best solution in this case is a hybrid: we will design custom tags to parse and iterate through the RSS data, and expose JSP scripting variables with the relevant values. This solution allows us the flexibility to format the output any way we want, while performing the heavy lifting in the custom tag logic. To format the RSS data as a table, for example, we would like to have our JSP look something like Example 4-8.
Example 4-8. JSP custom tags in action
<%@page contentType="text/html"%>
<%@ taglib prefix="rss" uri="/ReadRSS" %>
<html>
<head><title>RSS Results</title></head>
<body>
<rss:RSSChannel URL="http://www.patternsnews.com/patternsnews.xml">
<b><a href="/<%= channelLink %>"><%= channelName %></a></b>
<br>
<table>
<rss:RSSItems>
<tr><td><a href="/<%= itemLink %>"><%= itemName %></a></td></tr>
</rss:RSSItems>
</table>
</rss:RSSChannel>
</body>
</html>
Here, our view helper consists of two custom tags. The first, RSSChannel, takes a URL corresponding to an RSS file and downloads and parses that file using the RSSInfo class. It exposes the channel title and channel link information in two scripting variables: channelTitle and channelLink. The second tag, RSSItems, performs a similar task, repeating its body for each item in turn and exposing the itemTitle and itemLink variables. The helper (the tags) is therefore responsible for creating and interpreting the intermediate data model (the RSSInfo).
The RSSChannel tag source is shown in Example 4-9.
Example 4-9. RSSChannelTag.java
import javax.servlet.*;
import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;
public class RSSChannelTag extends BodyTagSupport {
// scripting variable names
private static final String NAME_ATTR = "channelName";
private static final String LINK_ATTR = "channelLink";
// the input parameter
private String url;
// the RSS parser
private RSSInfo rssInfo;
public RSSChannelTag( ) {
rssInfo = new RSSInfoImpl( );
}
// called with the URL parameter from the tag
public void setURL(String url) {
this.url = url;
}
// used by the RSSItemsTag
protected RSSInfo getRSSInfo( ) {
return rssInfo;
}
// parse the RSS and set up the scripting variables
public int doStartTag( ) throws JspException {
try {
rssInfo.parse(url);
pageContext.setAttribute(NAME_ATTR, rssInfo.getChannelTitle( ));
pageContext.setAttribute(LINK_ATTR, rssInfo.getChannelLink( ));
} catch (Exception ex) {
throw new JspException("Unable to parse " + url, ex);
}
return Tag.EVAL_BODY_INCLUDE;
}
}
The RSSItems tag is slightly more complicated, since it implements the IterationTag interface. It loops through the item data, setting the itemTitle and itemLink variables with every pass. RSSItems also uses the findAncestorWithClass( ) method to locate the RSSInfo object that was stored by the parent. The RSSItemsTag source is shown i
Read 381 times
Published in
Java
Vicky
E-mail: This e-mail address is being protected from spambots. You need JavaScript enabled to view itLatest from Vicky
More in this category:
« J2EE Design Pattern - The business Tier