 |
 |
Understanding JavaServer Pages Model 2 architecture
Exploring the MVC design pattern
Summary
By developing a familiar Web-based shopping cart, you'll learn how to utilize
the Model-View-Controller (MVC) design pattern and truly separate presentation
from content when using JavaServer Pages. Govind Seshadri shows you out how
easy it can be. (2,000 words)
By Govind Seshadri

Printer-friendly version | Mail this to a friend
espite
its relatively recent introduction, JavaServer Pages (JSP) technology is
well on its way to becoming the preeminent Java technology for building applications
that serve dynamic Web content. Java developers love JSP for myriad reasons.
Some like the fact that it brings the "write once, run anywhere" paradigm
to interactive Web pages; others appreciate the fact that it is fairly simple
to learn and lets them wield Java as a server-side scripting language. But
most concur on one thing -- the biggest advantage of using JSP is that it
helps effectively separate presentation from content. In this article, I
provide an in-depth look at how you can gain optimal separation of presentation
from content by using the JSP Model 2 architecture. This model can also be
seen as a server-side implementation of the popular Model-View-Controller
(MVC) design pattern. Please note that you should be familiar with the basics
of JSP and servlet programming before continuing on, as I do not address
any syntax issues in this article.
Server-Side Java: Read the whole series!
|
|
So, what's wrong with servlets?
While JSP may be great for serving up dynamic Web content and separating
content from presentation, some may still wonder why servlets should be cast
aside for JSP. The utility of servlets is not in question. They are excellent
for server-side processing, and, with their significant installed base, are
here to stay. In fact, architecturally speaking, you can view JSP as a high-level
abstraction of servlets that is implemented as an extension of the Servlet
2.1 API. Still, you shouldn't use servlets indiscriminately; they may not
be appropriate for everyone. For instance, while page designers can easily
write a JSP page using conventional HTML or XML tools, servlets are more
suited for back-end developers because they are often written using an IDE
-- a process that generally requires a higher level of programming expertise.
When deploying servlets, even developers have to be careful and ensure that
there is no tight coupling between presentation and content. You can usually
do this by adding a third-party HTML wrapper package like htmlKona
to the mix. But even this approach, though providing some flexibility with
simple screen changes, still does not shield you from a change in the presentation
format itself. For example, if your presentation changed from HTML to DHTML,
you would still need to ensure that wrapper packages were compliant with
the new format. In a worst-case scenario, if a wrapper package is not available,
you may end up hardcoding the presentation within the dynamic content. So,
what is the solution? As you shall soon see, one approach would be to use
both JSP and servlet technologies for building application systems. Differing philosophies
The early JSP specifications advocated two philosophical approaches for
building applications using JSP technology. These approaches, termed the
JSP Model 1 and Model 2 architectures, differ essentially in the location
at which the bulk of the request processing was performed. In the Model 1
architecture, shown in Figure 1, the JSP page alone is responsible for processing
the incoming request and replying back to the client. There is still separation
of presentation from content, because all data access is performed using
beans. Although the Model 1 architecture should be perfectly suitable for
simple applications, it may not be desirable for complex implementations.
Indiscriminate usage of this architecture usually leads to a significant
amount of scriptlets or Java code embedded within the JSP page, especially
if there is a significant amount of request processing to be performed. While
this may not seem to be much of a problem for Java developers, it is certainly
an issue if your JSP pages are created and maintained by designers -- which
is usually the norm on large projects. Ultimately, it may even lead to an
unclear definition of roles and allocation of responsibilities, causing easily
avoidable project-management headaches.

Figure 1: JSP Model 1 architecture
|
The Model 2 architecture,
shown in Figure 2, is a hybrid approach for serving dynamic content, since
it combines the use of both servlets and JSP. It takes advantage of the predominant
strengths of both technologies, using JSP to generate the presentation layer
and servlets to perform process-intensive tasks. Here, the servlet acts as
the controller and is in charge of the request processing and the
creation of any beans or objects used by the JSP, as well as deciding, depending
on the user's actions, which JSP page to forward the request to. Note particularly
that there is no processing logic within the JSP page itself; it is simply
responsible for retrieving any objects or beans that may have been previously
created by the servlet, and extracting the dynamic content from that servlet
for insertion within static templates. In my opinion, this approach typically
results in the cleanest separation of presentation from content, leading
to clear delineation of the roles and responsibilities of the developers
and page designers on your programming team. In fact, the more complex your
application, the greater the benefits of using the Model 2 architecture should
be.

