JSP syntax is quite straightforward and compact: it all fits on
a double-page syntax card available from Sun
(java.sun.com/products/jsp/syntax.pdf). A JSP consists of template
data and JSP elements, which fall into the following groups:
directives, scripting elements, comments, and
actions. Scripting elements are further subdivided into
declarations, expressions, and code fragments (or
scriptlets). We'll see their syntax and describe their
meaning shortly.
The first three groups have always been part of JSP, and they
have non-XML syntax as well as alternative XML+Namespace syntax.
The actions group is more recent, and uses only the XML+Namespace
syntax.
Non-XML Elements
Non-XML syntax of directives, scripting elements and comments is
summarized in the table below. You have seen all of them used in
our simple example:
|
Type of element
|
Syntax description
|
Example
|
|
Directives
|
<%@ directive %>
|
<%@ page language="java" %>
|
|
Declarations
|
<%! declarations %>
|
<%! int i=0, j=5; %>
|
|
Expressions
|
<%= expression %>
|
<%= i+7 %>
|
|
Code fragments
|
<% code fragment %>
|
<% if(i < j-4 { %>
|
|
Comments
|
<%-- comment --%>
|
<%-- not for the client --%>
|
What they all Mean
Ø Directives are addressed to
the JSP engine. They do not produce any output. You will see two
directives in our output pages: page and include. include does
exactly what its name suggests: it is used to include files into a
page. The page directive is for setting the global properties of
the page, such as language and content type.
Ø Within scripting elements,
declarations are exactly that: Java declarations and (perhaps)
initializations. Declarations do not produce any output.
Ø Expressions are Java
expressions; they are evaluated and their values are inserted into
the output stream.
Ø Code fragments are
stretches of Java code. They don't have to be complete statements
or valid expressions.
Ø JSP comments are not sent
to the client; they are strictly for documenting code.
Ø You can also use standard
XML comments in a JSP, and they will be treated like regular XML
comments. You can even include non-XML JSP content in XML-style
comments, and it will be treated as part of the comment - that
is, it will be ignored.
Action Elements
Action elements fall into two groups:
Ø Actions having to do with
JavaBeans: useBean, getProperty, setProperty. Beans are Java
classes that conform to some simple patterns: using public get/set
methods to access properties, providing a public, no-arguments
constructor, and being serializable.
Ø include and forward
actions
All action tags appear with the jsp: namespace prefix; here's an
example of using them:
<jsp:useBean id="mbean" scope="session"
class="weather.MainBean" />
Beginning with JSP 1.1, it is possible to define custom actions
(using the jsp:taglib element). This has the potential greatly to
extend the application of JSP and the reuse of common
functionality, allowing non-programmers to create JSPs by
simplifying the architecture of JSP-based applications.
JSPs in the Application
Our application uses the main JSP page for overall control,
configure.jsp to configure the system, and several JSP pages for
output. I'll show them in that order.
The Main Page, weather.jsp
This page never sees the light of day (in the browser), so it
has only JSP elements: two directives, a useBean action, and a
piece of Java code. The code carries out the dialog between the
main page and the main bean described in the diagram: the main page
sends the request object to the main bean for analysis; the main
bean tells it to which output page to forward the request. Here's
the code:
<%@ page errorPage="errorpage.jsp" %>
<%@ include file="configure.jsp" %>
<jsp:useBean id="qBean" class="MyNa.jspUtil.QBean"
scope="session"/>
<%
qBean.doCommand(request);
if(true) {
out.clear();
pageContext.forward(qBean.whereTo());
return;
}
%>
configure.jsp
Here is configure.jsp. Like weather.jsp, it contains nothing but
Java code:
<%
// Information for connecting to database
String[] dbParams = new String[] {
"sun.jdbc.odbc.JdbcOdbcDriver",
"jdbc:odbc:WEATHER",
"userName",
"defaultPassword"
};
// Queries available to the client
String[][] dbQueries = new String[][] {
{"AllTable", // All fields
formatted as a table
"SELECT * FROM FORECAST
WHERE Zip=?"},
{"AllText", // All fields
formatted as a paragraph of text
"SELECT * FROM FORECAST
WHERE Zip=?"},
{"TimeTemp", // Date and
temperature only
"SELECT
RecTime,Temp,DayLo,DayHi FROM FORECAST WHERE Zip=?"}
};
// Associate each query with an output JSP file
String[][] responseTargets = new String[][]{
{"AllTable ",
"/jsp/weather/xhtml/AllTable.jsp"},
{"AllText ",
"/jsp/weather/xhtml/AllText.jsp"},
{"TimeTemp",
"/jsp/weather/xhtml/TimeTemp.jsp"}
};
// This code is actually run once per session
qBean.doConfigure(dbParams, dbQueries,
responseTargets);
%>
As you can see, there are three declaration sections in the
file, followed by a single function call. The first section has to
do with information that is needed to connect to a database using a
JDBC driver - that is, information needed to create a
Connection object.
The second section has to do with running queries. It consists
of name-value pairs, where each value is a string you would use to
create a PreparedStatement object. These values are stored in a
dictionary-like object (a Hashtable), indexed by the corresponding
names. In order to run a query, the user has only to specify its
name (in a select element of the form) and provide the values for
the parameters of the PreparedStatement. Since these parameters are
ordered, the form elements for entering query parameters must have
such names as Parameter1, Parameter2, and so on (or, to shorten
them for the wireless Web where every byte counts, QP1, QP2). This
is a convention that is needed for our DBHandler.
The third section associates output templates with query names.
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.
The configuration page includes a call to qBean.doConfigure().
The task of doConfigure() is to set up an object of our DBHandler
class, presented in the next section.
Although doConfigure() is called once per request, it will have
no effect after the session is first 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.)
We could also instantiate and configure the main bean, including
a DBHandler, from an XML configuration file. This would require
more background machinery to explain. The method shown here allows
a system administrator to configure the database access and alter
the target files without introducing too many new concepts.
JSPs for Output
There are three output pages corresponding to three queries:
TimeTemp, AllTable, and AllText. Here is AllTable.jsp:
<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//PHONE.COM//DTD WML 1.1//EN"
"http://www.phone.com/dtd/wml11.dtd">
<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="AllTable">
<do type="accept"
label="again" > <go href="#askCard" /> </do>
<%
MyNa.jspUtil.QueryResult qR = qBean.queryResult();
String[][] rows
= (null == qR) ? null : qR.getRows();
if(null == rows
|| rows.length < 1) {
%>
<p>Sorry, no weather in
zip-code.</p>
<%
} else {
String[] headers = qR.getColumnHeaders();
%>
<p>Weather:</p>
<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>
<card id="askCard" title="Weather">
<do type="accept" label="Go!"
>
<go
href="weather.jsp">
<postfield name="query" value="$query" />
<postfield name="QP1" value="$QP1" />
<postfield name="target" value="wml-$(query)" />
</go>
</do>
<p align="center">Weather
Page</p>
<p>
Zip?
<input
name="QP1" format="*N" maxlength="10" value="" /><br
/>
Query?
<select
name="query">
<option value="TimeTemp" > time & temp
</option>
<option value="AllTable" > all fields </option>
<option value="AllText" > all fields-format
</option>
</select>
</p>
</card>
</wml>
Notice how this page gets the result set from the DBHandler as a
string matrix and dumps it directly to an output table, without any
regard for its size. This is OK for a web application, but a risky
thing to do in a WAP application. It is for this reason that we
also provide an AllText query, in which the output is crafted by
hand and so can be controlled more precisely. Here are the cards
for AllText.jsp:
<card id="output" title="TimeTemp">
<do type="accept" label="again">
<go href="#ask" />
</do>
<do type="accept" label="details" >
<go href="#details" />
</do>
<% //
Zip,Day,RecTime,Temp,DayLo,DayHi,Precip%,Warn,Tomorrow,NextDay
MyNa.jspUtil.QueryResult qR =
qBean.queryResult();
String[][]rows = (null == qR) ?
null : qR.getRows();
if(rows == null || rows.length
< 1){
// title row should be always
there
%>
<p>Sorry, no weather in your zip
</p>
</card>
<% } else {
Dict D = new Dict();
D.setDef(qR.getColumnHeaders(), rows[0]);
D.setOutLimit(300);
%>
<p>
zip: <%= D.getDef("zip") %>
<br/>
temp: <%= D.getDef("temp")
%><br/>
at <%= D.getDef("RECTIME")
%> <br/>
day's lo: <%=
D.getDef("daylo") %> <br/>
day's hi: <%=
D.getDef("dayhi") %>
</p>
</card>
<card id="details" title="TimeTemp">
<do type="accept" label="again" >
<go href="#ask" />
</do>
<do type="accept" label="more" >
<go href="#moreDetails" />
</do>
<p>
day: <%=D.getDef("day")
%><br/>
precip: <%=D.getDef("precip")
%>
</p>
</card>
<card id="moreDetails" title="TimeTemp">
<do type="accept" label="again" > <go
href="#ask" /> </do>
<do type="accept" label="basics" > <go
href="#output" /> </do>
<p>
<% String W =
D.getDef("warn");
if (W.length()
> 0) { %>
Warning: <%= W %>
<br/>
<% } %>
Tomorrow: <%= D.getDef("tomorrow")
%> <br/>
NextDay: <%= D.getDef("nextday")
%>
</p>
</card>
<% } %>
<!-- the remaining ask card is the same -->
This time, we control the amount of output by means of the
setOutLimit() method, and we provide three cards of increasing
level of detail. Something to be aware of is that the amount of
output is controlled on the server, in characters. Testing is
needed to see how this limit translates into limits on the amount
of output going from the gateway to the browser.
JDBC basics
We have now seen the configuration page and the output pages.
It's time to look at the machinery in the middle that gets the
data. In outline, JDBC code usually goes through the following
steps:
Ø Load the database
driver
Ø Open a connection to the
database
Ø Create a Statement
object
Ø Use the Statement object to
send SQL statements to the database
Ø Process the results
I will now go through the steps in more detail, with simple code
to illustrate them.
Load the driver
The JDBC API is based on the notion of database-specific drivers
that are manipulated by a DriverManager object.
Drivers come in several shapes and forms. Some drivers are
freeware or open source; others are commercial products. The JDK
comes with a generic JDBC-ODBC bridge that passes SQL
statements on to an appropriate ODBC driver. This way, a Java
application can work with any database for which an ODBC driver is
available. JDBC drivers that talk to the database directly will
generally provide better performance, though, so the bridge
shouldn't be used in production environments.
A JDBC driver is loaded by calling the forName() method of the
class called Class:
String driverName =
"sun.jdbc.odbc.JdbcOdbcDriver";
...
Class.forName(driverName);
Objects of the Class class contain information about Java
classes, such as the names of their methods and the arguments of
their constructors.
For each Java class that has been loaded into the Java Virtual
Machine there is a Class object. You can retrieve that object by
calling the getClass() method on any instance of your class, or you
can simply say myObj.class. Once you have obtained the Class object
for your class, you have access to a lot of information about
it.
The forName() method dynamically loads a class that has not yet
been loaded. Since a JDBC driver is a Java class, it can be loaded
using the forName() method. All you need to know is the fully
qualified name of the driver, which in our case is
sun.jdbc.odbc.JdbcOdbcDriver.
Open a connection
String dbURL = "jdbc:odbc:PhoneBook";
...
<