perfectxml.com
 Basic Search  Advanced Search   
Topics Resources Free Library Software XML News About Us
You are here: home Free Library Wrox Press » Professional WAP Friday, 13 July 2007

Excerpt from the book Professional WAP (chapter 10), by Wrox Press.

Java, XML and WAP

In this chapter, we show how to generate WML content using Java Server Pages (JSP) and XML. We use as a specific focus for the chapter the development of an application that uses a database as its data source. This application is a Ride Board, and may be familiar to many students across the world. It enables people to either post or look for rides (or lifts) from one place to another.

In this chapter, we think of the actual WML content of the decks that are sent to the user as the tip of a large application iceberg, or the icing on a big application cake. Most of the work is done elsewhere, in processing the user request and obtaining the data the user wants. Once this is done, we just need a general-purpose output component that wraps the data into WML, but can also wrap it into other formats. This output component is our main interest, and we will present two versions of it, a JSP version and an XSLT version. After we present both of them, you will be able to decide for yourself which of these technologies works better for you. We believe that both have a great potential and both should be in the toolkit of a WAP and web programmer.

This chapter may be approached in two halves. In the first half, we consider using JSP as the output component. In order to do this, we start the chapter with an introduction to firstly servlets, and then JSP. If these technologies are already common knowledge to you, then of course you may move forward to the following sections where we first discuss the strategies of developing the application, and then go on to explain the application in full.

In the second half of this chapter we will be considering using XSLT as the output component for the application. Since XSLT was discussed in detail in Chapter 9, we quickly move onto an example, and then redevelop the Ride Board application in this perspective.
In order to work through this chapter, you need to have a little background knowledge of Java and XML. In Java, you just need to know the basics of the language, as we will introduce the necessary background in servlets and JSP. In XML, you need to know about XML documents, DTDs, what it means for a document to be well formed and valid, and the details of the XSLT language. This, together with material on XML parsing and XSLT processing, was discussed in Chapter 9 of this book. We also assume that you know how a SAX parser works. We will be using some SAX classes and interfaces, such as InputSource and DocumentHandler.
After you finish the chapter, you will have learnt two important technologies: servlets and JSP, and obtained a deeper understanding of XSLT. Our main focus will be JSP and XSLT but, as you will see, an understanding of servlets is necessary because they are actively involved with the other two technologies: JSP are just servlets in disguise, and an XSLT processor is most commonly used within a servlet. In addition to learning about those technologies, you will, more importantly, learn how to use them within a 3-tier application with Java middleware.

The Ride Board Application

The application is fairly small but it goes much further than generating WAP content from an XML file: it is a complete 3-tier application, in the sense that it has a dedicated component that retrieves data from a database and makes it available for either web or WAP output. The database component is robust (it provides for security and connection pooling), so the application can be scaled up easily.

Our application is developed within a generic framework that is quite flexible both on its data end and on its output end. On the data end, it is quite easy to change the content of the application just replace the database and the queries. Alternatively, you can replace the database with a different data source altogether, as long as it contains the same kind of metadata that a database does the names of data fields and their data types. (For instance, it would be easy to use a mail folder and retrieve summary information from it.)

On the output end, it is quite easy to retarget the output from the WAP microbrowser to a different program, for example, a web browser, so our framework will allow you to generate both WAP and web content from the same database, using a number of shared components. In general, you can output any XML content. (Remember that both WML and XHTML are XML applications.)

The main components of our application are shown in the diagram below. Using this diagram, it will be easy to see what you need to know to work through this chapter, how this chapter is organized, and what you will have learned by the time the chapter is over:



In this diagram, the Main servlet or a Java Server Page (JSP) sits between the web server and the Java application. The central point of the application is a bean, a Java class that analyzes the request coming from the client and instructs the Main servlet where that request should be forwarded. The recipient of the forwarded request is another servlet or JSP page that is customized for one particular kind of request.

It sends the query embedded into the request to the Main bean, and the Main bean dispatches it to the data handler component. The data handler submits the query to the data source, obtains the result, and sends it to the output component. That component uses the query result to create an XML document and sends it back to the client.

In terms of this diagram, our framework has the following options or 'degrees of freedom':
  • We can use either a servlet or a JSP page for the Main servlet. We will use a JSP page to illustrate the way JSP works; there's no strong advantage to JSP over a simple servlet for this component of the system.

  • Our data source can be a relational database or any other source of structured data, as long as the data comes together with the names and data types of its fields. We will use a database.

  • The output component that incorporates query results into the output documents can be a JSP page or an XSLT processor. As discussed above, we will use a JSP page in the first half of the chapter, and then replace it with XSLT.

  • Finally, the output can be any kind of XML; in fact, if the output component is a JSP page then output can be literally anything, as we will see later. We will mostly be outputting WML, but will also show how to create a similar web (XHTML) application.
Most of the technologies mentioned above should be familiar to you if you have read through the preceding chapters. You may wonder why we would show a web application in a book on WAP. However, we remark that a very common situation will be that you will need to send the same information (at a different level of detail and differently formatted) both to the wired and the wireless web. At the present stage of tool development, we actually find it easier to debug the wired (XHTML) version as a stage in developing the WML; the tools are more mature.

Try Out the Ride Board Application