Figure 2: JSP Model 2 architecture
|
In order to clarify the concepts
behind the Model 2 architecture, let's walk through a detailed implementation
of it: a sample online music store called Music Without Borders.
Understanding Music Without Borders
The main view, or presentation, for our Music Without Borders online store is facilitated by the JSP page EShop.jsp
(shown in Listing 1). You will notice that the page deals almost exclusively
with presenting the main user interface of the application to the client,
and performs no processing whatsoever -- an optimal JSP scenario. Also, notice
that another JSP page, Cart.jsp (shown in Listing 2), is included within EShop.jsp via the directive <jsp:include page="Cart.jsp" flush="true" />.
Listing 1:
EShop.jsp
|
<%@ page session="true" %>
<html>
<head>
<title>Music Without Borders</title>
</head>
<body bgcolor="#33CCFF">
<font face="Times New Roman,Times" size="+3">
Music Without Borders
</font>
<hr><p>
<center>
<form name="shoppingForm"
action="/examples/servlet/ShoppingServlet"
method="POST">
<b>CD:</b>
<select name=CD>
<option>Yuan | The Guo Brothers | China | $14.95</option>
<option>Drums of Passion | Babatunde Olatunji | Nigeria | $16.95</option>
<option>Kaira | Tounami Diabate| Mali | $16.95</option>
<option>The Lion is Loose | Eliades Ochoa | Cuba | $13.95</option>
<option>Dance the Devil Away | Outback | Australia | $14.95</option>
<option>Record of Changes | Samulnori | Korea | $12.95</option>
<option>Djelika | Tounami Diabate | Mali | $14.95</option>
<option>Rapture | Nusrat Fateh Ali Khan | Pakistan | $12.95</option>
<option>Cesaria Evora | Cesaria Evora | Cape Verde | $16.95</option>
<option>Ibuki | Kodo | Japan | $13.95</option>
</select>
<b>Quantity: </b><input type="text" name="qty" SIZE="3" value=1>
<input type="hidden" name="action" value="ADD">
<input type="submit" name="Submit" value="Add to Cart">
</form>
</center>
<p>
<jsp:include page="Cart.jsp" flush="true" />
</body>
</html>
|
Listing 2:
Cart.jsp
|
<%@ page session="true" import="java.util.*, shopping.CD" %>
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>
<center>
<table border="0" cellpadding="0" width="100%" bgcolor="#FFFFFF">
<tr>
<td><b>ALBUM</b></td>
<td><b>ARTIST</b></td>
<td><b>COUNTRY</b></td>
<td><b>PRICE</b></td>
<td><b>QUANTITY</b></td>
<td></td>
</tr>
<%
for (int index=0; index < buylist.size();index++) {
CD anOrder = (CD) buylist.elementAt(index);
%>
<tr>
<td><b><%= anOrder.getAlbum() %></b></td>
<td><b><%= anOrder.getArtist() %></b></td>
<td><b><%= anOrder.getCountry() %></b></td>
<td><b><%= anOrder.getPrice() %></b></td>
<td><b><%= anOrder.getQuantity() %></b></td>
<td>
<form name="deleteForm"
action="/examples/servlet/ShoppingServlet"
method="POST">
<input type="submit" value="Delete">
<input type="hidden" name= "delindex" value='<%= index %>'>
<input type="hidden" name="action" value="DELETE">
</form>
</td>
</tr>
<% } %>
</table>
<p>
<form name="checkoutForm"
action="/examples/servlet/ShoppingServlet"
method="POST">
<input type="hidden" name="action" value="CHECKOUT">
<input type="submit" name="Checkout" value="Checkout">
</form>
</center>
<% } %>
|
Here, Cart.jsp handles the presentation of the session-based shopping cart, which constitutes the model in our MVC architecture. Observe the scriptlet at the beginning of Cart.jsp:
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
if (buylist != null && (buylist.size() > 0)) {
%>
Basically, the scriptlet
extracts the shopping cart from the session. If the cart is empty or not
yet created, it displays nothing; thus, the first time a user accesses the
application, she is presented with the view shown in Figure 3.

Figure 3: Music Without Borders, main view
|
If the shopping cart is not
empty, then the selected items are extracted from the cart one at a time,
as demonstrated by the following scriptlet:
<%
for (int index=0; index < buylist.size(); index++) {
CD anOrder = (CD) buylist.elementAt(index);
%>
Once the variables describing
an item have been created, they are then simply inserted into the static
HTML template using JSP expressions. Figure 4 shows the application view
after the user has placed some items in the shopping cart.

Figure 4: Music Without Borders, shopping cart view
|
The important thing to observe here is that the processing for all actions carried out within either Eshop.jsp or Cart.jsp is handled by the controller servlet, ShoppingServlet.java, which is shown in Listing 3.
Listing 3:
ShoppingServlet.java
|
import java.util.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import shopping.CD;
public class ShoppingServlet extends HttpServlet {
public void init(ServletConfig conf) throws ServletException {
super.init(conf);
}
public void doPost (HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
HttpSession session = req.getSession(false);
if (session == null) {
res.sendRedirect("http://localhost:8080/error.html");
}
Vector buylist=
(Vector)session.getValue("shopping.shoppingcart");
String action = req.getParameter("action");
if (!action.equals("CHECKOUT")) {
if (action.equals("DELETE")) {
String del = req.getParameter("delindex");
int d = (new Integer(del)).intValue();
buylist.removeElementAt(d);
} else if (action.equals("ADD")) {
//any previous buys of same cd?
boolean match=false;
CD aCD = getCD(req);
if (buylist==null) {
//add first cd to the cart
buylist = new Vector(); //first order
buylist.addElement(aCD);
} else { // not first buy
for (int i=0; i< buylist.size(); i++) {
CD cd = (CD) buylist.elementAt(i);
if (cd.getAlbum().equals(aCD.getAlbum())) {
cd.setQuantity(cd.getQuantity()+aCD.getQuantity());
buylist.setElementAt(cd,i);
match = true;
} //end of if name matches
} // end of for
if (!match)
buylist.addElement(aCD);
}
}
session.putValue("shopping.shoppingcart", buylist);
String url="/jsp/shopping/EShop.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req, res);
} else if (action.equals("CHECKOUT")) {
float total =0;
for (int i=0; i< buylist.size();i++) {
CD anOrder = (CD) buylist.elementAt(i);
float price= anOrder.getPrice();
int qty = anOrder.getQuantity();
total += (price * qty);
}
total += 0.005;
String amount = new Float(total).toString();
int n = amount.indexOf('.');
amount = amount.substring(0,n+3);
req.setAttribute("amount",amount);
String url="/jsp/shopping/Checkout.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req,res);
}
}
private CD getCD(HttpServletRequest req) {
//imagine if all this was in a scriptlet...ugly, eh?
String myCd = req.getParameter("CD");
String qty = req.getParameter("qty");
StringTokenizer t = new StringTokenizer(myCd,"|");
String album= t.nextToken();
String artist = t.nextToken();
String country = t.nextToken();
String price = t.nextToken();
price = price.replace('$',' ').trim();
CD cd = new CD();
cd.setAlbum(album);
cd.setArtist(artist);
cd.setCountry(country);
cd.setPrice((new Float(price)).floatValue());
cd.setQuantity((new Integer(qty)).intValue());
return cd;
}
}
|
Every time the user adds an item within EShop.jsp,
the request is posted to the controller servlet. The servlet in turn determines
the appropriate action, and then processes the request parameters for the
item to be added. It then instantiates a new CD bean (shown in Listing 4)
representing the selection, and goes about updating the shopping cart object
before placing it back within the session.
Listing 4:
CD.java
|
package shopping;
public class CD {
String album;
String artist;
String country;
float price;
int quantity;
public CD() {
album="";
artist="";
country="";
price=0;
quantity=0;
}
public void setAlbum(String title) {
album=title;
}
public String getAlbum() {
return album;
}
public void setArtist(String group) {
artist=group;
}
public String getArtist() {
return artist;
}
public void setCountry(String cty) {
country=cty;
}
public String getCountry() {
return country;
}
public void setPrice(float p) {
price=p;
}
public float getPrice() {
return price;
}
public void setQuantity(int q) {
quantity=q;
}
public int getQuantity() {
return quantity;
}
}
|
Notice that we have also
included additional intelligence within the servlet, so that it understands
that, if a previously added CD is reselected, it should simply increase the
count for that CD bean within the shopping cart. The controller servlet also
processes actions triggered from within Cart.jsp, such as the
user deleting items from the shopping cart, or proceeding to the checkout
counter. Observe that the controller always has complete control over which
resources should be invoked in response to specific actions. For example,
changes made to the state of the shopping cart, such as additions or deletions,
cause the controller servlet to forward the request after processing to the
Eshop.jsp page. This in turn causes the page to redisplay the
main view, along with the updated contents of the shopping cart. If the user
decides to check out, the request is forwarded after processing to the Checkout.jsp page (shown in Listing 5) by means of the following request dispatcher, as shown below:
String url="/jsp/shopping/Checkout.jsp";
ServletContext sc = getServletContext();
RequestDispatcher rd = sc.getRequestDispatcher(url);
rd.forward(req,res);
Listing 5:
Checkout.jsp
|
<%@ page session="true" import="java.util.*, shopping.CD" %>
<html>
<head>
<title>Music Without Borders Checkout</title>
</head>
<body bgcolor="#33CCFF">
<font face="Times New Roman,Times" size=+3>
Music Without Borders Checkout
</font>
<hr><p>
<center>
<table border="0" cellpadding="0" width="100%" bgcolor="#FFFFFF">
<tr>
<td><b>ALBUM</b></td>
<td><b>ARTIST</b></td>
<td><b>COUNTRY</b></td>
<td><b>PRICE</b></td>
<td><b>QUANTITY</b></td>
<td></td>
</tr>
<%
Vector buylist = (Vector) session.getValue("shopping.shoppingcart");
String amount = (String) request.getAttribute("amount");
for (int i=0; i < buylist.size();i++) {
CD anOrder = (CD) buylist.elementAt(i);
%>
<tr>
<td><b><%= anOrder.getAlbum() %></b></td>
<td><b><%= anOrder.getArtist() %></b></td>
<td><b><%= anOrder.getCountry() %></b></td>
<td><b><%= anOrder.getPrice() %></b></td>
<td><b><%= anOrder.getQuantity() %></b></td>
</tr>
<%
}
session.invalidate();
%>
<tr>
<td> </td>
<td> </td>
<td><b>TOTAL</b></td>
<td><b>$<%= amount %></b></td>
<td> </td>
</tr>
</table>
<p>
<a href="/examples/jsp/shopping/EShop.jsp">Shop some more!</a>
</center>
</body>
</html>
|
Checkout.jsp
simply extracts the shopping cart from the session and the total amount for
the request, and then displays the selected items and their total cost. Figure
5 shows the client view upon checkout. Once the user goes to the checkout
counter, it is equally important to get rid of the session object. That is
taken care of by having a session.invalidate() invocation at
the end of the page. This process is necessary for two reasons. First, if
the session is not invalidated, the user's shopping cart is not reinitialized;
if the user then attempts to commence another round of shopping upon checkout,
her shopping cart will continue to hold items that she has already purchased.
The second reason is that if the user simply left the site upon checkout,
the session object will not be garbage collected and will continue to take
up valuable system resources until its lease period expires. Since the default
session-lease period is about 30 minutes, this can quickly lead to the system
running out of memory in a high-volume system. Of course, we all know what
happens to an application that runs out of system resources!

Figure 5: Music Without Borders, checkout view
|
Notice that all the resources
for this application are session aware, since the model here is stored within
the session. Consequently, you must ensure that the user does not somehow
access the controller directly, even by mistake. You can take care of this
by implementing the automatic client redirection to the error page (shown
in Listing 6) when the controller detects the absence of a valid session.
Listing 6:
error.html
|
<html>
<body>
<h1>
Sorry, there was an unrecoverable error! <br>
Please try <a href="/examples/jsp/shopping/EShop.jsp">again</a>.
</h1>
</body>
</html>
| Deploying Music Without Borders
I will assume that you are using the latest version of JavaServer Web Development
Kit (JSWDK) from Sun for running the example. If not, see the Resources section to find out where to get it. Assuming that the server is installed in \jswdk-1.0.1, its default location in Microsoft Windows, deploy the Music Without Borders application files as follows:
- Create shopping directory under
\jswdk-1.0.1\examples\jsp - Copy
EShop.jsp to \jswdk-1.0.1\examples\jsp\shopping - Copy
Cart.jsp to \jswdk-1.0.1\examples\jsp\shopping - Copy
Checkout.jsp to \jswdk-1.0.1\examples\jsp\shopping - Compile the
.java files by typing javac *.java - Copy
ShoppingServlet.class to \jswdk-1.0.1\webpages\Web-Inf\servlets - Create shopping directory under
\jswdk-1.0.1\examples\Web-Inf\jsp\beans - Copy
CD.class to \jswdk-1.0.1\examples\Web-Inf\jsp\beans\shopping - Copy
error.html to \jswdk-1.0.1\webpages - Once your server has been started, you should be able to access the application using http://localhost:8080/examples/jsp/shopping/EShop.jsp as the URL
Leveraging JSP and servlets
In this example, we have examined in detail the level of control and flexibility
provided by the Model 2 architecture. In particular, we've seen how the best
features of servlets and JSP pages can be exploited to maximize the separation
of presentation from content. Properly applied, the Model 2 architecture
should result in the concentration of all of the processing logic in the
hands of the controller servlet, with the JSP pages responsible only for
the view or presentation. However, the downside of using the Model 2 approach
is its complexity. Consequently, it may be desirable to use the Model 1 approach
for simpler applications. 
Printer-friendly version | Mail this to a friend
|
 |