JSP - Personal World Wide Web Pages

advertisement
Introducing JavaServer Pages
Based on servlet technology, and currently shaping up at breakneck speed, JavaServer Pages (JSP) is set
to be one of the most important elements of Java server programming. It's by no means complete yet,
but that will change as the Java 2 Enterprise Edition comes together.
So what are JavaServer Pages? Well, they combine markup (whether HTML or XML) with nuggets of Java
code to produce a dynamic web page. Each page is automatically compiled to a servlet by the JSP engine,
the first time it is requested, and then executed. JSP provides a variety of ways to talk to Java classes,
servlets, applets and the web server. With it, you can split the functionality of your web applications into
components with well-defined public interfaces glued together by a simple page.
This model allows tasks to be sub-divided - a developer builds custom components and the page designer
assembles the application with a few judicious method calls. In this 'application assembly' model, the
business logic is separated from the presentation of data.
To give you an idea of the future, this separation of logic and presentation may become yet more extreme
with the use of custom tags slated for JSP 1.1.
JavaServer Pages is a specification that is already implemented by several web servers (for more details,
see the JSP FAQ at http://www.esperanto.org.nz/jsp/jspfaq.html), on which your code will run without
change, making it more portable and the server market more competitive than its rivals. Finally, it's nice
and simple!
In this chapter, we will :




Discuss the JavaServer Pages (JSP) architecture
Look at the elements of a JSP file, and the tags used to represent them
Encapsulate logic in a JavaBean component and integrate it with JSP
Walk through a detailed example using JSP, showing a typical web application architecture
Architectural Overview
A JavaServer Page is a simple text file consisting of HTML or XML content along with JSP elements (a sort
of shorthand for Java code). When a client requests a JSP page of the web server and it has not been run
before, the page is first passed to a JSP engine which compiles the page to a servlet, runs it and returns
the resulting content to the client. Thereafter, the web server's servlet engine will run the compiled page.
It should be no surprise, given the flexibility of the servlet model, that the current reference
implementation of the JSP engine is itself a servlet.
It is possible to view the finished servlet code that is generated by locating it within the directory structure
of the servlet engine. For example, with JRun, you can find the source code for your JSP files (in servlet
form) in the jrun/jsm-default/services/jse/servlets/jsp directory. This is very helpful when
trying to debug your JSP files.
If you take a look in the source files for the javax.servlet.jsp package, you'll find the following
classes :


JSPPage
HttpJspPage
They define the interface for the compiled JSP page - namely that it must have three methods. Not
surprisingly they are :

jspInit()


jspDestroy()
_jspService(HttpServletRequest request, HttpServletResponse response)
The first two methods can be defined by the JSP author (we'll see how in a moment), but the third is the
compiled version of the JSP page, and its creation is the responsibility of the JSP engine.
It's time we saw a JSP file :
<html>
<head>
<title>Demo of a JSP page</title>
</head>
<body>
<!-- Set global information for the page -->
<%@ page language="java" %>
<!-- Declare the character variable -->
<%! char c = 0; %>
<!-- Scriptlet - Java code -->
<%
for (int i = 0; i < 26; i++)
{
for (int j = 0; j < 26; j++)
{
// Output capital letters of the alphabet, and change starting letter
c = (char)(0x41 + (26 - i + j)%26);
%>
Continued on Following Page
<!-- Output the value of c.toString() to the HTML page -->
<%= c %>
<%
}
%>
<br>
<%
}
%>
</body>
</html>
This page just outputs the alphabet 26 times and changes the starting letter. The HTML is self-explanatory
and written the way it should be, rather than cluttering up methods of a servlet.
Elements of a JavaServer Page
The Java code in the page includes :




Directives – these provide global information to the page, for example, import statements, the
page for error handling or whether the page is part of a session. In the above example we set the
script language to Java.
Declaratives - these are for page-wide variable and method declarations.
Scriptlets - the Java code embedded in the page.
Expressions - formats the expression as a string for inclusion in the output of the page.
We will meet the last JSP element type, actions, soon. These elements follow an XML-like syntax, and
perform a function behind the scenes. They provide the means to totally separate presentation from logic.
A good example is <jsp:useBean .../> which finds or creates an instance of a bean with the given
scope and name. With the tag extension mechanism to be introduced in JSP 1.1, you'll be able to define
similar action tags and put their functionality in a tag library.
Now let's examine the basic elements of a JSP in a more complete fashion, before we code some more.
Something I've found very useful to have around while coding is the syntax crib sheet available at
http://java.sun.com/products/jsp/syntax.html in PDF format. This has a concise summary of what we'll
see here.
JSP Directives
A JSP directive is a statement that gives the JSP engine information for the page that follows. The general
syntax of a JSP directive is <%@ directive { attribute=”value” } %>, where the directive may
have a number of (optional) attributes. Each directive has an optional XML equivalent, but these are
intended for future JSP tools, so we won't consider them here.
Possible directives in JSP 1.0 are :



Page – information for that page
Include – files to be included verbatim
Taglib – the URI for a library of tags that you'll use in the page (unimplemented at the time of
writing)
As is to be expected, the page directive has many possible attributes. Specifying these is optional, as the
mandatory ones have default values.
Attribute and possible values
language=”java”
Description
The language variable tells the server what
language will be used in the file. Java is the
only supported syntax for a JSP in the current
specification.
Support for other scripting languages is
available at http://www.plenix.org/polyjsp and
http://www.caucho.com (JavaScript).
extends="package.class"
The extends variable defines the parent class
of the generated servlet. It isn't normally
necessary to use anything other than the
provided base classes.
import="package.*,package.class"
The import variable is similar to the first
section of any Java program. As such, it should
always be placed at the top of the JSP file. The
value of the import variable should be a
comma-separated list of the packages and
classes that you wish to import.
session="true|false"
By default, the session variable is true,
meaning that session data is available to a
page.
buffer="none|8kb|sizekb"
Determines if the output stream is buffered. By
default it is set to 8kb. Use with autoFlush
autoFlush="true|false"
If set to true, flushes the output buffer when
it's full, rather than raising an exception.
Continued on Following Page
Attribute and possible values
isThreadSafe="true|false"
Description
By default this is set true, signaling to the JSP engine
that that multiple client requests can be dealt with at
once. It's the JSP author's job to synchronize shared
state, so that the page really is thread safe.
If isThreadSafe is set to false, the single thread
model will be used, controlling client access to the
page.
This doesn't let you off the hook, however, as servers
may, at their discretion, have multiple instances of the
page running to deal with the client request load. And
there's no guarantee that consecutive requests from
the same client will be directed to the same instance
of JSP page. Any resources or state that are shared
between page requests must therefore be
synchronized.
info="text"
Information on the page that can be accessed through
the page's Servlet.getServletInfo() method.
errorPage="pathToErrorPage"
Gives the relative path to the JSP page that will
handle unhandled exceptions. That JSP page will have
isErrorPage set to true
isErrorPage="true|false"
Marks the page as an error page. We'll see this in
action later.
contentType="text/html;
charset=ISO-8859-1"
The mime type and character set of the JSP and the
final page. This information must come early in the
file, before any non-latin-1 characters appear in the
file.
In the JSWDK-1.0-ea1 release there's a bug - the import statement needs to be imports to satisfy the
JSP engine.
We'll see more of the include directive in a later example.
JSP Declarations
A JSP declaration can be thought of as the definition of class-level variables and methods that are to be
used throughout the page. To define a declarative block, begin the block of code with <%!
declaration>.
<%!
String var1 = "x";
int count = 0;
private void incrementCount()
{
count++;
}
%>
Note that you put semi-colons after each variable declaration, just as if you were writing it in a class.
This is how you would define the optional jspInit() and jspDestroy() methods that we mentioned
earlier.
JSP Scriptlets
Scriptlets are defined as any block of valid Java code that resides between <% and %> tags.
This code will be placed in the generated servlet's _jspService() method. Code that is defined within a
scriptlet can access any variable and any beans that have been declared. There are also a host of implicit
objects available to a scriptlet from the servlet environment.
Implicit Objects
request
Description
response
The JSP page's response, a subclass of HttpServletResponse.
pageContext
Page attributes and implicit objects (essentially what makes up the
server environment in which the JSP runs) need to be accessible through
a uniform API, to allow the JSP engine to compile pages. But each server
will have specific implementations of these attributes and objects.
The client's request. This is usually a subclass of HttpServletRequest.
This has the parameter list if there is one.
The solution to this problem is for the JSP engine to compile in code that
uses a factory class to return the server's implementation of the
PageContext class. That PageContext class has been initialized with the
request and response objects and some of the attributes from the page
directive (errorpage, session, buffer and autoflush) and provides the
other implicit objects for the page request. We'll see more on this in a
moment.
session
The HTTP session object associated with the request.
application
The servlet context returned by a call to
getServletConfig().getContext() (see Chapter 5).
out
The object representing the output stream.
Implicit Objects
config
Description
page
The page's way of referring to itself (as an alternative to this in any Java
code).
exception
The uncaught subclass of Throwable that is passed to the errorpage URL.
The ServletConfig object for the page.
The following snippet shows both how to get a named parameter from the request object, and how to
pass a string to the output stream for the page.
<%
String var1 = request.getParameter("lname");
out.println(var1);
%>
Having discussed implicit objects, we're in a better position to understand the code featured in the
comment for the PageContext source from the JSP 1.0 reference implementation. This shows the code
that the JSP 1.0 reference engine injects into the JSP page's _jspService() method.
public void _jspService(HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
JspFactory factory = JspFactory.getDefaultFactory();
PageContext pageContext = factory.getPageContext(
this, // servlet
request,
response,
null, // errorPageURL
false, // needsSession
JspWriter.DEFAULT_BUFFER,
true
// autoFlush
);
// Initialize implicit variables for scripting environment
HttpSession session = pageContext.getSession();
JspWriter out = pageContext.getOut();
Object page = this;
try
{
// Body of translated JSP here...
} catch (Exception e)
{
out.clear();
pageContext.handlePageException(e);
} finally
{
out.close();
factory.releasePageContext(pageContext);
}
}
JspFactory returns the pageContext implicit object, from which the other implicit objects are obtained
for the duration of the _jspService() method.
JSP Expressions
A JSP expression is a very nice tool for embedding values within your HTML code. Anything between <%=
and %> tags will be evaluated, converted to a string, and then displayed. Conversion from a primitive type
to a string is handled automatically.
The current price of item A100 is
<%= request.getParameter("price") %>
Something you'll want to note is that the expression doesn't end with a semi-colon. That's because the
JSP engine will put the expression within an out.println() call for you.
JSP expressions allow you to essentially parameterize HTML (just as you would parameterize a SQL query
that differs by only a couple of values). Again and again, your code will set up conditions and loops using
a one-line JSP scriptlet and then include the HTML code directly beneath it. Simply enclose the beginning
and the ending of the condition or loop in separate pairs of <% and %> tags :
<% for (int i = 0; i < 10; i++)
{ %>
<br>
Counter value is <%= i %>
<% } %>
Coding JSP Pages
A big advantage in developing a JavaServer Page is that you can code the HTML without enclosing it in
Java code, as you must do in a servlet. You can then take advantage of HTML editors to develop your
content.
So from the drudgery of coding HTML output in servlets, we've arrived at the flexibility of coding Java
snippets into the HTML page. But don't be dragged too far the other way. Heard of the people who can't fit
their ASP files into Notepad? And if there's one problem with ASP and JSP, it's debugging the pages,
making improvements to them, and untangling the meaning of that section you coded months back.
The "third way" is a mixture of presentation-based calls to a Bean or servlet, which components can be
better tested, are well-encapsulated and good OOP citizens. This moves from embedding code to
embedding components and action tags in the page.
Note that the current reference JSP implementations don't automatically reload the new Bean if you
recompile while the server is running. You'll need to restart the servlet engine.
Using JavaBeans Components with JSP
So, when setting up an application development architecture that involves JavaServer Pages, it is a good
idea to try and put all of your business logic inside reusable components. These components can then be
'plugged' into any JavaServer Page that requires them.
What is a JavaBean?
The Java language has implemented the idea of a component as something called a JavaBean. A JavaBean
is a Java class that fits the following criteria :