Before we get involved in the technicalities of servlets and JSP, you may want to try out the Ride Board application. All the code for the application can be downloaded from our web site. Point your WAP device at wml/top.jsp and you will see the following screen:



The operation of the program is completely straightforward. As distributed, the database doesn't have a lot to offer; we suggest that you offer several rides yourself before asking for one of them. Upon a successful operation (whether search, offer or accept), the page will display the response followed by the same form as in the initial screen. For instance, if you offer a ride, you will see a screen like this:

Note that in the code available for download there is an extra option available from the main page for checking ride details. This has not been included in the code description for the sake of simplicity.

Design Considerations

The biggest challenge of JSP is to find a good way to structure the application into components. The main two approaches to a good structure are:
  • Separate a main JSP page, which functions as a servlet, from the JSP pages for output. Perhaps even use a servlet rather than a JSP page for that purpose.

  • Isolate Java code into a compiled bean, and use JSP elements mainly to create and manipulate an instance of such a bean.

The subject of structuring JSP applications has received a lot of attention on the JSP-INTEREST list. We summarize one of the suggestions below, and present one of our own.

Servlet for Entry, JSP for Output

Servlets and JSP are so closely related that you can forward from a servlet to a JSP. This makes the following strategy possible. Use a servlet as the main entry point of your server-side application. Have the servlet create instances of Java classes that do all the work in the application, but forward to a JSP for output. Since the entry point servlet does not produce any output, there would not be any advantages for using a JSP in its place, while Java code is easier to read in a servlet as it is more logically structured according to function.

The servlet would have some code like this:


// Set up a bean with the results of the processing    
   MyBean myBean = new MyBean(....);    

   request.setAttribute("myBean", myBean);

   RequestDispatcher rd =
          getServletContext().getRequestDispatcher("/nextpage.jsp");    

   rd.forward(request, response);    

   return;

To access the bean from within the JSP page, the useBean directive is then invoked:


<jsp:useBean id="myBean" scope="request" class="MyBean" />

Note that the bean is passed from the servlet to the JSP as an attribute of the request object, and, in the JSP, is declared as having request scope. If you need the information for a longer period of time, then you can have your servlet store objects into the user's session (with session.putValue()) instead, and use scope="session" instead of scope="request" in the useBean directive.

Separate the Servlet from Output Given in JSP

The strategy described above is a neat scheme, because it implements different functionalities as slightly different entities: servlets and JSP. However, keeping two slightly different syntaxes in sync may be confusing. Another approach would be to keep everything in JSP (and beans, of course) but have a very clear distinction between the main page and the output template pages:
  • The main page is the target of the action attribute of the HTML form or the WML <go> element.

  • The main page produces little or no output.

  • The main page may contain some Java code, but mostly it instantiates the main bean and perhaps other classes that do the business logic of the application.

  • The main page may include, or forward to, different output pages depending on the values computed by the main bean.

  • Output pages have little or no Java code. They contain mostly template material and Java expressions to be evaluated. If they have shared material, it can be placed into a file that all of them include.

Dialog with the Client

In the example application presented in this chapter, we will be using a version of the last approach, with an additional twist that allows for a very compact and elegant conversation between the JSP page and the client. The idea is that the main JSP page, functioning as a servlet, uses include or forward directives to specify output template pages, while each output template page contains either an HTML form whose action attribute is the main JSP page or a WML <go> element whose href attribute is the main JSP page.

In more detail and specific to our application, the main JSP page interacts with the main bean using two string variables: beanCmd and jspCmd. The beanCmd variable, set from the JSP page, determines the kind of action that the bean executes. The jspCmd variable, set in the bean, determines the output template that the main JSP page selects for sending back the response:

The Ride Board Application

The subject matter of our application is a Ride Board of the kind you frequently find on college campuses. If you are traveling across the country and have room in the car to give other people a ride or a lift, you can use the Ride Board to advertise this fact. If on the other hand, you need a ride or a lift to another part of the country, you can use the Ride Board to look for rides, and sign up for a ride when you find one.

The Strategy

In the Ride Board application, we will use the design discussed in the previous section Dialog with the Client and illustrated in the diagram above. We will have a main JSP page forwarding the user to different output pages depending on what kind of query has been received from the client and whether the client is a web browser or a WAP microbrowser. We now describe how this works in the case of WML.

The opening URL of the application is a JSP page, top.jsp, which has no content of its own: all it does is include another JSP page, ridesInc.jsp. That page is also included in all the output pages. It has no Java content whatsoever, only a few cards. The first card has a select element that allows the user to select a query: this can be to Find Ride, Accept ride, or Offer ride:



Whichever query is selected, the user is taken to an appropriate card that has one or more <input> elements and a <go> element. For example, here is the card for the 'find a ride' query:


<card id="find">
   <do type="accept" label="Go!" >     
	<go href="/examples/jsp/rides/core.jsp">        
		<postfield name="query" value="findByZip" />        
		<postfield name="QP1" value="$toZip" />        
		<postfield name="target" value="wml-findByZip" />     
	</go>   
   </do>   

   <p align="center">Rides to Zip </p>   

   <p>
       Zip? <input name="toZip" format="*N" maxlength="5" /><br />   
   </p> 
</card>

