Designing a J2EE Application

J2EE applications generally fall into two general categories: Web applications and client-server applications. The difference, obviously, is that in a Web application, the client interface uses a Web browser, whereas in a client-server application, the client interface is a standard application written using Java or another programming language.

This distinction between Web applications and client-server applications is not strict, however, because a well-designed multitier J2EE application can be flexible enough that the presentation layer the portion of the application that interacts with the client interface can be replaced with no change to the business logic or any other back-end components, such as a database. This is an important benefit, because it means that an application can be quickly extended in the face of emerging business needs, such as the need to support Web services.

The most commonly used design for building multitier applications is called the Model View Controller (MVC) design (Figure 10-2).

Figure 10-2. MVC architecture in J2EE.

graphics/10fig02.gif

This design breaks an application into three parts with the following responsibilities:

  • Model: Represents the data and the underlying business logic.

  • View: Presents the data.

  • Controller: Mediates between the View and Model and allows the user to interact with the data.

There are a number of ways to implement this design using J2EE components, depending on the type of application. Assuming that it is a critical design requirement to use a database for storage, Table 10-1 lists some possible implementations.

Table 10-1. Some Possible Implementations of the MVC Design
 

Web application 1

Web application 2

Client-server application

Model

JavaBeans/JDBC

EJB

EJB

View

JSP

JSP

Java application

Controller

Servlet/Java classes

Servlet/Java classes

EJB/Java classes

The first application, Web application 1, is not strictly a J2EE application because it uses only Web components and does not use EJB. It uses JavaBeans as its Model classes and uses JDBC to access the database. This design is relatively simple to develop and is the one most commonly followed.

The second Web application, the EJB model (Figure 10-3), uses Web components for the Controller and View, but it uses EJB (or possibly, EJB plus JavaBeans) to access the database. This design is significantly more complex to develop than is the non-EJB version, largely because of issues introduced by the use of EJBs.

Figure 10-3. The EJB model.

graphics/10fig03.gif

The third application is a client-server application that is fundamentally based on EJB. If the requirements demand a distributed application especially if the requirements include transaction support, authentication, or access to legacy systems EJBs can hide many of the details and much of the complexity from the developer.

In this chapter, we will use Oracle 9i AS OC4J to develop examples that demonstrate the use of servlets and JSPs. Apart from the details of deployment and configuration, nothing will be Oracle-specific and should work on any other application server. We will also take a brief look at using Oracle's JDeveloper to develop a container-managed persistent entity bean as means of providing easy access to the database. In the next chapter, we'll explore EJBs in general and in more detail.

Persistence in a Java Web Application

The sample Web application we'll examine here will provide a simple Web view for the CD collection database that we've been developing off and on in previous chapters. It will allow searching for CDs based on artist or title.

The principal design goal is to divide the logic of the application into distinct areas of responsibility. In particular, with a Web application, it is important to keep the code for page design separate from the application logic because typically, different developers or development teams are responsible for each. Not only do these tasks require different skill sets, but appearance of a Web site also may change more frequently than the underlying logic.

A similar consideration applies between the general application logic and the database access logic; this is particularly true in a larger application that integrates with a database that is used for other applications. The application's code will change more frequently than the underlying database. But even if the same developer or developers are responsible for the general application and the database access logic, it is easier to maintain and reuse components if the database components are independent of other components.

Applications sometimes use separate servlets to perform different actions, but this example will use a single servlet as the only entry point into the application. The servlet will do little more that obtain an instance of another class, a helper class that will actually process the request. We can consider this combination of a servlet and its helper classes depicted in Figure 10-4, to be the controller in the MVC model.

Figure 10-4. The servlet and helper classes.

graphics/10fig04.gif

There are two main benefits to using a single servlet with plain old Java classes as the controller. First, it allows us to simplify overall logic because a single class delegates to the appropriate helper class based on the request. Second, it simplifies the issue of multi-threading. Because servlets are only instantiated once, then shared by all requests, we need to make sure that everything we do in them is thread-safe. For example, instance variables that are modified by one thread could affect another adversely and yield unexpected results. Off-loading most of the logic to controller classes, which are instantiated for each thread individually, minimizes these concerns.