Public class.
Public constructor with no arguments.
Public set and get methods to simulate properties. The get method has no arguments, unless it
acts on an indexed property.
The JavaBeans architecture uses reflection to infer the public methods. But you can provide a BeanInfo
class, called BeanName BeanInfo to give more explicit information.
A bean can also be serialized and saved for later use. It does this by implementing the Serializable
interface. When a bean component is serialized, it saves its current state. The current state of a bean is
defined by the current values of its public properties.
Properties are always set and retrieved using a common naming convention. For each property, two
methods must exist, a getxxx() and setxxx() method (where xxx is the name of the property).
Apart from that a bean is just like any other Java class. Typically, a bean component is imported into a
program, its properties are set and its methods are called.
Most of the time beans are used to encapsulate visual and non-visual elements of a GUI. There's some
snazzy stuff to link beans up, to implement drag-and-drop and to save the state of a bean between
instances. The ultimate aim is to make them part of graphical application assembly tools. I found that
once I stopped thinking about these graphical tools and concentrated on it just being a class that follows a
few design patterns, things were easier to understand!
Because of this history, Beans don't sit obviously in JSP. Don't get me wrong - they work well. But it's not
what they were originally designed for. If we think of them as components, simple encapsulations of Java
code, then their purpose is clearer. Beans mean that your page isn't clogged with code.
Much of the business logic we're hinting at might be better placed in an Enterprise JavaBean, where the
transactions and scaling issues are explicitly the problem of the container and not the bean. That kind of
heavyweight use may be necessary sometimes but I think that beans will stay around for the lighter work
we show here. And beans as graphical components of your final web page, for a richer user interface, will
be another expression of their code- and complexity-wrapping role.
Letters from a Bean
To show how to use a simple bean, we're going to develop the alphabet example from earlier, and
associate each letter with a color.
The presentation of the letters will remain the responsibility of the JSP page, but the color mapping will be
the bean's job.
package com.wrox.jspexamples;
import java.awt.Color;
import java.util.*;
public class AlphabetCode
{
HashMap map;
char c = 0;
Integer colorNumber;
static int FIRST_LETTER = 0x41;
static int ALPHABET_LENGTH = 26;
float s = 0.9f;
float b = 0.9f;
public AlphabetCode()
{
this.map = new HashMap(ALPHABET_LENGTH);
for (int i = 0; i < ALPHABET_LENGTH; i++)
{
this.c = (char)(FIRST_LETTER + i);
float h = (float)i/ALPHABET_LENGTH;
this.map.put(new Character(c), Color.getHSBColor(h, s, b));
}
}
public void setCharacter(String nextChar)
{
this.c = nextChar.charAt(0);
}
public String getCharacter()
{
return (new Character(this.c).toString());
}
public String getColor()
{
Color rgb = (Color)map.get(new Character(this.c));
StringBuffer htmlColor = new StringBuffer(
colorNumber.toHexString(rgb.getRGB()
& 0x00ffffff));
// toHexString() won't preserve leading zeros, so need to add them back in
// if they've gone missing...
if (htmlColor.length() != 6)
{
htmlColor.insert(0, "\"#00");
} else
htmlColor.insert(0, "\"#");
htmlColor.append("\"");
return htmlColor.toString();
}
}
The bean things to note are the public class and constructor, and the set and get methods. The class sets
up the color of each character in the constructor, and then returns the color of the current character as an
HTML color.
Letters from a Bean
The modified JSP page looks like :
<html>
<head>
<title>
Color demo I</title>
</head>
<body>
<!-- This page generates a series of rainbow colored letters -->
<!-- It uses plain method calls to the Bean -->
<%@ page language="java" %>
<%! char c = 0; %>
<jsp:useBean id="letterColor" scope="application"
class="com.wrox.jspexamples.AlphabetCode" />
<%
for (int i = 0; i < 26; i++)
{
for (int j = 0; j < 26; j++)
{
c = (char)(0x41 + (26 - i + j)%26);
Character thisChar = new Character(c);
letterColor.setCharacter(thisChar.toString());
%>
<FONT COLOR=<%= letterColor.getColor() %> >
<%= letterColor.getCharacter() %>
</font>
<%
}
%>
<BR>
<%
}
%>
</body>
</html>
Giving the result :
The JSP page uses straight method calls ( setCharacter(), getCharacter() and getColor()) to
the bean. The only new syntax is <jsp:useBean ... />, so let's look at that :
<jsp:useBean id="letterColor" scope="application"
class="com.wrox.jspexamples.AlphabetCode" />
The jsp:useBean tag first searches for a bean instance that matches the scope and class. If it can't find
one it instantiates the named class, using its public, no-args constructor. If there are any initialization
time properties you need to set, you can place the appropriate tags between <jsp:useBean> and
</jsp:useBean> - these are only executed when a new instance is created.
Letters from a Bean
In the JSP 1.0 reference implementation, the server looks for your packages in the CLASSPATH
environment variable (on my machine, startserver includes jsp1.0/examples/WEBINF/jsp/beans). Let's look at the attributes :
useBean Attributes
Description
id="name"
The name by which the instance of the bean can be referenced
in the page. Other Bean tags use name to refer to it, so do note
the difference.
scope="page"
The scope over which the bean can be called. More below.
class="package.class"
Fully qualified name of the bean class. Because this needs to
be the full name, you don't need to import these classes in the
page directive.
beanName="name"
This allows you to specify a serialized bean (.ser file) or a
bean's name that can be passed to the instantiate() method
from the java.beans.Beans. Needs an associated type tag
rather than class.
type="package.class"
A synonym for class that is used for beanName.
Scope on a JSP page goes something like this :
Scope
page
Description
This is the scope of the PageContext object we saw earlier. It lasts
until the page completes, and there is no storage of state.
page wasn't implemented in the EA1 reference version of JSP 1.0.
request
The bean instance lasts for the client request. The named bean
instance is available as an attribute of the ServletRequest object,
so it follows the request to another servlet / JSP if control is
forwarded.
session
The bean lasts as long as the client session. A reference is
available through HttpSession.getValue(name).
Don't use this scope level if you have declared the page directive
session false.
application
The bean instance is created with the application and remains in
use until the application ends. It's an attribute of the
ServletContext object.
We're not finished in our tour of tags, though. With a couple of changes to our JSP code, we can show you
two more - jsp:getProperty and jsp:setProperty :
<html>
<head>
<title>
Color demo
</title>
</head>
<body>
<!-- This page generates a series of rainbow colored letters -->
<!-- It uses the JSP property set and get tags -->
<%@ page language="java" %>
<%! char c = 0; %>
<jsp:useBean id="letterColor" scope="application"
class="com.wrox.jspexamples.AlphabetCode" />
<%
for (int i = 0; i < 26; i++)
{
for (int j = 0; j < 26; j++)
{
c = (char)(0x41 + (26 - i + j)%26);
Character thisChar = new Character(c);
%>
<jsp:setProperty name="letterColor" property="Character"
value="<%= thisChar.toString() %>" />
<FONT COLOR=<jsp:getProperty name="letterColor" property="Color" /> >
<jsp:getProperty name="letterColor" property="Character" />
</FONT>
...
The <jsp:setProperty ... /> needs the bean name, the property name and the value with which to
set the property. Note that the property name is the setCharacter() method without the prefix.
<jsp:getProperty ... /> is self-explanatory.
We'll see an alternative version of the jsp:setProperty tag later.
A File Viewer
There's a little too much code in the page in the last example. It shows off the various JSP elements quite
well, but it's not a particularly good design.
If we look at the MVC pattern, we see that the bean is the model, and the page is both the view and the
controller (using the HTTP request). The question is how to prevent the control code taking over the page.
Ideally, the page should be almost all presentation with simple JSP elements (methods and properties) to
control the bean. Then the code to actually manipulate the model is in the bean, but the control rests with
the page.
To show this simply, let's design a file viewer bean that will read a specific directory ( C:\jdk1.2.1) and
output the file and directory names, their size (if they're files) and their timestamp :
package com.wrox.jspexamples;
import
import
import
import
java.io.File;
java.util.Date;
java.util.Iterator;
java.util.Vector;
public class FileViewerBean
{
File myDir;
File[] contents;
Vector vectorList;
Iterator currentFileView;
File currentFile;
public FileViewerBean()
{
myDir = new File("C:\\jdk1.2.1");
vectorList = new Vector();
}
public String getDirectory()
{
return myDir.getPath();
}
public void refreshList()
{
contents = myDir.listFiles();
vectorList.clear();
for (int i = 0; i < contents.length; i++)
vectorList.add(contents[i]);
currentFileView = vectorList.iterator();
}
public boolean nextFile()
{
while (currentFileView.hasNext())
{
currentFile = (File)currentFileView.next();
return true;
}
return false;
}
public String getFileName()
{
return currentFile.getName();
}
public String getFileSize()
{
return new Long(currentFile.length()).toString();
}
public String getFileTimeStamp()
{
return new Date(currentFile.lastModified()).toString();
}
public boolean getFileType()
{
return currentFile.isDirectory();
}
}
The idea here is to make the interface to the bean as simple as possible, so we need to support the
different requirements of JSP pages. Therefore, a full-featured file viewer bean might have further
methods to detail a file's attributes, order the vector by different criteria and so forth.
To put the bean to work, we have the FileView.jsp code :
<html>
<head>
<title>
A JSP file viewer
</title>
</head>
<body>
<!-- This page allows you to see files in selected parts of the drive -->
<%@ page language="java" %>
<jsp:useBean id="fileViewer" scope="session"
class="com.wrox.jspexamples.FileViewerBean" />
<hr>
<jsp:getProperty name="fileViewer" property="Directory" />
<table>
<%
fileViewer.refreshList();
while(fileViewer.nextFile())
{
%>
<tr>
<td>
<%= fileViewer.getFileName() %>
</td>
<td>
<%
if (!fileViewer.getFileType()) {
%>
<%= fileViewer.getFileSize() %>
<%
}
%>
</td>
<td>
<%= fileViewer.getFileTimeStamp() %>
</td>
</tr>
<% } %>
</table>
</body>
</html>
The page is much clearer here, with the bean almost acting as an iterator over the directory listing. The
fine-grained properties simplify the page further.
Browsing and Querying Databases
To illustrate more functionality of JavaServer Pages, we will build an example that allows you to select
from available databases, see their tables and column names, and query them. Our JSP application will
make use of a bean to access the database.
I tried various architectures as I developed the application, as I saw how things panned out and learnt
how to use more tags! The client's request should yield a ResultSet object, which has 22 getXxx()
methods to work with. And I'm not interested in wrapping all that functionality in a bean when it's already
available, so I've decided to handle it in the page.
This is where the tag extension mechanism will prove its worth - in that we'll be able to define actions that
return ResultSet objects for the page to retrieve values from. Similar tags will enable JSPs to handle
iterators.
Working backwards, let's look at the requirements :




Consistent web interface across pages.
Self-contained parts of the application each have their own JSP.
Central error handling.
Same bean code used for different client requests.
The parts of the project are :






DataList.jsp - the main application page. It decodes GET requests and passes them to the
relevant pages.
List1.jsp - Presents the available databases. This is fixed by the sys-admin and is essentially
HTML.
List2.jsp - Shows the tables and their columns for the selected database.
List3.jsp - Shows the result of a SQL query.
ErrorPage.jsp - Where all the exceptions end up, their origin suitably noted.
Header.html - The header for all the application pages.
Ask the Right Question
We want one page to coordinate the application, to receive all requests. We'll start DataList.jsp for the
case where the client has just typed http://URL/DataList.jsp and needs the menu of databases.
<%@ page language="java" errorPage="ErrorPage.jsp" %>
<% if (request.getParameterNames().hasMoreElements() == false)
{ %>
<jsp:forward page="/jsp/data/List1.jsp" />
<% } %>
We have already sorted out an errorpage in the page directive for all error reporting. We'll repeat this
code on each page.
The request object will have no parameters for the plain URL, so we can forward the work to the
List1.jsp page. The <jsp:forward ... /> needs the URL relative to the servlet context /
application. In this case that's /jsp/employee/List1.jsp.
DataList.jsp is only used for checking parameters and forwarding to the right page, you
can use a regular servlet instead. You can mix and match servlets and JSPs depending
which is most appropriate.
Now we'll code the List1.jsp page that shows the client what choice they have. From this they can
formulate a query, or browse those databases :
<html>
<head>
<title>
Database Search
</title>
</head>
<body>
<%@ page language="java" errorPage="ErrorPage.jsp" %>
<%@ include file="Header.html" %>
Choose database:
<form method="GET" ACTION="DataList.jsp">
Database URL:<select name=DbURL size=1>
<option>jdbc:odbc:employee
<option>jdbc:odbc:techlib
<option>jdbc:odbc:this_should_break
</select>
<p>
Database driver:<select name=DbDriver size=1>
<option>sun.jdbc.odbc.JdbcOdbcDriver
<option>com.imaginary.sql.msql.MsqlDriver
</select>
<p>
Input SQL if you know your query:
<p>
<input type=text name=inputSQL size=40>
<p>
<input type=submit>
</form>
</body>
</html>
This is straightforward HTML for the most part. We've sorted out the consistent web interface for the
application by using the <%@ include %> tag to include Header.html. I've put in databases and a
driver I don't have, to test the error handling later.
It should be possible to make this a static HTML file, but the JSP engine complained mightily when I tried.
I'm informed that support for forwards to a static page is a matter of interpretation of the specification at
this time. Something for the future.
Finally, we have specified that DataList.jsp receives any submissions from this page.
To make this example a bit more realistic, we could let the main page (or servlet) create beans with all
available databases and drivers and let this page use the bean to generate the database option lists.
Querying a Database
Assuming all three sections of the List1.jsp form are filled in, DataList.jsp needs to forward the
request to a page that can display the ResultSet of the query, or report why it didn't work.
The changes to DataList.jsp are the following :
<%@ page language="java" errorPage="ErrorPage.jsp" %>
<% if (request.getParameterNames().hasMoreElements() == false)
{ %>
<jsp:forward page="/jsp/data/List1.jsp" />
<% } else if ((request.getParameter("DbDriver") != null) &&
(request.getParameter("DbURL") != null) &&
(request.getParameter("inputSQL") != null))
{ %>
<jsp:forward page="/jsp/data/List3.jsp" />
<% } %>
Browsing our JDBC tutorials, we start to construct a bean that can connect to the specified database URL
with the specified driver, query the database, and provide suitable methods to display the returned data.
Plus if it all goes wrong the error handling needs to be informative.
package com.wrox.jspexamples;
import java.sql.*;
import java.io.*;
public class DbBean
{
String dbURL;
String dbDriver = "sun.jdbc.odbc.JdbcOdbcDriver";
private Connection dbCon;
public DbBean()
{
super();
}
public boolean connect() throws ClassNotFoundException, SQLException
{
Class.forName(this.getDbDriver());
dbCon = DriverManager.getConnection(this.getDbURL());
return true;
}
public void close() throws SQLException
{
dbCon.close();
}
public ResultSet execSQL(String sql) throws SQLException
{
Statement s = dbCon.createStatement();
ResultSet r = s.executeQuery(sql);
return (r == null) ? null : r;
}
public String getDbDriver()
{
return this.dbDriver;
}
public void setDbDriver(String newValue)
{
this.dbDriver = newValue;
}
public String getDbURL()
{
return this.dbURL;
}
public void setDbURL(String newValue)
{
this.dbURL = newValue;
}
}
Note that we've not wrapped the ResultSet, as it's the view of the data that the page will manipulate.
There's also no error handling in here, bar the mandatory declaration of throws clauses. The context of
the error is better handled in the JSP where the call was made.
Let's write a test class to test out the methods and get the error handling sorted :
import java.sql.*;
import com.wrox.jspexamples.DbBean;
public class TestDbBean
{
public static void main(String[] argc)
{
DbBean myDbBean = new DbBean();
myDbBean.setDbDriver("sun.jdbc.odbc.JdbcOdbcDriver");
myDbBean.setDbURL("jdbc:odbc:techlib");
int numColumns = 0;
String sql = "select authid, lastname, firstname from authors";
ResultSet rs = null;
ResultSetMetaData rsmd = null;
try {
myDbBean.connect();
} catch (ClassNotFoundException e)
{
System.out.println("connect() ClassNotFoundException: " + e);
} catch (SQLException e)
{
System.out.println("connect() SQLException: " + e);
}
try
{
rs = myDbBean.execSQL(sql);
} catch (SQLException e)
{
System.out.println("execSQL() SQLException: " + e);
}
try
{
rsmd = rs.getMetaData();
numColumns = rsmd.getColumnCount();
for (int column = 1; column <= numColumns; column++)
{
System.out.println(rsmd.getColumnName(column));
}
while (rs.next())
{
for (int column = 1; column <= numColumns; column++)
{
System.out.println(rs.getString(column));
}
}
myDbBean.close();
} catch (SQLException e)
{
System.out.println("Problems with the database - SQLException: " + e);
}
}
}
Now we know it works, we can reuse the logic in the JSP file, and we've got the makings of the error
handling. We want to tell the client what went wrong, and show them how to correct it if possible.
The possible errors are :




The drivers for the database are unavailable ( ClassNotFoundException) - which only a swift
kick to the system administrator will remedy.
The DriverManager.getConnection() fails because the dbURL is incorrect - another job for the
sys admin.
The call to execSQL() can throw a SQLException if the query is invalid - that's something that
the user should be able to sort out.
The rest are database or page errors, and should be shown as such.
Finally, we can code List3.jsp :
<HTML>
<HEAD>
<TITLE>
Database Search
</TITLE>
</HEAD>
<BODY>
<%@ page language="java" import="java.sql.*" errorPage="ErrorPage.jsp" %>
<%@ include file="Header.html" %>
<jsp:useBean id="db" scope="request" class="com.wrox.jspexamples.DbBean" />
<jsp:setProperty name="db" property="*" />
<%! int numColumns;
ResultSet rs = null;
ResultSetMetaData rsmd = null;
%>
<CENTER>
<H2>Results from</H2>
<H2><%= request.getParameter("inputSQL") %></H2>
<HR>
<BR><BR>
<TABLE BORDER="1" BGCOLOR="#cccc99" BORDERCOLOR="#003366">
<TR>
<%
String sql = request.getParameter("inputSQL");
try
{
db.connect();
} catch (ClassNotFoundException e)
{
throw new ServletException("Database drivers not available", e);
} catch (SQLException e)
{
throw new ServletException("Database URL is wrong", e);
}
Continued on Following Page
try
{
rs = db.execSQL(sql);
} catch (SQLException e)
{
throw new ServletException("Your query isn't working. " +
"Do you want to browse the database? " +
"If so, leave the SQL input empty", e);
}
try
{
rsmd = rs.getMetaData();
numColumns = rsmd.getColumnCount();
for (int column = 1; column <= numColumns; column++)
{
%>
<TH><%= rsmd.getColumnName(column) %></TH>
<%
}
%>
</TR>
<%
while (rs.next())
{
%>
<TR>
<%
for (int column = 1; column <= numColumns; column++)
{
%>
<TD><%= rs.getString(column) %></TD>
<%
} %>
</TR>
<%
}
rs.close();
db.close();
} catch (SQLException e)
{
throw new ServletException("Database error. The query worked, " +
"but the display didn't", e);
}
%>
</TABLE>
</CENTER>
<P>
<FORM METHOD="GET" ACTION="DataList.jsp">
<INPUT TYPE=hidden NAME=DbDriver VALUE=<%= request.getParameter("DbDriver") %>>
<INPUT TYPE=hidden NAME=DbURL VALUE=<%= request.getParameter("DbURL") %>>
Input SQL:<INPUT TYPE=text NAME=inputSQL SIZE=40>
<P>
<INPUT TYPE=submit>
</FORM>
</BODY>
</HTML>
There are a couple of noteworthy things in the code. First is the alternative use of jsp:setProperty
tags.
<jsp:setProperty name="db" property="*" />
This matches request parameters with the property names of the db bean instance. DbDriver and
DbURL will match, and so both these will be set with the values in the request.
This is a shorthand form of
<jsp:setProperty name="beanName" property="propertyName" param="parameterName" />
where propertyName and parameterName are identical.
In order that users can requery the database we have an input form that uses the current DbURL and
DbDriver values as hidden input and takes a new SQL query.
The error handling is split into the connection, querying and display parts of the page, each throwing a
different ServletException that the ErrorPage.jsp file will display.
The errorpage for the application must declare itself with the isErrorPage page directive set to true.
Then we output the implicit exception object. And just to see where we came from and what we've
brought with us, we cycle through the parameter and attribute names.
<HTML>
<TITLE>
DataList Error Page
</TITLE>
<BODY>
<%@ include file="Header.html" %>
<!-- Need an error page to handle the exception message -->
<!-- What error page does an error page use? -->
<%@ page language="java" isErrorPage="true" import="java.util.*, java.sql.*" %>
<BR>
<H4>Exception details:</H4>
<P>
<!-- The fully-qualified class that is the exception -->
<%= exception.toString() %>
<BR>
<!-- The exception's message to the world -->
<%= exception.getMessage() %>
<P>
Continued on Following Page
<A href="DataList.jsp">Want to try again?</A>
<P>
<%! Enumeration parameterList; %>
<%! Enumeration attributeList; %>
<P>
<H4>Parameter listing: </H4>
<P>
<%
parameterList = request.getParameterNames();
while (parameterList.hasMoreElements()) {
%>
<%=
parameterList.nextElement().toString() %> <BR>
<% } %>
<P>
<H4>Attribute listing: </H4>
<P>
<%
Enumeration attributeList = request.getAttributeNames();
while (attributeList.hasMoreElements()) {
%>
<%=
attributeList.nextElement().toString() %> <BR>
<% } %>
</BODY>
</HTML>
Browsing Databases
What if the user selects one of the available databases, and the driver, but doesn't enter an SQL query,
because they don't have the table details. We need to decode that, and then show the user the database
information they need to form a query.
First off, how do we look at the tables of a database? Reaching for Chapter 19 of Beginning Java 2, I see
that you need the metadata of the connection - encapsulated in the java.sql.DatabaseMetaData
object. The following code snippet shows this :
private DatabaseMetaData dbMetaData;
static String[] tableTypes = {"TABLES"};
...
dbMetaData.getTables(catalog, schema, tableName, tableTypes)
dbMetaData.getColumns(catalog, schema, tableName, columnName)
Here are the two new DbBean methods we need to return the appropriate ResultSets to the JSP,
public ResultSet getTables() throws SQLException
{
dbMetaData = dbCon.getMetaData();
return dbMetaData.getTables(null, null, null, tableTypes);
}
public ResultSet getTable(String tableName) throws SQLException
{
return dbMetaData.getColumns(null, null, tableName, null);
}
with tableTypes and dbMetaData declared as above. Note how both methods can throw a
SQLException.
Having added the declarations and methods to the DbBean class, here's the new code for the test bean :
while (rs.next())
{
for (int column = 1; column <= numColumns; column++)
{
System.out.println(rs.getString(column));
}
}
}
catch (SQLException e)
{
System.out.println("Problems with the database - SQLException: " + e);
}
// Test for List2.jsp
ResultSet tables = null;
try
{
tables = myDbBean.getTables();
}
catch (SQLException e)
{
System.out.println("getTables() SQLException: " + e);
}
try
{
while (tables.next())
{
String current_table = tables.getString("TABLE_NAME");
System.out.println(current_table);
System.out.println(" ");
ResultSet table = myDbBean.getTable(current_table);
while (table.next())
{
System.out.println(table.getString("COLUMN_NAME"));
}
System.out.println(" ");
}
myDbBean.close();
} catch (SQLException e) {
System.out.println("Table listing failed: " + e);
}
}
}
From this, we can create the List2.jsp file. This will display every table in the database, plus the names
of the columns in that table.
<HTML>
<HEAD>
<TITLE>
Database Details
</TITLE>
</HEAD>
<BODY>
<%@ page language="java" import="java.sql.*" errorPage="ErrorPage.jsp" %>
<%@ include file="Header.html" %>
<jsp:useBean id="db" scope="request" class="com.wrox.jspexamples.DbBean" />
<jsp:setProperty name="db" property="*" />
<%! int numColumns;
String current_table;
%>
The database (<%= request.getParameter("DbURL") %>) you have selected has the
following tables:
<%
try
{
db.connect();
} catch (ClassNotFoundException e)
{
throw new ServletException("Database drivers not available", e);
} catch (SQLException e)
{
throw new ServletException("Database URL is wrong", e);
}
try
{
ResultSet tables = db.getTables();
while (tables.next())
{
current_table = tables.getString("TABLE_NAME");
%>
<P>
<H4><%= current_table %>
</H4>
<TABLE BORDER="1" BGCOLOR="#cccc99" BORDERCOLOR="#003366">
<TR>
<%
ResultSet table = db.getTable(current_table);
// Loop through columns and get their names and characteristics
while (table.next())
{
%>
<TD><%= table.getString("COLUMN_NAME") %></TD>
<%
}
%>
</TR>
</TABLE>
<%
table.close();
}
tables.close();
db.close();
} catch (SQLException e)
{
throw new ServletException("Database problems", e);
}
%>
<P>
<FORM METHOD="GET" ACTION="DataList.jsp">
<INPUT TYPE=hidden NAME=DbDriver VALUE=<%= request.getParameter("DbDriver") %>>
<INPUT TYPE=hidden NAME=DbURL VALUE=<%= request.getParameter("DbURL") %>>
Input SQL:<INPUT TYPE=text NAME=inputSQL SIZE=40>
<P>
<INPUT TYPE=submit>
</FORM>
</BODY>
</HTML>
When you have a text field, the browser will always send a parameter with the same name as the text
field element. The value is an empty string if the user has not entered anything, but it's hard to see if
there are just a couple of space characters in the field. To check if inputSQL contains something that
could be SQL, you need to check for a non-empty string that isn't all space characters. I've created a
convenience method in DataList.jsp to do this. The code for the final DataList.jsp looks like :
<%@ page language="java" errorPage="ErrorPage.jsp" %>
<%! boolean emptySQL(String sql)
{
if (sql != null)
{
if (sql.trim().length() == 0)
return true;
else
return false;
}
return true;
}
%>
<% if (request.getParameterNames().hasMoreElements() == false)
{ %>
<jsp:forward page="/jsp/data/List1.jsp" />
<% } else if ((request.getParameter("DbDriver") != null) &&
(request.getParameter("DbURL") != null) &&
(emptySQL(request.getParameter("inputSQL"))))
{ %>
Continued on Following Page
<jsp:forward page="/jsp/data/List2.jsp" />
<% } else if ((request.getParameter("DbDriver") != null) &&
(request.getParameter("DbURL") != null) &&
(emptySQL(request.getParameter("inputSQL")) == false))
{ %>
<jsp:forward page="/jsp/data/List3.jsp" />
<% } %>
Mixing Servlets and JSPs
As we mentioned at the start of this example, it's very easy to mix JSP and servlets as and when the need
arises. An ideal candidate is the DataList.jsp file which just forwards requests to the right JSP page.
To recast the code as a servlet, we need to use RequestDispatcher classes. We'll look at these in more
detail in Chapter 11, but they are essentially the servlet equivalent of the <jsp:forward ... /> and
<%@ include ... %> tags we've used previously. We need one instance for each forward request.
Remember to change the ListX.jsp files to point to the servlet. I installed the servlet in examples\WEBINF\servlets of the reference implementation and pointed my browser at
http://localhost:8080/examples/servlet/DataList.
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class DataList extends HttpServlet
{
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
RequestDispatcher firstListRD = this.getServletContext().
getRequestDispatcher("/jsp/data/List1.jsp");
RequestDispatcher secondListRD = this.getServletContext().
getRequestDispatcher("/jsp/data/List2.jsp");
RequestDispatcher thirdListRD = this.getServletContext().
getRequestDispatcher("/jsp/data/List3.jsp");
if (request.getParameterNames().hasMoreElements() == false)
{
firstListRD.forward(request, response);
} else if ((request.getParameter("DbDriver") != null) &&
(request.getParameter("DbURL") != null) &&
(emptySQL(request.getParameter("inputSQL"))))
{
secondListRD.forward(request, response);
} else if ((request.getParameter("DbDriver") != null) &&
(request.getParameter("DbURL") != null) &&
(emptySQL(request.getParameter("inputSQL")) == false))
{
thirdListRD.forward(request, response);
}
}
boolean emptySQL(String sql)
{
if (sql != null)
{
if (sql.trim().length() == 0)
return true;
else
return false;
}
return true;
}
}
Note that you can't forward the request if you have an open output stream or writer. This means that you
can't include a file and then forward the request, and so the DataList servlet can't act as a template file.
Enhancing the User Interface with Applets and Beans
The <jsp:plugin> ... </jsp:plugin> tag included in JSP 1.0 (but not currently implemented)
allows you to specify an applet's or bean's place in the final page. For example :
<jsp:plugin type="applet" code="NervousText.class"
codebase="/applets/NervousText"
height="50" width="375" />
<params>
<param name=text value="<%= someObject.getWhackyText() %>" >
</params>
<fallback> <p>It's messed up - apologies</p> </fallback>
</jsp:plugin>
This holds out the potential of creating very rich, dynamic user interfaces within a web page.
Summary
JavaServer Pages technology brings together the power of Java Servlets and the ease of HTML coding to
give developers a powerful method in which to create server-side web applications. It is currently one of
the most exciting and fast-changing topics in the Java world.
In this chapter, we learned the following :



A JavaServer Page file consists of standard markup tags, content, JSP directives, JSP
declarations, JSP scriptlets and actions tags.
The JSP engine performs a first-time compile on a JSP file which generates a new servlet with the
desired content. If the file changes, then a new servlet is generated, otherwise the compiled
version is used.
JavaBeans can be used within a JSP file to help split presentation and logic into their component
parts.
Download