As you can see, the <go> element of the card sends the query to core.jsp. This is the main page of the application that functions as the servlet. It instantiates a bean and forwards the action to the output page for, in this case, the findByZip query. The output page contacts the database that holds all the data on posted rides, gets the result and incorporates it into the output. The last thing it does is to include the same ridesInc.jsp page, so that the user can submit another query.

This was a barebones skeleton of the application. The web version is very similar to the WAP version; the only difference is in the output JSP pages. To emphasize how little the two versions differ, we will first present the WAP version. It will take the next fifteen pages or so. After that, we'll need only a page or two to retarget the application to a web device.

We will present the Ride Board application in the following order:
  • The database

  • The entry page: top.jsp and ridesInc.jsp

  • The main JSP page (which is Java code only): core.jsp

  • The configuration page, configure.jsp, and two back-end utility classes called DBHandler and Dict

  • The main bean: qBean

  • The output JSP pages: wml-findByZip.jsp, wml-acceptZip.jsp and wml-offer.jsp

Before we go on, note that the application is fairly generic: in order to change the subject matter from a Ride Board to, for instance, a weather report, all you would need to do is:
  • Provide a different database

  • Replace the configuration file

  • Provide appropriate WML decks for input and output

The Database

We assume that we have a database whose ODBC name is MyNaWap, with a table called Rides. Each row in the table has the following fields (all of type TEXT, except rowID which is INT and Day which is DATE):
  • rowID (the primary key)

  • OfferedBy

  • email contact for offerer

  • FromZip

  • ToZip

  • Day of Ride

  • AcceptedBy (initially empty)

  • email contact for accepter

As distributed, the database doesn't have a lot to offer; we suggest that you offer several rides yourself before asking for one of them.

The Entry Page: top.jsp and ridesInc.jsp

The application's entry page top.jsp is deliberately simple, being reduced to just the essentials. Most of its material is in the included ridesInc.jsp:


<%@ page contentType="text/vnd.wap.wml;charset=ISO-8859-1" %> 
<?xml version="1.0"?> 
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
    "http://www.wapforum.org/DTD/wml_1.1.xml"> 

<wml>
 <%@ page errorPage="../wmlerrorpage.jsp" %> 
 <jsp:useBean id="qBean" scope="session" class="MyNa.jspUtil.QBean" /> 
 <head>
    <meta http-equiv="Cache-Control" content="no-cache" forua="true"/> 
 </head> 
 
 <%@ include file="ridesInc.jsp" %> 
 
</wml>

Note the use of the <meta> element, to control caching. Since WAP microbrowsers cache very aggressively to reduce traffic, it is essential to turn caching off when you want fresh updates.

The included file, ridesInc.jsp, consists of four cards: the first card is used to select a query and the three other cards are used for the three different queries. It has no JSP-specific elements at all; the only reason it's a JSP file is because this way it can be included in other JSP files and easily extended to include some computed material, for example the last values entered by the user. (We don't do this here for reasons of simplicity, but would of course do so in a real application.)

Here is the entire page.


<card id="start">
   <p>
      <select>
           <option onpick="#find">Find ride </option>     
           <option onpick="#accept">Accept ride </option>     
           <option onpick="#offer">Offer ride </option>   
     </select>   
  </p> 
</card> 

<card id="find">
   <do type="accept" label="Go!">
        <go href="/examples/jsp/rides/core.jsp">
               <postfield name="query" value="findByZip" />       
               <postfield name="QP1" value="$QP1" />       
               <postfield name="target" value="wml-findByZip" />     
        </go>   
  </do> 

  <p align="center">
     Rides to </p> 
     
     <p>   Zip? <input name="QP1" format="*N" maxlength="5" /><br /> 
     </p> 
</card> 

<card id="accept">
   <do type="accept" label="Go!">
        <go href="/examples/jsp/rides/core.jsp">
               <postfield name="query" value="acceptZip" />       
               <postfield name="QP1" value="$QP1" />       
               <postfield name="QP2" value="$QP2" />       
               <postfield name="QP3" value="$QP3" />       
               <postfield name="QP4" value="$QP4" />       
               <postfield name="target" value="wml-acceptZip" />     
        </go>   
   </do> 
   
   <p align="center">
      Accept a ride 
   </p> 
   
   <p>
      Name? <input name="QP1" format="*m" maxlength="8" /><br />   
      eMail? <input name="QP2" format="*m" maxlength="20" /><br />   
      Date? <input name="QP3" format="NN\/NN\/*N" maxlength="10" /><br />   
      toZip? <input name="QP4" format="*N" maxlength="5" /><br /> 
   </p> 
</card> 

<card id="offer">
   <do type="accept" label="Go!">
        <go href="/examples/jsp/rides/core.jsp">
               <postfield name="query" value="offer" />
               <postfield name="QP1" value="$QP1" />       
               <postfield name="QP2" value="$QP2" />       
               <postfield name="QP3" value="$QP3" />       
               <postfield name="QP4" value="$QP4" />       
               <postfield name="QP5" value="$QP5" />       
               <postfield name="target" value="wml-offer" />     
        </go>   
  </do> 
  
  <p align="center">
     Offer a ride 
  </p> 
  
  <p>
     Name? <input name="QP1" format="*m" maxlength="8" /><br />   
     eMail? <input name="QP2" format="*m" maxlength="20" /><br />   
     fromZip? <input name="QP3" format="*N" maxlength="5" /><br />   
     toZip? <input name="QP4" format="*N" maxlength="5" /><br />   
     Date? <input name="QP5" format="NN\/NN\/*N" maxlength="10" /><br /> 
  </p> 