The job of the controller helper classes is to obtain whatever data is necessary to fulfill the request and to send it to a JSP page that is responsible for displaying it. As we shall see, JavaBeans are the ideal vehicle for transporting this data from the servlet/controller classes to the JSP page.

We'll next look at each of these parts in more depth.

Controller: Servlet and Helper Classes

When the Web server gets a request for a URL that is mapped to a servlet by the container, it instantiates the servlet class (if it hasn't been instantiated already) then calls several methods. These methods are implemented in the HttpServlet superclass, so by virtue of the fact that our servlet extends HttpServlet, we don't have to implement them unless we want our servlet to actually do something! Because that is always the case, we need to override at least one of these methods usually either doGet(), doPost(), or both.

When a browser requests a Web page, it usually sends either an HTTP GET or an HTTP PUT request. These differ in the way they pass information to the Web server but they have the same net effect. (GET sends the information all as a single line as part of the GET request; POST sends the information as separate lines following the POST request.) Depending on which one was used to invoke our servlet, the Web container will call either doGet() or doPost(). We'll always use POST when we set up forms but if a user types our servlet's URL into the browser, that will send a GET request, so we need to implement both. We could have doGet() call doPost() or vice versa or (for the sake of symmetry) we can have them both call another method. Here we take the latter route and use a method called doAction(). This is the start of the MusicServlet class:

 import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class MusicServlet extends HttpServlet {   public final static String pageParamName = "REQUESTPAGE";   public final static String defaultPage = "/StartPage.jsp";       public void doGet(HttpServletRequest request,         HttpServletResponse response)   {     doAction(request, response);   }       public void doPost(HttpServletRequest request,         HttpServletResponse response)   {     doAction(request, response);   } 

Both methods, doGet() and doPost(), take the same arguments, HttpServletRequest and HttpServletResponse, that we pass on to the doAction() method. The HttpServletRequest object contains (among other things) any information that came with the request. This information is stored as name-value pairs, which are called parameters (or attributes).

We define one parameter, REQUESTPAGE, for internal use by our application. As we'll see later, it tells us what page to go to next. If REQUESTPAGE is set, we use its value as a key to determine which controller class to instantiate to service this request. Our controller class has a method, dispatch(), that we call to hand off the request.

When a user first types in the URL for our servlet, however, REQUESTPAGE is not set. In this case, we want to go to our default or home page, StartPage.jsp. We also go to this default page if we are unable to instantiate a controller class based on the REQUESTPAGE value.

 public void doAction(HttpServletRequest request, HttpServletResponse response)   {     try     {       String page = request.getParameter(pageParamName);       HttpController controller;       if(page==null         || (controller=             HttpController.createController(page))==null)       {         RequestDispatcher dispatcher =           getServletContext().getRequestDispatcher(defaultPage);               dispatcher.forward(request, response);       }       else       {         controller.dispatch(request, response,               getServletContext());       }     }     catch(Exception e)     {       System.out.println("Caught: " + e);     }   } } 

The StartPage.jsp page is actually just a plain HTML form. One limitation of the RequestDispatcher.forward() method is that we can't forward HTTP POST requests to a static HTML page and we'll be doing that a lot. The solution is simply to name the HTML file with the .jsp extension. StartPage.jsp is the first thing the user will see when starting the application. It has fields for artist and title, as well as a Search button.

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE>CD Collection Search</TITLE> </HEAD> <BODY> <FORM ACTION="/servlet/MusicServlet" METHOD="POST"> <TABLE>   <TR><TD COLSPAN=2 ALIGN=CENTER>          <H2>CD Collection Search</H2>       </TD></TR>   <TR>     <TD>Artist:</TD><TD><INPUT TYPE="TEXT" NAME="ARTISTNAME"></TD>   </TR>   <TR>     <TD>CD Title:</TD><TD><INPUT TYPE="TEXT" NAME="CDTITLE"></TD>   </TR>   <TR><TD COLSPAN=2 ALIGN=CENTER>        <INPUT TYPE="SUBMIT" NAME="SEARCH" VALUE="Search collection">      </TD>   </TR> </TABLE> <INPUT TYPE="HIDDEN" NAME="REQUESTPAGE" VALUE="RESULTSPAGE"> </FORM> </BODY> </HTML> 

Once the user has filled in the form and pressed Submit, it will be posted to the URL listed as the ACTION attribute in the FORM tag near the top of the file: /servlet/MusicServlet. This is the same URL that starts the application, but the difference is that this form has this hidden input field near the bottom to set the REQUESTPAGE:

 <INPUT TYPE="HIDDEN" NAME="REQUESTPAGE" VALUE="RESULTSPAGE"> 

When we return to the servlet from this page, the value of the REQUESTPAGE parameter in this case, RESULTPAGE can be obtained as follows:

 String page = request.getParameter(pageParamName); 

The HttpController createController() method is called with this value so that it can determine which concrete controller class to instantiate:

 controller= HttpController.createController(page) 

The mapping between valid REQUESTPAGE values (the page argument in the call above) and the concrete controller classes are in a .properties file. There are only two controller classes implemented in this example, as shown in the following file, PageClasses.properties:

 # PageClasses.properties # These are the page name to controller class mappings RESULTSPAGE = ResultsPageController CDINFO = CDInfoController 

The HttpController class factory method, createController(), reads the properties, (using a helper method), looks up the requested page value to obtain the classname, then instantiates the class by name.

 import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public abstract class HttpController {        private final static String propertiesFileName =          "config\\PageClasses.properties";        private static Properties properties = null;   public static HttpController createController(String pageName)   {     HttpController controller = null;     if(properties==null)     {       readProperties();     }     if(properties!=null)     {       String className = properties.getProperty(pageName);       if(className!=null)       {         try         {           controller = (HttpController)             Class.forName(className).newInstance();         }         catch(Exception e)         {           System.out.println("Caught " + e);         }       }     }     return controller;   }    private static void readProperties()   {     try     {       properties = new Properties();       FileInputStream is = new           FileInputStream(propertiesFileName);       properties.load(is);     }     catch (IOException e)     {       System.out.println("Caught: " + e);     }   } 

The controller class that is instantiated, which is a concrete subclass of HttpController, is required to implement one method, as declared in HttpController.

 public abstract void dispatch(HttpServletRequest request,                               HttpServletResponse response,                               ServletContext servletContext)         throws ServletException, IOException; } 

As a result of the user pressing the Search button, the servlet is invoked with REQUESTPAGE set to RESULTPAGE, causing HttpServlet to instantiate the ResultsPageController class. The ResultsPageController class has a single method, dispatch(), as required by its superclass, HttpClass.

 import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.*; public class ResultsPageController extends HttpController {   private static final String jspPageName = "/MusicSearch.jsp";   private static final String artistParamName = "ARTISTNAME";   private static final String titleParamName  = "CDTITLE";   public void dispatch(HttpServletRequest request,                        HttpServletResponse response,                        ServletContext servletContext)    throws ServletException, IOException   {     String artistName = request.getParameter(artistParamName);     String cdTitle    = request.getParameter(titleParamName);     MusicResults results = new MusicResults(artistName, cdTitle);             request.setAttribute("MusicResults", results);     RequestDispatcher dispatcher =         servletContext.getRequestDispatcher(jspPageName);     dispatcher.forward(request, response);   } } 

This method does several things: First, it obtains the search parameters that the user specified, using the HTML form above, by calling getParameter(). (These are in the request parameters named ARTISTNAME and CDTITLE.) Second, it uses these parameters to create a JavaBean: an object of type MusicResults that holds the results of the search (as we'll soon see). Finally, it adds the MusicResults object to the HttpServletRequest, using the setAttribute() method, and forwards the request to the JSP page that will display it.

Model: JavaBeans

JavaBeans, as we use the term here, are really nothing special. (JavaBeans used in other contexts can be more complex, particularly those used to build graphical user interfaces [GUIs].) They are just a convention for building classes that are primarily designed for holding data and, in this context, are also called value objects. Most (and often all) of their methods are getter and setter methods methods to allow other classes to store information in them and pull information out of them.

The conventions that we use for JavaBeans are:

  • Attributes are stored as private instance variables. The first letter of the attribute is lowercase; other words in the variable name are capitalized. For example, an attribute may be named myVariable.

  • Methods are provided to obtain the value of each attribute. These getter methods are named get plus the name of the attribute. The first letter of the attribute is uppercase, in this case, so the name of the method to get myVariable would be getMyVariable(). Getter methods take no parameters.

  • Methods are provided to set the value of each attribute. These setter methods are named set plus the name of the attribute with first letter uppercase, as with the getter methods. The name of the method to set myVariable would be setMyVariable(). Setter methods take one parameter, the attribute value.

  • The JavaBean class must have a no-argument constructor.

When we use JavaBeans to hold data from a database, we can take two approaches: we can use lightweight JavaBeans that have no database access logic or we can use heavyweight JavaBeans that know how to retrieve and store themselves in the database.

The benefit of using lightweight JavaBeans is that they are smaller, and we can potentially encapsulate all database access logic in another single, separate class a data access object. The benefit of using heavyweight JavaBeans is that, although they are larger and have some code that is duplicated in other JavaBeans, they are completely self-contained and hide the database completely from the rest of the application. Furthermore, most of the code that is redundant could be implemented in a JavaBean superclass.

In this example, the MusicResults class is a heavyweight JavaBean, mainly to keep the number of classes in this sample application to a minimum. Given the search parameters that the user specified, MusicResults knows how to populate itself from the database and will fill several vectors with the results of a query constructed using the search parameters.

 import java.sql.*; import java.util.Vector; public class MusicResults {   Connection connection = null;   private Vector artist = new Vector();   private Vector title = new Vector();   private Vector artistId = new Vector();   private Vector cdId = new Vector();   private int size = 0;   private static final int MAXRESULTS = 25;   public MusicResults()   throws SQLException   {     connect();     executeQuery(null, null);   }   MusicResults(String artistName, String cdTitle)   {     try     {       connect();       executeQuery(artistName, cdTitle);     }     catch(SQLException e)     {       System.out.println("Caught: " + e);     }     finally     {       try       {         if(connection!=null)         {           connection.close();         }       }       catch       {         // ignore error closing connection       }     }   }   private void executeQuery(String artistName, String cdTitle)   throws SQLException   {     StringBuffer sql = new StringBuffer(           "SELECT DISPLAY_NAME(A.NAME, A.SURNAME, A.LOCALE), " +           "C.ALBUM_TITLE, A.ARTIST_ID, C.CD_ID " +           "FROM ARTISTS A, CD_COLLECTION C " +           "WHERE C.ARTIST_ID = A.ARTIST_ID ");     if(artistName!=null && !artistName.equals(""))     {       sql.append(          "AND UPPER(DISPLAY_NAME(A.NAME, A.SURNAME, A.LOCALE)) " +          "LIKE UPPER('%" +          artistName + "%') ");     }     if(cdTitle!=null && !cdTitle.equals(""))     {       sql.append("AND UPPER(C.ALBUM_TITLE) LIKE UPPER('%" + cdTitle +         "%') ");     }     sql.append("ORDER BY A.SORT_NAME, C.ALBUM_TITLE");     Statement stmt = connection.createStatement();     ResultSet rs = stmt.executeQuery(sql.toString());     int i = 0;     while(rs.next() && i++ < MAXRESULTS)     {       artist.add(rs.getString(1));       title.add(rs.getString(2));       artistId.add(new Integer(rs.getInt(3)));       cdId.add(new Integer(rs.getInt(4)));     }     size = artist.size();     rs.close();     stmt.close();   }   private void connect()   throws SQLException   {        DriverManager.registerDriver(        new oracle.jdbc.OracleDriver());        connection = DriverManager.getConnection(                "jdbc:oracle:thin:@noizmaker:1521:osiris",                "david", "bigcat");   } 

We'll consider several aspects of this code separately: how it connects to the database, the query that it makes, and its characteristics as a JavaBean.

The Database Connection

Obtaining a database connection is computationally costly. In this example, we've kept the code simple but if it were a real application that we expect would see significant traffic, we would likely consider finding a way to reuse connections, rather than using a new one for each request. One possible way is to reuse the connection on a per-session basis by storing it in the session scope with request.setAttribute(). Another way that might be more complicated to set up initially but has a better performance payoff is to use a JDBC 2.0 connection pool.

The Database Query

One of the database tables we are querying is the ARTISTS table, for which we developed a number of different PL/SQL stored procedures and a trigger to make it easier to display artist's names individuals and groups in a linguistically correct way. We will use this in a join, together with the CD_COLLECTION table.

The CD_COLLECTION table, which we last saw in Chapter 3, needs to be updated to use the ARTISTS table we created in Chapter 5, however. Specifically, we need to remove the artist information from CD_COLLECTION table and replace it with a foreign key that references ARTIST_ID, the primary key in the ARTISTS table. This is how we redefine CD_COLLECTION:

 DROP TABLE CD_COLLECTION; CREATE TABLE CD_COLLECTION (   ALBUM_TITLE VARCHAR2(100),   RELEASE_DATE DATE,   LABEL VARCHAR2(25),   ARTIST_ID NUMBER,   CD_ID NUMBER,   FOREIGN KEY (ARTIST_ID) REFERENCES ARTISTS(ARTIST_ID),   PRIMARY KEY (CD_ID) ); 

We'll also add some records for each of the artists in the ARTISTS table.

 INSERT INTO CD_COLLECTION VALUES(   'Mer de Noms', '1-JAN-2000', 'Virgin', 24,   CD_ID_SEQUENCE.NEXTVAL); INSERT INTO CD_COLLECTION VALUES(   'Big Science', '1-JAN-1982', 'Warner', 29,   CD_ID_SEQUENCE.NEXTVAL); INSERT INTO CD_COLLECTION VALUES(   'Mister Heartbreak','1-JAN-1984','Warner',29,   CD_ID_SEQUENCE.NEXTVAL); INSERT INTO CD_COLLECTION VALUES(   'Nothing to My Name', '1-JAN-1989', 'EMI Hong Kong', 22,   CD_ID_SEQUENCE.NEXTVAL); INSERT INTO CD_COLLECTION VALUES(   'Power of the Powerless', '1-JAN-1999', 'World Beat', 22,   CD_ID_SEQUENCE.NEXTVAL); INSERT INTO CD_COLLECTION VALUES(   'Ten', '1-JAN-1991', 'Epic', 25,   CD_ID_SEQUENCE.NEXTVAL); INSERT INTO CD_COLLECTION VALUES(   'Vitalogy', '1-JAN-1994', 'Epic', 25,   CD_ID_SEQUENCE.NEXTVAL); 

The SQL to query these two tables is generated dynamically by our code, based on whether the user typed any search criteria into the Artist or Title boxes. The first part of the query is constant and sets up a join between the ARTISTS table and the CD_COLLECTION tables. This is the SQL:

 SELECT DISPLAY_NAME(A.NAME, A.SURNAME, A.LOCALE),   C.ALBUM_TITLE, A.ARTIST_ID, C.CD_ID   FROM ARTISTS A, CD_COLLECTION C   WHERE C.ARTIST_ID = A.ARTIST_ID 

The select list, you'll note, uses the user-defined function DISPLAY_ NAME() (from Chapter 5) to put the name together in a culturally correct way, based on the artist's locale. For example, in Chinese names, the surname comes first, followed by the given name, whereas in English, it's the reverse.

The next part varies, depending on whether the user entered text for artist and title. If there is text for artist, the following is appended:

 AND UPPER(DISPLAY_NAME(A.NAME, A.SURNAME, A.LOCALE))         LIKE UPPER('%artistName%') 

If there is a title, the following is appended:

 AND UPPER(C.ALBUM_TITLE) LIKE UPPER('%cdTitle%') 

It is important to recognize that comparisons such as this can cause performance problems. Normally, this query would require the database to use the DISPLAY_NAME() and UPPER() functions for every row in the database to perform the comparison. If there are a lot of records in the table, this can take a long time and performance could easily be degraded to unacceptable levels.

One solution available in Oracle 9i is to use function-based indexes. This will store the results of a function in an index. Another solution is to store the results of these calculations in separate columns each time a row is inserted or updated, using a trigger as we did for the SORT_NAME column and to create an index on these columns. For a large production system, issues such as this need to be considered carefully by an experienced database administrator or consultant.

The JavaBean Class

Because the query can return multiple rows of results which we store in vectors, our value class, MusicResults, is not a totally compliant JavaBean because the getter methods take a parameter specifying the index of the attribute.

 public String getArtistAt(int i) {   return (String) artist.elementAt(i); } public String getTitleAt(int i) {   return (String) title.elementAt(i); } public int getArtisIdAt(int i) {   return ((Integer)artistId.elementAt(i)).intValue();   } public int getCdIdAt(int i) {   return ((Integer)cdId.elementAt(i)).intValue(); } public int getSize() {   return size; } } 

The fact that this is not a pure JavaBean means that we'll be able to load it as a JavaBean in our JSP page but we won't be able to take advantage of some of the special automatic support that JSP has for JavaBeans. We'll need to call the getter methods explicitly using embedded Java code.

View: The JSP Page

JSP, as noted above, is a server-side scripting language that is embedded within HTML. It is comparable to other server-side scripting products, such as ASP and ColdFusion. JSP has the advantage that it is based on Java, so it is platform and vendor-neutral and works well with other Java technologies.

Assuming that a Web server supports JSP, a JSP page can be placed anywhere in the server's directory structure that a regular HTML page can be placed, and it will automatically be translated into a Java servlet class, compiled, and run the first time it is requested.

It's beyond the scope of this book to teach JSP but before delving into the JSP page used to display the search results, we'll briefly look at four common scripting elements: scriptlets, expressions, actions, and custom tags.

JSP Scriptlets

Scriptlets are bits of Java code that are embedded within the HTML code. A scriplet has the basic format

 <% Java code %> 

The Java code in a scriptlet can be multiple statements or even just part of a statement. (What's important is that the Java code, if spread across various scriplets, must be valid as a whole.) The following example declares and initializes a variable to the number of milliseconds since 12:00 a.m. GMT on 1 January 1970:

 <%  long secs = (new java.util.Date()).getTime(); %> 

We can create a loop.

 <% for(int i=0; i<10; i++)    {      // do something    } %> 

What can we do inside the loop? We can do the usual Java things but we can also break the loop into two separate scriplets and put some HTML in the middle.

 <% for(int i=0; i<10; i++)    { %> <H2> I'm thinking! </H2> <%    } %> 

This will print out "I'm thinking!" 10 times.

JSP Expressions

JSP expressions allow us to embed a Java expression in HTML code. Its basic format is

 <%= Java expression %> 

At runtime, the value of the Java expression is calculated, converted to a string, and included in the HTML page that is sent to the user's browser. For example, we can print out the value of the startTime variable that we declared and initialized above like this:

 <%= secs %> 

This will convert the long value to a string and print it out as part of the pages HTML code in essentially the same way that using System.out .print() would.

Besides being useful for creating user-visible text, JSP expressions can also be used to generate HTML tags and JavaScript dynamically. Tags and JavaScript, after all, are just text to the Web server they are interpreted only when they reach a user's browser.

JSP Actions

JSP actions (or tags) are XML tags that invoke Java code. For example, to use a JavaBean, we use a useBean tag with the following syntax:

 <jsp:useBean docEmphasis">name" docEmphasis">classname" scope="scope" /> 

The id attribute indicates the variable name that is associated with the bean. The class attribute refers to the JavaBean's class name. The scope attribute refers to whether the bean belongs to the current request (by setting it to either "page" or "request"), the current session (by setting it to "session"), or the entire application for the duration of the servlet's lifetime (by setting it to "application").

When JSP processes this tag, it first checks to see whether a bean that matches the id attribute has already been instantiated in the specified scope. If there is, it uses that bean. If not, it instantiates a new bean of the specified class. To access the MusicResults bean that we created using our controller class, we use the following tag:

 <jsp:useBean                scope="request" /> 

Note that the id attribute, musicResults, is the name we gave the bean when we called request.setAttribute() in our controller class. The class attribute, MusicResults, is the name of the Java class that musicResults is an instance of. The scope is the request because we associated the bean with the session's request parameter by calling request.setAttribute().

Another JSP action is the getProperty tag that we can use to obtain values from a bean. It has the following attributes:

 <jsp:getProperty name="name" property="propertyname"/> 

The name here corresponds to the id with which we associated the bean in the useBean tag. The property must correspond to a standard (i.e., parameterless) getter method in the bean class. As it happens, our MusicResults class has only one such method, getSize(), which returns the value of the private instance variable size. We can get this property as follows:

 <jsp:getProperty name="musicResults" property="size"/> 

This is equivalent to the following JSP expression which uses an explicit Java method call:

 <%= musicResults.getSize() %> 

Custom Tags

graphics/note_icon.gif

It bears mentioning, if only in passing, that a feature introduced in JSP 1.1, custom tags, can be invaluable in reducing the amount of Java code in a JSP page.

Custom tags are grouped into custom tag libraries, which are groups of Java classes that are mapped to tag names, using an XML file called a tag library descriptor file (TLD). They are used on the JSP page in exactly the same way that standard JSP actions are used.

In our example, the amount of Java code that we need to include in our JSP page is already fairly small, so we won't use custom tags. Custom tags would, in this case, increase the complexity while doing little to reduce the coding that is necessary on the JSP page.

The Search Results JSP

Now that we've seen the basics of JSP, let's take a look at the JSP that will display our search results. First is a standard HTML header.

 <!DOCTYPE HTML PUBLIC "-//W3C//DTD 4.0 Transitional//EN"> <HTML> <HEAD> <TITLE>CD Collection Search Results</TITLE> </HEAD> <BODY> <H2>CD Collection Search Results</H2> <P> 

Next is the useBean tag.

 <jsp:useBean                             scope="request" /> 

A FORM tag indicates that when the user presses a Submit button, the form information should be posted to the MusicServlet.

 <FORM ACTION="/servlet/MusicServlet" METHOD="POST"> 

The next section displays the search results in a table, looping through the bean's values using Java code embedded as JSP scriplets and expressions.

 <TABLE BORDER=2> <TR><TH>Select</TH><TH>Artist</TH><TH>Title</TH><TR> <%  for(int i= 0; i<musicResults.getSize() ;i++)     { %>      <TR> 

One column in the table contains a button that the user can press to see information about a specific CD. Its value is set to the CD's CD_ID; when we process the form information in a controller, we can obtain its value by using the CDSELECTED parameter.

 <TD>   <INPUT TYPE="SUBMIT" VALUE=" > " NAME=          "CDSELECTED=<%=MusicResults.getCdIdAt(i)%>;           ARTISTNAME=<%=MusicResults.getArtistAt(i)%>"> </TD> 

Note that the NAME attribute, beginning with CDSELECTED, has been broken into two lines and indented for clarity; it should be a single line that concatenates the selected CD_ID and the artist's name. The reason for this is that, depending on which Submit button is pressed, we want to pass both of these values to our servlet, but a Submit button allows passing only one value. A second issue you may notice is that we don't pass these values in the VALUE attribute but instead convert them to name-value pairs that we pass as the NAME attribute. This is because there is no foolproof way in HTML to display something other than the VALUE in the Submit button, and we don't want our internal CD ID and the artist's name displayed in each of the Submit buttons, so we set the VALUE to something appropriate for display. We'll see how to retrieve these values later.

The next lines call the bean's getter methods to obtain the artist name and CD title.

    <TD><%= musicResults.getArtistAt(i) %></TD>    <TD><%= musicResults.getTitleAt(i) %></TD>    </TR> <%  } %> 

A button at the bottom will return us to the search page indirectly by setting the CDSELECTED value to "New search".

 <TR><TD COLSPAN=3 ALIGN=CENTER>   <INPUT TYPE="SUBMIT" NAME="CDSELECTED" VALUE="New search"> </TD></TR> 

The next line is a hidden field that indicates the page that the MusicServlet should use next.

 <INPUT TYPE="HIDDEN" NAME="REQUESTPAGE" VALUE="CDINFO"> 

Whether the user selects a CD or selected a new search, the MusicServlet will invoke the controller class corresponding to CDINFO. If the CDSELECTED value is "New search", the controller will return to the default search instead of performing the search.

Finally, we close the remaining open tags.

 </FORM> </TABLE> </BODY> </HTML> 

In the next section, we'll see how to deploy and run what we have so far using the Oracle's Containers for J2EE.



Java Oracle Database Development
Java Oracle Database Development
ISBN: 0130462187
EAN: 2147483647
Year: 2002
Pages: 71

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net