</card>

All three of the query cards are structured in the same way: they have a <do> element that submits the query and a <p> element that contains input fields. We pre-format the date field so that the user doesn't have to type non-alphabetic characters.

You will see this page again when we get to the JSP pages for output. As was discussed in the design section, all three output pages end with the same forms that produce them allowing the user to 'continue the conversation' with the database. Rather than repeating that material, we put it into ridesInc.jsp, which can be included in the end of each output page.

The nature of the queries and the process of specifying a query will be discussed shortly. For now, just note two details. First, each form has a hidden field, query, whose value is the name of the query: findByZip, acceptZip and offer. Second, query parameters are the values of input fields with names like QP1, QP2, and so on.

When submitted, the form goes to the main JSP page, core.jsp.

The Main JSP: core.jsp

This page has no output elements, only directives, action elements and Java code. Since it produces no output, the main page does not need a header, whether HTML or XML, or WML. It carries out the following tasks:
  • Instantiate the main bean, qBean

  • Configure the bean using the configure.jsp file, which we will see shortly, that is included into core.jsp

  • Call the bean's doCommand() method with the request object as argument

  • Forward the request to the output file determined by the bean's whereTo() method

In this example, the destination is the output file corresponding to the submitted query. Later in the chapter, the number of destinations will double to include JSP pages that produce output for web browsers as well as WAP microbrowsers:


<!-- database fields are as follows:
    rowId,OfferedBy,OfferEmail,FromZip,ToZip,Day,AcceptedBy,AcceptedEmail 
    of types:    
    INT,TEXT,TEXT,TEXT,TEXT,DATE,TEXT,TEXT --> 
    
<%@ page  errorPage="errorpage.jsp" %> 

<jsp:useBean id="qBean" class="MyNa.jspUtil.QBean" scope="session"/> 

<%@ include file="configure.jsp" %>

<%
      qBean.doCommand(request);
      if(true){
             out.clear();       
             pageContext.forward(qBean.whereTo());       
             return;     
             } 
%>

The curious notation if(true) is needed because JSP inserts Java scriptlets (code fragments) into the larger body of the generated servlet code, and the code is then compiled. If you simply insert a line that says return in the middle of other code, the compiler will complain that subsequent code is unreachable. In this case we do want the remaining code to be unreachable, but the compiler doesn't understand that, and needs to be pacified.

Two larger items mentioned in this page that require an explanation are the configuration page and the bean. We move onto these next.

The Configuration Page: configure.jsp

The configure.jsp page contains nothing but Java code it consists of a single scriptlet. Within it, there are three sections, corresponding to three kinds of information that are needed for setting up a session. First, once per session, the user has to connect to the database. Second, within a session the user submits query requests, and depending on the query, the request is forwarded to one of several JSP pages for output. The first part of configure.jsp contains the information for setting up the database connection; the second part specifies the queries available to the user, and the third part specifies the output page for each query, and an error page.

The configuration page concludes with the call on qBean.configure(). The task of configure() is to set up an object of our DBHandler class (presented in the next section) which handles all the interactions with the database. Although configure() is called once per request, it will have no effect after the first call, when the session is set up and a DBHandler object is stored in it. (The method starts with an if statement that checks to see whether the DBHandler is null or not.)

This is not the ideal way to handle configuration, and in our own practice we usually do it differently: we instantiate and configure the main bean, including a DBHandler from an XML configuration file. However, this would require a lot more background machinery to explain here. The method used in this chapter allows a system administrator to configure the database access and alter the target files, without introducing too many new concepts.

Here is the entire page; the explanation that follows is divided into three subsections corresponding to the divisions in the page:


<%

   // Part1: information for connecting to the database 
   String[][]dbParams=new String[][]{
      {"dbDriver", "sun.jdbc.odbc.JdbcOdbcDriver"},   
      {"dbName", "jdbc:odbc:MyNaWap"},   
      {"dbUser","userName"},   
      {"dbPwd",""},  
      {"dbDateFormat","yyyy-MM-dd"}     // sets input format;          
      // in this version, date output format is fixed 
   };   
   
   // Part2: queries available to the client 
   String[][]dbQueries=new String[][]{
      {"findByZip",     "SELECT * FROM Rides WHERE ToZip=?",     "TEXT"},   
      {"acceptZip",     "UPDATE Rides SET AcceptedBy=?,AcceptedEmail=? "+
           "WHERE rowId=(SELECT Min(rowID) FROM Rides "+     
           "WHERE Day=? AND ToZip=? AND AcceptedBy='-')",     
           "TEXT,TEXT,DATE,TEXT"},   
           {"offer",
                "INSERT INTO Rides SELECT 1+Max(r.rowId) as rowId,"+     
                "? as OfferedBy,? as OfferedEmail,? as FromZip,? as ToZip,"+     
                "? as Day,'-' as AcceptedBy,'-' as AcceptedEmail FROM Rides r",     
                "TEXT,TEXT,TEXT,TEXT,DATE"}
  };   
  
  // Part3: output JSP files for queries (and an error page) 
  String[][]responseTargets=new String[][]{
     {"error","/jsp/rides/errorpage.jsp"}   
     {"wml-findByZip","/jsp/rides/wml/findByZip.jsp"},   
     {"wml-acceptZip","/jsp/rides/wml/acceptZip.jsp"},   
     {"wml-offer","/jsp/rides/wml/offer.jsp"} };

        qBean.configure(dbParams,dbQueries,responseTargets); // once per session 
%>

Connecting to the Database

In order to connect a user to a database, we need information about the user and information about the database. In the Java Database Connectivity (JDBC) framework, a database is specified by two items: a JDBC driver that connects Java code to the database, and the name by which the database can be found. On the basis of these two pieces of information, the program can construct a Connection object that handles the connection details.

The user information consists of a user name and a password. A simple approach to security is to send the initial request encrypted and have the database itself handle user authentication. Our DBHandler assigns each user (each username-password pair) a separate connection pool, so that different users don't mix.

Query Specification

The second section has to do with running queries. In our framework, queries have names and values, where the value is a SQL string with question marks used as place holders for the parameters of the query. (See the example below.) In JDBC, you use such strings to create a PreparedStatement object that has an executeQuery() method. Once the database connection is established, the PreparedStatement object can be used to run a query, with parameters supplied by the Request object.

When the user submits a query, the submitted values of the WML input elements replace the question marks in the SQL string. The values of the input elements are, of course, text strings that have to be converted to the appropriate SQL data types. In the case of DATEs, this conversion is not trivial because dates are formatted differently in different parts of the world. In order to make sure that the conversion process works correctly, we provide a string that lists the data types of the parameters. For instance, the acceptZip query is like this:


      {"acceptZip",     "UPDATE Rides SET AcceptedBy=?,AcceptedEmail=? "+
           "WHERE rowId=(SELECT Min(rowID) FROM Rides "+     
           "WHERE Day=? AND ToZip=? AND AcceptedBy='-')",     
           "TEXT,TEXT,DATE,TEXT"},   

Here, the first string is the name of the query, the second is a SQL string with four place holders in it, and the third a list of four data types for the four query parameters. You will see how it works in the section on the DBHandler, coming up soon.

The SQL strings are stored in a dictionary-like object, indexed by the corresponding names. In order to run a query, the user only has to use the appropriate form and provide the values for the parameters of the PreparedStatement. Since these parameters are ordered, the input elements for entering query parameters must have such names as QueryParameter1, QueryParameter2, and so on. For the Wireless Web, where every byte counts, we shorten them to QP1, QP2, and so on. You saw these names in the entry page.

The SQL query for offering rides is a bit more involved. We'll take a brief detour to go through it before resuming the overview of the configure.jsp page.
The Offer Query
The tricky part about this query is that we want new offers to go to the end of the Rides table so they are listed in the order in which they are submitted. In order to find the end of the table we have to refer to it in the inner SELECT query as well as in the outer INSERT query. It is easy to send SQL into an infinite loop here; the way to stay out of trouble is to provide an alias for 'Rides' and use it in the inner query:


           {"offer",
                "INSERT INTO Rides SELECT 1+Max(r.rowId) as rowId,"+     
                "? as OfferedBy,? as OfferedEmail,? as FromZip,? as ToZip,"+     
                "? as Day,'-' as AcceptedBy,'-' as AcceptedEmail FROM Rides r",     
                "TEXT,TEXT,TEXT,TEXT,DATE"}

Output Templates

The third and final section of the configuration page specifies an output template for each type of query. It also consists of name-value pairs where the names are the names of queries and values are the names of JSP files to forward the request to. In addition to the error page, there are three forward destinations, which correspond to the three available queries. Later in the chapter, to produce web-based output, we'll add three more forward destinations for JSP pages whose template material is XHTML rather than WML.

A Look at the Back End

The centerpiece of the back end processing is the main bean, QBean.java, which dispatches queries from the main JSP page to an appropriate handler and forwards the result to the appropriate page for output. It uses two custom classes to do its job: DBHandler and Dict. While the details are a bit involved, the main ideas behind these two classes can be summarized compactly. We do it here, to take the mystery out of it and also to explain how the application gets initialized from the request data.

DBHandler

Our main tool for database processing is the DBHandler class. There is a DBHandler object for each database that is used by the application within a user session. This means that a DBHandler object needs three kinds of information:
  • Information about the database: the JDBC driver and the database URL

  • Information about the user: username and password

  • Information about the applications: what queries does the application make available to the user?

The first two items on this list are just Strings, and you have seen them specified in the configuration file. But how does one represent queries? DBHandler contains an inner class called Query that implements the 'named query' abstraction: a Query object is, in effect, a query string for a PreparedStatement (in the JDBC sense) with a name given to it. DBHandler contains a Hashtable of such Query objects, indexed by their names. The configuration file you have just seen results in a DBHandler object whose queries Hashtable contains three Query objects, named findByZip, acceptZip and offer.

A DBHandler is created once per session. Once it is created, it connects to the database (through a 'connection pool' reusing existing connections as much as possible). Next, it creates its PreparedStatement objects. After that, the program is ready to accept queries from the query entry page. Let us walk through an example of a query. The entry page for our Ride Board application contains the following card, among others:


<card id="accept">
   <do type="accept" label="Go!">
        <go href="/examples/jsp/rides/core.jsp">
               <postfield name="query" value="acceptZip" />       
               <postfield name="QP1" value="$QP1" />       
               <postfield name="QP2" value="$QP2" />       
               <postfield name="QP3" value="$QP3" />       
               <postfield name="QP4" value="$QP4" />       
               <postfield name="target" value="wml-acceptZip" />     
        </go>   
   </do> 
   
   <p align="center">
      Accept a ride 
   </p> 
   
   <p>
      Name? <input name="QP1" format="*m" maxlength="8" /><br />   
      eMail? <input name="QP2" format="*m" maxlength="20" /><br />   
      Date? <input name="QP3" format="NN\/NN\/*N" maxlength="10" /><br />   
      toZip? <input name="QP4" format="*N" maxlength="5" /><br /> 
   </p> 
</card> 

To run this query, the user fills in the entry fields for query parameters (named QP1, QP2, and so on). This information gets to the DBHandler via the Request object and the main bean. The DBHandler retrieves the PreparedStatement using the name given, replaces its question marks with the values of the QP parameters, and runs the query.

This is an outline. It will be fleshed out to a certain extent as we go through the code of the main bean. For the complete detail, including the connection pooling mechanism that is built into the DBHandler, you should consult the code, which is available with the rest of the code for this book.

The Dict class

The Dict class is a convenience class for storing and retrieving Strings. It is derived from java.util.Properties (itself derived from Hashtable). The reason we extend Properties rather than use the class directly is because we want a couple of additional features, as follows:
  • Dict is used for configuration by system administrators, so we make the keys case-insensitive: "Key", "key" and "KEY" all map to the same value.

  • We make it possible to place a limit on how long the total length of the output can be, set by the outLimit variable. This is useful for HTML, and crucial for WML.

  • We define a setDef() method, overloaded so that in addition to a single name-value pair, it can take an array of such pairs (a 2-dimensional array of Strings), or two parallel arrays of names and values, or, indeed, a Request object arriving from a servlet or a JSP page.

  • Finally, we define a getDef() method that is like the getProperty() method of Properties, except it makes the retrieval case-insensitive and it keeps track of outLimit. This latter feature can be useful for WML output where the maximal page size is on the order of 1.5K.

The entire class is shown below:


package MyNa.jspUtil; 
package MyNa.jspUtil; 

import java.util.Properties; 
import javax.servlet.http.HttpServletRequest;   
public class Dict extends Properties {
        int outLimit; // controls amount of output   
        
        // two basic constructors that create an empty Dict   
        public Dict(int outLimit){
             this.outLimit=outLimit;   
        }   
        
        public Dict(){this(-1);} // there is no limit   
        
        // the remaining constructors all use setDef() method   
        public Dict(String[][]pairs){
             this(-1);     
             setDef(pairs);
        }   
        
        public Dict(String[]names,String[] vals){
             this(-1);     
             setDef(names,vals);
        }   
        
        public Dict(HttpServletRequest req){
             this(-1);     
             setDef(req);   
        }   
        
        // setter and getter for outLimit   
        public void setOutLimit(int outLimit){
             this.outLimit=outLimit;   
        }   
             
        public int getOutLimit(){
             return outLimit;   
        }   
             
        // several versions of setDef   
        public void setDef(String name,String val){
             setProperty(name.toUpperCase(),val);   
        }   
             
        public void setDef(String[][]pairs){
             for(int i=0;i<pairs.length;i++)
                    setDef(pairs[i][0],pairs[i][1]);   
        }   

        public void setDef(String[]names,String[]vals){
             int len=names.length; 
             
             if(len>vals.length)len=vals.length;     
             for(int i=0;i<len;i++)
                    setDef(names[i],vals[i]);   
        }   
                    
        public  void setDef(HttpServletRequest req){
             java.util.Enumeration enum=req.getParameterNames();     

             while(enum.hasMoreElements()){
                    String name=(String)enum.nextElement();       
                    String val=req.getParameter(name);       
                    setDef(name,val);     
             }   
        }   

        // several versions of getDef
        public String getDef(String name){
             return getDef(name,"");      
        }   
             
        public String getDef(String name,String dflt){
                String val=getProperty(name.toUpperCase(),dflt);     
                
                if(val==null)val="";     

                if(outLimit<0)return val;     

                int len=val.length();     

                if(len>outLimit){
                       val=val.substring(0,outLimit);       
                       outLimit=0;     
                } 
                else
                 outLimit-=len;     
                return val;   
        }   

        public String[]getDef(String[]names,String[]dflt){
             String[]vals=new String[names.length];     

             for(int i=0;i<vals.length;i++){
                    vals[i]=getDef(names[i],dflt[i]);
             }     
             return vals;   
        }   
        
        public String[]getDef(String[]names,String dflt){
             String[]vals=new String[names.length];     
             for(int i=0;i<vals.length;i++){
                    vals[i]=getDef(names[i],dflt);     
             }     
             return vals;   
        }   
        
        public String[]getDef(String[]names){
             return getDef(names,""); 
        
        }        // end of Dict class

Most of this code is completely straightforward, except perhaps the second version of getDef(). Remember that outLimit sets the allowed length of output, so every time we output a string, we subtract its length from outLimit. If the length of the string is greater than the remaining quota of characters, we output as much as we can and set outLimit to 0.

The Main Bean: qBean

With the supporting classes cleared, it is now time to look inside the main bean. The most important tasks of the main bean are to set up a DBHandler and to forward the request to the right target. All the pertinent information has to be extracted from the Request object. It follows that the bean needs three variables: a DBHandler, a Dict to hold the request object information, and a Dict to hold the targets after they are extracted from the request. Here is the beginning of the bean's code:


package MyNa.jspUtil; 

import java.sql.SQLException; 
import javax.servlet.http.HttpServletRequest; 

public class QBean {
	DBHandler dbH=null;   
	Dict targets=null;   
	Dict requestDict=null;   
	
	public QBean(){}   
	
	public Dict getTargets(){
	     return targets;   
	}   
	
	public Dict getRequestDict(){
	     return requestDict;   
	}

configure()

The first thing that the bean does is configure(), which is called from configure.jsp. The call, as you may remember, looks like this


qBean.configure(dbParams,dbQueries,responseTargets);

Here, dbParams is an array of strings that contains database connection information, and the other two arguments are two-dimensional arrays of strings in which each row is a name-value pair. Not surprisingly, they end up as Dict objects:


public void doConfigure(
   String[]dbParams,         // dbName,jdbc driver name,username and password   
   String[][]dbQueries,      // query name,sql string for PreparedStatement   
   String[][]responseTargets)// query name,output JSP page             

           throws SQLException{
                if(dbH!=null)return;    // we only do this once per session     
                targets=new Dict();     
                targets.setDef(responseTargets); // store response targets in the Dict     
                dbH=makeDBHandler(dbParams,dbQueries);   
           }

Now, how do you make a DBHandler? Actually, it's quite easy:


private DBHandler makeDBHandler(
       String[]dbParams,       
       String[][]dbQueries)             

            throws SQLException{
                 String dbDriver=dbParams[0];     
                 String dbName=dbParams[1];   
                 
                 //set dbUser, dbPwd or leave as null if not provided     
                 String dbUser, dbPwd;     
                 
                 if(dbParams.length<3)dbUser=null; else dbUser=dbParams[2];     
                 if(dbParams.length<4)dbPwd=null; else dbPwd=dbParams[3];   
                 
                 // Misc.column() is a utility that returns a column of a 2-d array     
                 String []qNames=Misc.column(0,dbQueries);
                 String []qSqlStrings=Misc.column(1,dbQueries);     
                 return new DBHandler(dbDriver,dbName,
                                           dbUser,dbPwd,                          
                                           qNames,qSqlStrings);   
}// DBHandler is created, ready to run queries

This concludes the configure() part, executed from configure.jsp that is included in the main JSP page, core.jsp. As you can see, although configure() is called on every request, its code gets executed only once, when the session is first created.

doCommand() and whereTo()

The main JSP page, core.jsp, calls doCommand() and whereTo() which get executed on every request. doCommand() does not do much at all: it just wraps the request object into a Dict object and calls setupRequest(), a placeholder method that can be used, as needed, to validate the request information; we leave it empty of content:


public void doCommand(HttpServletRequest request){
   requestDict=new Dict();   
   requestDict.setDef(request);   
   setupRequestDict();  // check through parameter name assumptions 
} 

private void setupRequestDict(){
   // here we can check the request and complain if we don't like it 
}

The business of identifying the page to forward to is dispatched by whereTo(). By now, all request information is in the requestDict, and all targets are in the targets Dict, so we use getDef() often.


public String whereTo(){
   String targetType=requestDict.getDef("target","");   

   if(0==targetType.length())targetType=requestDict.getDef("query","");   
   if(0==targetType.length())targetType="error";   
   return targets.getDef(targetType,""); 
}

To give an example, if the query is findByZip, then requestDict.getDef("target") will return wml-findByZip, and targets.getDef("wml-findByZip") will return "wml/findByZip.jsp", the name of the JSP page to forward the request to.

What about the query?

The main bean does include a method for executing the query, queryResult(), but it is not called from any of the JSP pages that you have seen: it is called from the target page to which the request is forwarded. The queryResult() method, in turn, calls an output method of the DBHandler. The DBHandler has several such methods that differ in how the result set is packaged: it can be a two- dimensional array of Strings, or a lazily evaluated sequence. Whatever the format, the result of the query is returned as a variable within an object that implements our QueryResult interface. QueryResult extends the XmlOb interface, both within the MyNa.jspUtil package:


public interface XmlOb {
   public String toXmlString(); 
} 

public interface QueryResult extends XmlOb {
   public String[] getColumnHeaders();   
   public String[] getColumnTypes();   
   public String[][]getRows(); 
}

As you can see, an object that implements XmlOb knows how to write itself out to an XML string. An object that implements QueryResult, in addition, can provide two String arrays of equal size that specify the names and data types of record fields. The fields themselves are returned, as a String matrix, by the getRows() method.

Here is the queryResult() method that calls the DBHandler's getQueryResult. Note that the argument to getQueryResult() is a Dict, not a Request object. There is no dependence on the specific servlet/JSP context: DBHandler doesn't know or care where the Dict object comes from. It can, for instance, come from an XML file or from a socket stream:


public QueryResult queryResult()throws SQLException{
   // called from receiving page, not forwarding page.   
   return dbH.getQueryResult(requestDict); 
}

And that's all there is to the main bean. It is now time to move on downstream to the output pages.

JSP for Output

There are three WML output pages corresponding to the three queries: findByZip, acceptZip and offer. The names of the output files are the same as the names of the queries, with .jsp added on the end; they will be stored in a wml directory, so that the findByZip query with wml-findByZip target is the wml/findByZip.jsp file. There will also be an xhtml directory with files of the same names which respond to the same queries; the whereTo() method in the bean must direct these requests appropriately. As described in the section on design considerations and in the section on the entry page, all three output pages end with the same forms that produce them, allowing the user to 'continue the conversation' with the database. The repeated material is in ridesInc.jsp, which gets included in the end of each output page. You saw that page in the entry page section, so we won't repeat it here.

The material that precedes this input file varies depending on the query. Let's start with the natural first query, wml-findByZip.jsp.

findByZip.jsp

Like all output pages, this page starts out by creating an instance of the main bean and asking it to run a query. The query returns a QueryResult object, from which we extract a two-dimensional array of Strings. The rest of the page is a typical example of how a JSP page would display the result of a database query as a WML table. Although the page is fairly long, we don't break it into pieces but provide a running commentary:


<%@ page contentType="text/vnd.wap.wml;charset=ISO-8859-1" %>
<?xml version="1.0"?> 
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.1//EN"
   "http://www.wapforum.org/DTD/wml_1.1.xml"> 

<wml>
 <%@ page errorPage="../wmlerrorpage.jsp" %>
  <jsp:useBean id="qBean" scope="session" class="MyNa.jspUtil.QBean" /> 
  
  <head>
     <meta http-equiv="Cache-Control" content="no-cache" forua="true"/> 
  </head> 
  
  <card id="output" title="findByZip">
     <do type="accept" label="again" > 
		<go href="#start" />  
     </do> 
     
     <%
        MyNa.jspUtil.QueryResult qR=qBean.queryResult(); 
        
        //  QueryResult  has  an  array  of  headers  and  a  2-d  array  of  records   
        String[][]rows=(null==qR)?null:qR.getRows();   
        if(null==rows || rows.length<1){ // no matches found? 
     %>   
     
     <p>Sorry, no rides to your zip-code.</p> 
     <% 
     }else{ String[]headers=qR.getColumnHeaders();  %>      
     
     <p>
          <table columns="2"> 
          
			<%  // we use only rows[0] to generate a 2-column table of fields       
			for(int j=0;j<headers.length;j++){ 
			%>       
			<tr>
			          <td><%= headers[j] %></td><td><%= rows[0][j] %></td>       
			</tr> 
          
			<%      
			}                                                    
			%>    
          
          </table>    
    </p> 
    <% 
    } 
    %> 
  </card> 

  <%@ include file="ridesInc.jsp" %> 
</wml>

acceptZip.jsp

The beginning and the end of this page are the same as the preceding one; only the display of the query result is different:


<card id="output" title="acceptByZip">
   <do type="accept" label="again" >
    <go href="#start" />  
   </do> 
   
   <%
      MyNa.jspUtil.QueryResult qR=qBean.queryResult();   
      String[][]rows=(null==qR)?null:qR.getRows();

      if(null==rows || rows.length<1 || rows[0].length<1){ 
   %>   
   
   <p>Sorry, no ride; please try again.</p> 
   <%   
   }else{
         int numberAffected=Integer.parseInt(rows[0][0]);      
         if(numberAffected==0){ 
         %>   
            <p>Sorry, no ride available.</p> 
         <%      
         } else {     
         %>
            <p>Okay, you've got a ride!</p> 
         <% 
         } 
  } 
  %> 
</card>

In this case, the result of the query is just an integer the number of rows affected. (It is still returned inside a two-dimensional array of strings that has one row and one column.) The same is true for the next query; the only difference is that in acceptZip we modify an existing row of the database table, while in offer we add a new one, or several new ones if more than one seat is added.

offer.jsp

Since this output page is very similar to the preceding one, we'll only repeat the middle part, until the include directive:


<card id="output" title="offer">
   <do type="accept" label="again" >
    <go href="#start" />
   </do> 
   
   <%
      MyNa.jspUtil.QueryResult qR=qBean.queryResult();   
      String[][]rows=(null==qR)?null:qR.getRows();   
      if(null==rows || rows.length<1 || rows[0].length<1){ 
      %>   
		<p>Sorry, your offer went wrong; please try again.</p> 
	  <%   
	  }else{
	        int numberAffected=Integer.parseInt(rows[0][0]);      
	        if(numberAffected==0){ 
	        %>   
	        <p>Sorry, your offer failed. Please report this problem
	           to our Friendly Support Staff. (or try again).
	        </p> 
	        
	        <%      
	        } else {     
	        %>
	           <p>Thanks; now we'll see who accepts it.</p> 
	        <% 
	        } 
	 } 
	 %> 
</card>

[The chapter continues by looking at ways to implement a web front end to this application, before changing direction and considering the application from the perspective of XSLT.]
  Contact Us | E-mail Us | Site Guide | About PerfectXML | Advertise ©2004 perfectxml.com. All rights reserved. | Privacy