0% found this document useful (0 votes)
2K views684 pages

Developing Java Enterprise Applications

Developing Java Enterprise Applications by Stephen Asbury and Scott R. Weiner is a tutorial and reference for serious developers interested in learning how to build large applications using java. Once upon a time, java was viewed as a language for building tiny Web applications. This book provides not only a detailed description of today's java platform, but all of the enhanced specifications mentioned above.

Uploaded by

anhcdt
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2K views684 pages

Developing Java Enterprise Applications

Developing Java Enterprise Applications by Stephen Asbury and Scott R. Weiner is a tutorial and reference for serious developers interested in learning how to build large applications using java. Once upon a time, java was viewed as a language for building tiny Web applications. This book provides not only a detailed description of today's java platform, but all of the enhanced specifications mentioned above.

Uploaded by

anhcdt
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 684

Release Team[oR] 2001

[x] java
Developing Java Enterprise Applications
by Stephen Asbury and Scott R. Weiner ISBN: 0471327565

John Wiley & Sons © 1999, 780 pages


A tutorial and reference for serious developers interested in
learning how to build large applications using Java.

Table of Contents
Back Cover

Synopsis by Alan Zeichick

Once upon a time, Java was viewed as a language for building tiny Web
applications. No longer. With the rise of advanced standards such as JDBC,
Java can talk to nearly all relations or object databases; thanks to JNDI, Java
can access directory services; Sun's specification for Enterprise JavaBeans
allows developers to build distributed applications which can communicate
with each other; the Java Messaging Service provides for applications to
"publish" and "subscribe" to services, and so on. Java's time as a serious
programming platform has arrived, and this book provides not only a detailed
description of today's Java platform, but all of the enhanced specifications
mentioned above. Thanks to its clear explanations, this book is valuable for
developers or their managers considering the use of Java for enterprise-scale
applications. The inclusion of complete examples and source code makes it
even more valuable for current Java developers looking to understand all of
the capabilities unleashed by this new programming technology.

Table of Contents

Developing Java™ Enterprise Applications - 3


Introduction - 5
Chapter 1 - An Introduction to Java Enterprise Development - 10
Chapter 2 - What Is JDBC? - 15
Chapter 3 - Basic JDBC Programming - 20
Chapter 4 - What Is JNDI? - 69
Chapter 5 - Using JNDI - 75
Chapter 6 - What Are Servlets? - 125
Chapter 7 - Programming Servlets - 135
Chapter 8 - A Servlet-Based Search Engine - 197
Chapter 9 - What Is Server-Side Scripting? - 214
Chapter 10 - Creating JavaServer Pages - 226
Chapter 11 - A JavaServer Page Online Store - 259
Chapter 12 - Overview of Distributed Objects - 281
Chapter 13 - Introduction to Java RMI - 290
Chapter 14 - A Network File-Locking Server - 321

-2-
Chapter 15 - What Are Enterprise JavaBeans? - 338
Chapter 16 - Programming Enterprise JavaBeans - 346
Chapter 17 - Deploying Enterprise JavaBeans - 399
Chapter 18 - Enterprise JavaBean Business Rules Engine - 404
Chapter 19 - What Are Messaging and the Java Messaging Service? - 430
Chapter 20 - Programming with the Java Messaging Service - 435
Chapter 21 - A JMS-Based Alarm System - 489
Chapter 22 - Transactions, JTA, and JTS - 511
Chapter 23 - Using Transactions with Enterprise JavaBeans - 515
Chapter 24 - Architecture Review - 531
Chapter 25 - A Four-Tier Online Store - 539
Chapter 26 - MiniJMS: A Java Messaging Service Provider - 592
Appendix A - JDBC Information - 671
Appendix B - What’s on the CD-ROM? - 681

Back Cover
Stephen Asbury and Scott Weiner – two of Sun’s favorite Java programming
trainers – provide easy-to-digest instructions for the major Java Enterprise
APIs and their associated programming tools and products. They describe
proven techniques for combining these APIs to create powerful enterprise
applications and discuss the role middleware products play in the enterprise
development process. You’ll learn how to:

• Program with Java Enterprise APIs like RMI, Servlets, JDBC, JNDI,
JTS, and others
• Build N-Tier transactions applications with Enterprise JavaBeans
• Create messaging applications with Java Messaging Service (JMS)
• Build a servlet search engine for your Web site
• Create an online store with JavaServer Pages (JSP)

About the Authors

Stephen Asbury and Scott R. Weiner are cofounders of Paradigm Research,


Inc., a training company that specializes in object-oriented programming with
Java, JavaScript, and other Web technologies.

Developing Java™ Enterprise Applications

Stephen Asbury
Scott R. Weiner

Publisher: Robert Ipsen


Editor: Theresa Hudson
Assistant Editor: Kathryn A. Malm
Managing Editor: Angela Murphy
Electronic Products, Associate Editor: Mike Sosa
Text Design & Composition: Benchmark Productions, Inc.

-3-
Designations used by companies to distinguish their products are often claimed as
trademarks. In all instances where John Wiley & Sons, Inc., is aware of a claim, the
product names appear in initial capital or all capital letters. Readers, however, should
contact the appropriate companies for more complete information regarding trademarks
and registration.

Copyright © 1999 by Stephen Asbury and Scott R. Weiner. All rights reserved.
Published by John Wiley & Sons, Inc.
Published simultaneously in Canada.

No part of this publication may be reproduced, stored in a retrieval system or transmitted


in any form or by any means, electronic, mechanical, photocopying, recording, scanning
or otherwise, except as permitted under Sections 107 or 108 of the 1976 United States
Copyright Act, without either the prior written permission of the Publisher, or authorization
through payment of the appropriate per-copy fee to the Copyright Clearance Center, 222
Rosewood Drive, Danvers, MA 01923, (978) 750-8400, fax (978) 750-4744. Requests to
the Publisher for permission should be addressed to the Permissions Department, John
Wiley & Sons, Inc., 605 Third Avenue, New York, NY 10158-0012, (212) 850-6011, fax
(212) 850-6008, E-Mail: PERMREQ @ WILEY.COM.

This publication is designed to provide accurate and authoritative information in regard to


the subject matter covered. It is sold with the understanding that the publisher is not
engaged in professional services. If professional advice or other expert assistance is
required, the services of a competent professional person should be sought.

Dedication
For my friends and the lessons they taught that were sometimes hard to learn.

—Stephen Asbury

For my wife Susan, who is special in too many ways to list here. For my daughter Emily,
who comes to kiss me goodnight right about the time I start to work on the book each
night. They both make me smile and I just wanted them to know I appreciate their
patience, love and support.

—Scott Weiner

Acknowledgments
First, we would both like to thank the PRI gang; Alberto, Brad, Eric, Ethan, Glen, John,
Karen, Kerry, Nicole, Shrinand, and Tyler. Sometimes writing takes away from work and
we appreciate their dedication in our stead. Special thanks to Ethan for his LDAP
expertise and Alberto for listening to Stephen complain about another late night, a fouled
up example, or basically anything worth commiserating about.

And where would authors be without editors? Many thanks to Terri Hudson, Kathryn
Malm, and Gerrie Cho for transforming our sometimes confused expositions into logical
explanations.

Finally, unending thanks to our wives Cheryl and Susan. You can only apologize for
coming to bed at 3 a.m. and working through the weekend so many times before you
realize that the person you keep apologizing to knew long before you did what your
decision to write a book entailed. I know from my experience that Cheryl is always two or
more steps ahead of me, and although I kick myself for not figuring it out sooner, I
wouldn’t have it any other way. I bet if you asked Scott about Sue, he would tell you the
same thing.

-4-
— Stephen Asbury

About the Authors


Stephen Asbury is the Chief Technology Officer for Paradigm Research, Inc. This is
Stephen’s fourth book on Web technology. He has also authored numerous courses on
Java, JavaScript, Active Server pages, HTML and just about everything else that Web
programmers need to know. In addition, Stephen has designed and built two Web-based
training environments and a number of other commercial applications and frameworks.
Stephen lives in Sunnyvale, California with his beloved wife Cheryl.

Scott Weiner is president of Paradigm Research, Inc. He has been involved in


consulting with object-oriented design and programming for over ten years. Before
founding Paradigm Research, Scott ran a mentoring program for NeXT Computers where
he helped Fortune 1000 companies embrace object-oriented techniques for creating
mission-critical applications. Today, Scott is leading Paradigm Research on a mission to
create the best training for development teams leveraging leading-edge technologies and
corporate end-users using the latest productivity applications. Scott lives in Northern
California with his wife Susan, his beautiful one-year-old daughter, Emily, and their two
dogs, Casey and Jake.

Introduction
Overview
Java is steadily becoming the language of choice for new enterprise-wide development
projects. A number of factors have lead to this explosion in Java’s popularity as a
programming language and in particular as a language for large scale development
projects. If you are reading this book, we expect that you already have some familiarity
with Java as a programming language, and won’t bore you with the laundry list of
marketing buzz words that are often used to excite folks about Java. Instead, we have
focused this book on the enterprise technologies that Sun is standardizing for the Java
developers. These technologies, as standards, are motivating numerous companies to
move their new projects onto the Java platform.

Our goal for Developing Java Enterprise Applications is to provide developers and
evaluators alike an opportunity to learn about Java enterprise development at a
technology level and from the perspective of a real world project. There are a number of
emerging Java technologies and we have taken a snapshot of the available libraries,
frameworks, and tools. Certainly, the list of available technologies will grow, but this book
should serve as a solid foundation for your Java development.

Currently the main technologies being adopted, or about to be adopted, by enterprise


developers include JDBC for database connectivity, the Java Naming and Directory
Interface (JNDI) for accessing services, and RMI for Java remote method invocation and
distributed objects. On top of this foundation are Servlets, JavaServer Pages, Enterprise
Java Beans, Java Messaging Service, and Transaction Management. All of these
combine into a feature-rich tool kit for developing Java applications.

What about CORBA?


You may have noticed that in all this discussion, we have made no mention of one of the
more pervasive and potential influential technologies in enterprise development, CORBA.
In researching and writing this book, we realized that their just wasn’t room to do CORBA,
and the Java technologies for accessing it, justice. Seeing the number of books that just
discuss Java and CORBA, we decided that our time, and your money were better served
discussing the other technologies well and leaving CORBA for a separate manuscript. We

-5-
both realize that it would be short-sighted to expect Java developers to ignore CORBA, and
we do not intend that. Rather, we decided to leave it out for the opposite reason, and
ensure that your research into CORBA is complete and productive.

How This Book Is Organized


You will find three types of chapters in this book. First, each technology is introduced, the
underlying concepts are discussed, and comparable technologies are described. Second,
the programming techniques and technical concepts required to use a technology are
described using small examples. These chapters provide a framework for studying larger
examples. Each technology that this book focuses on has a targeted example provided
for it. This example solves a medium size problem and provides a larger context for
learning what the technology is for and how it is used. Finally, the book is concluded with
two large examples. These tie together numerous concepts from the book and are
intended to provide a stepping stone to your real world development projects.

At least that is the general idea. If we look at a specific example, Servlets, we see
Chapter 6: What Are Servlets? introduces the concept of a servlet as a Web server
extension. Chapter 7: Programming Servlets discusses the details of creating a servlet,
and provides a number of small examples. Chapter 8: A Servlet-Based Search Engine
demonstrates how servlets can be used to create a Web site search engine. Finally,
Chapter 25: A Four-Tier Online Store uses a number of servlets in the creation of a small
on-line store. And that’s just for servlets. RMI, JavaServer Pages, Enterprise Java Beans,
and Java Messaging Service all have the same, if not more, coverage. Also, JDBC and
JNDI have chapters to introduce them, and describe the basic techniques required to use
them. We decided that since these technologies show up in so many other places, it
wasn’t necessary to provide a single large example for them. One note about JBDC; it is
a fairly lengthy topic, and if you are planning extensive database access we suggest that
you use a book that focuses on JDBC. Like CORBA we didn’t think that we could meet
our main goals and do JDBC justice. But we thought that it was necessary to provide the
basics here as a foundation for the other examples.

Based on this underlying design template the book is organized into the following
chapters:

Chapter 1: An Introduction to Java Enterprise Development introduces the technologies


in this book and describes some of the fundamental concepts that these technologies are
built on.

Chapter 2: What Is JDBC? introduces the Java Database Connectivity framework (JDBC)
and describes the goals and basic architecture for this important library.

Chapter 3: Basic JDBC Programming discusses the basic techniques used to access
databases with JDBC. Because JDBC is a large library we have not tried to provide a
complete discussion of it. Instead, we introduce the basic techniques used to access
data. These techniques are used in numerous examples throughout the book.

Chapter 4: What Is JNDI? introduces what the Java Naming and Directory Interface is
and the design goals behind it. The discussion includes information on naming and
directory services in general as well as how JNDI accesses them.

Chapter 5: Using JNDI describes and provides techniques for developing Java programs
that access naming and directory services. These techniques are used later in the book
for accessing Enterprise JavaBeans and Java Messaging Service facilities.

Chapter 6: What Are Servlets? introduces servlets and compares them with other Web
server programming tools.

Chapter 7: Programming Servlets describes the techniques used to write servlets and the
libraries that support servlets. This chapter also provides a number of example servlets

-6-
for retrieving data from the user, sending data to the user and communicating between
applets and servlets.

Chapter 8: A Servlet-Based Search Engine is the first focused example in the book. This
chapter describes a servlet based Web site search engine, including the code for
indexing HTML pages.

Chapter 9: What is Server-side Scripting? introduces the concept of server-side scripting


for Web pages and compares the different techniques to JavaServer pages.

Chapter 10: Creating JavaServer Pages demonstrates the techniques and syntax used to
create JavaServer Pages. Examples are used to highlight each concept.

Chapter 11: A JavaServer Page Online Store discusses a larger JavaServer Page
example that defines an online store. This store dynamically provides an interface for a
categorized group of products. Advertisements are displayed on pages, and new
products can be added without changing the code.

Chapter 12: Overview of Distributed Objects introduces the concept of distributed objects
and describes how Java RMI fits in to the distributed object realm.

Chapter 13: Introduction to Java RMI describes the techniques and tools used to create
distributed Java applications using RMI. Examples are used to demonstrate important
points, like class loading, object passing and distributed garbage collection. A small
discussion is included, with an example, on the RMI Object Activation Framework
provided with Java 2.

Chapter 14: A Network File Locking Server describes a larger RMI example that
demonstrates how a server can be used to implement file locking on a single machine or
the network.

Chapter 15: What are Enterprise JavaBeans introduces the powerful new technology
called Enterprise JavaBeans. In this first discussion of EJB the various roles involved in
creating Enterprise JavaBeans are listed and EJB is motivated as a development
technology.

Chapter 16: Programming Enterprise JavaBeans discusses the techniques and libraries
that programmers use to create Enterprise JavaBeans. Examples are used to highlight
important concepts like Entity beans versus Session beans. Enterprise JavaBeans
defines an important standard for developing server side code.

Chapter 17: Deploying Enterprise JavaBeans lists the steps used to deploy an EJB
application on an application, or some other server. This chapter is provided as an
introduction to the basic issues when deploying EJB applications.

Chapter 18: Enterprise JavaBean Business Rules Engine describes an example


Enterprise JavaBean application that uses a configurable rules engine to process check
requests. This example demonstrates both the EJB technology and how scripting can be
used to create dynamic business rules.

Chapter 19: What is Messaging and the Java Messaging Service? introduces the concept
of messaging and how the Java Messaging Service supports this programming
paradigm. Messaging is an alternative method for defining network communication and
has begun to gain popularity for large, complex enterprise projects, especially for
integrated disparate systems. This chapter also defines the basic messaging styles,
including point-to-point and publish-subscribe.

Chapter 20: Programming With the Java Messaging Service demonstrates the
techniques used to create applications that use JMS to send point-to-point and publish-
subscribe style messages. Examples are provided for major concepts, including

-7-
applications that send and receive messages using both styles.

Chapter 21: A JMS-Based Alarm System defines and describes a larger JMS application.
This application uses messages to create an alarm system. Users can define alarms and
have them triggered automatically at the appropriate time. This chapter also includes an
interesting library for managing timed notifications.

Chapter 22: Transactions, JTA, and JTS introduces the concept of a transaction and
discusses the Java libraries being defined to manage them.

Chapter 23: Using Transactions with Enterprise JavaBeans relates transactions to


Enterprise JavaBeans and provides examples of how an enterprise bean can manage its
transactional context.

Chapter 24: Architecture Review ties many of the concepts in this book together and
introduces the two large examples that conclude the book.

Chapter 25: A Four-Tier Online Store discusses an online store implementation. This
store uses JavaServer pages and servlets to define a Web page interface. Servlets are
used to manage searching and advertisement placements. A servlet is also defined to
manage a shopping cart for the user. This servlet works with an Enterprise JavaBean to
access data in a database about inventory and credit. Another servlet is provided for
creating inventory reports.

Chapter 26: MiniJMS discusses an implementation of the JMS libraries. This JMS
provider uses RMI, JDBC and JNDI to implement network messaging.

As you can see, the basic flow for each technology is an introduction followed by details
and an example. Finally, two large examples are used to tie the concepts together. We
strove for educational clarity and usefulness in our examples, so please do not consider
them the end all be all of programming. We have tried to show the tradeoffs we made, but
there were tradeoffs. Please keep this in mind as you study the example code.

Who Should Read This Book


This book is designed for two types of readers. First, technical evaluators should be able
to use the introductory chapters to learn about each technology at a high level. Then the
example chapters can provide demonstrations for what the technology can do.
Experienced Java programmers can use the programming chapters to learn the new
libraries and classes before diving into the example code to see how these programs
really work and get ideas for their own projects. Both types of readers should be able to
leverage the rules and guidelines throughout the book in future projects.

This book covers a lot of topics. You may want to read it straight through or jump around.
In either case, keep in mind the design template discussed above so that you can jump to
a technology without jumping into the middle of it, unless you want to. If you are planning
to read the code for the examples, you should already have experience writing Java
programs, including basic threads programming. If you have also used the networking
libraries, that will help with some of the distributed technologies, although it is not a
requirement.

Necessary Tools and CD-ROM Content


One problem we encountered when writing this book is that enterprise programming
needs a lot of deployment support. In particular, JDBC needs a database to access, JNDI
needs special service providers, Servlets and JavaServer Pages need a Web server,
EJBs need a host and JMS requires a service provider as well. For JMS we have written
a sample provider that is discussed in Chapter 26: MiniJMS. For JDBC we have often
discussed ODBC to meet the most commonly available database engines. But we have
also provided a demonstration version of Cloudscape’s JDMS a pure Java database

-8-
engine on the CD-ROM. For EJB examples we have included a demonstration version of
the BEA WebLogic application server on the CD-ROM. This server also provides some
JNDI services, as do the sample providers available from Sun. For servlets, we have
included the Servlet Development Kit. Unfortunately, you will need to download a server,
possibly from Sun, that supports JavaServer pages to test the examples.

That’s quite a list. Each chapter that introduces a new technology will provide specific
details for the programs and tools you need to run the examples from that chapter.

All of the examples rely on the standard Java Development Kit, we chose 1.1.6 and 1.2.
Many use the Swing user interface library as well. Java 2 is provided on the CD-ROM,
while both the 1.1 JDK and Swing can be downloaded from Sun’s Web site.

Although we did our testing on Windows NT, Windows95, and Windows98 all of the
examples are pure Java and should be easily run on other platforms. Please refer to the
Web site for this book at http://www.crl.com./~sasbury/ejava/ for the latest information on
these examples. You can also use that site to let us know of any problems you have with a
particular version or operating system so that we can let others know about those issues.

Note on Versions
Java is a growing, changing and expanding technology. You will always hear about the
next version just when you learn the newest one. As authors this can be especially
challenging for us. We have tried to provide the best available information in this book.
However, we know of some rumors and releases already and for completeness have
listed them here.

At the time we are writing, Enterprise JavaBeans was at version 1.0. Sun has already
announced that there will be a “maintenance” release, probably called 1.1, that may
appear before this book is published. This release is designed to fix bugs and resolve
issues with the JDK security model. Sun is also working with its partners on EJB 2.0.
Little information is available on this release at the time of this writing, but indications are
that it will build on the 1.0 release, so what you learn here will provide a firm foundation
for future versions. Sun has also indicated that they will probably provide a “reference
implementation” for an EJB host. This will allow you to test your EJB projects in a product
independent environment.

The Java transaction APIs were not finalized at the time of this writing and may change
somewhat in early 1999. Please use the latest documentation on your EJB host and
Sun’s Web site for information on transaction management. Keep in mind that changes to
JTA may effect the EJB specification as well.

The Servlet specification is being refined and updated. Like EJB, the content of this book
should provide a great foundation for your work with future versions of servlet
programming. JavaServer pages are also being updated and the new specification is
discussed in some detail in chapter 10.

Things will always be changing, but for enterprise projects you want a stable development
platform. We strongly suggest that for at least the first half of 1999 that you stick to JDK
1.1.7 and possibly 1.2 for deployment. Also stay with the earlier versions of the enterprise
APIs until the new versions are fully supported by your server vendors. This means EJB
1.0, JDBC 1.0 and the other versions discussed here. Although specifications are being
updated it will often take time for the service providers to update their code. A great
example of this is JDBC where 2.0 is nearly out but the drivers are not yet supporting it. As
a result we have discussed 2.0 features but have not included examples of JDBC 2.0 here.

Summary
Our goal in writing this book was to provide a solid, example rich, introduction to the
Enterprise Java Technologies. We can’t hope to discuss every issue in enterprise

-9-
development, despite how hard we tried. We can hope that you will use this book to start
your journey into Java enterprise development. Please let us know how you do and what
you would like to see in future books so that we can continue to provide a solid
programming introduction to new technology.

Chapter 1: An Introduction to Java Enterprise


Development
Overview
Developing enterprise applications requires an understanding of many technologies. In
addition to understanding Java, it is important to understand the technologies that
support networking, the Web, and database connectivity. Often, the enterprise developer
is faced with the task of applying very specific knowledge about a technology to the
problem of connecting it with another possibly unknown application.

This chapter introduces some of the concepts that support enterprise Java development.
Once these concepts are defined, the main enterprise Java technologies are introduced.
The remainder of this book discusses these technologies in detail.

Supporting Concepts
A number of concepts support enterprise Java technologies. These concepts include
HTTP, threads, and the Java language itself. Let’s take a look at these basic concepts. If
you are familiar with these concepts, feel free to jump ahead to the section entitled
“Enterprise Technologies.”

HTTP
Many of the applications being written today are being deployed on the World Wide Web
or in a miniature version of the Web inside a corporate network. Most likely you are
familiar with the Web, but you may not have had to deal with the protocols that drive it.

The primary protocol that drives the Web is called the HyperText Transfer Protocol
(HTTP). Although the name implies that this protocol is used only to transfer text, it can
actually be used for any type of file. HTTP creates a relationship between a resource
provider (the server) and a resource requester (the client). All HTTP interactions occur in
the form of requests. The client sends a request, and the server replies. Between
requests, there is no connection between the client and the server, making HTTP a
stateless protocol.

Each HTTP request and reply includes a header and a body. The header contains
configuration information such as the resource being requested. The body for a request
can contain information relevant to the request; the body of the reply contains the
requested information. A number of request types can be used, each indicated in the
header. The first, called GET, is used to request a resource. POST requests also ask for
a resource but are expected to include information, often from an HTML form, along with
the request. PUT requests are used to put data on the server; other, more esoteric
request types can be used to query the server and perform other operations.

As far as the content of this book is concerned, the main concepts that you need to
understand are that HTTP is stateless, there are several types of HTTP requests, and the
requests contain a header and a body.

Java
A number of Java concepts are particularly relevant to enterprise programming. First,

- 10 -
enterprise applications often require the use of multiple threads. Second, many of the
enterprise technologies require object serialization. Finally, enterprise applications must
be deployed to be used.

Perhaps the only one of these concepts that is appropriate to mention in this book is
deployment. As Java has matured, a number of important deployment-oriented
technologies have been released. First, the Java Run-time Environment (JRE) is a
version of the Java interpreter and classes intended for deployment rather than
development. The JRE does not include the development tools but does include the
classes required to run Java applications. JDK 1.1 uses a separate JRE; JDK 1.2 (now
called Java 2) integrates the JRE into the JDK, providing JRE add-ons for development
tools.

The second big step in deployment is the Java plug-in, which is available for JDK 1.1 and
Java 2. The Java plug-in is a browser extension that works with Microsoft Internet
Explorer and Netscape Navigator. Basically, the plug-in is like the JRE for a browser.
Users who install the plug-in can run Java applets using the associated version of Java.
This is a great leap forward because the browser vendors were hard pressed to keep
their Java versions in sync with the versions being released by Sun. Using the Java plug-
in, you can release to your customers applets that use the latest technologies, without
waiting for customers’ browsers to be updated.

Specialized Servers
Enterprise development adds a new layer of servers to the basic Web application.
Whereas the Web has Web servers, enterprise applications have databases, application
servers, and transaction monitors. You may already be familiar with databases.
Application servers are programs that provide server-type services such as resource
management to extensions written by enterprise developers. The goal of application
servers is to manage the “hard parts” of client/server programming. Transaction monitors
are a special type of server that manages distributed transactions. These transactions
are discussed more in Chapter 23, “Using Transactions with Enterprise JavaBeans.”

Basic Enterprise Design


Over the past couple years, a number of buzzwords have been coined to describe the
basic designs for enterprise applications. In particular, the terms three-tier and n-tier are
used to describe enterprise applications that reside on the client and several servers.
This book describes and discusses a number of variations on the tiered application
theme. Specifically, two-tier applications, such as the designs pictured in Figure 1.1, are
created. These might connect a client to a Web server or database.

Figure 1.1: Two-tier applications.

Extending a two-tier application to three tiers allows the programmer to leverage other
resources. In particular, the server can use a database for storing information or an
application server to process information. Several three-tier application designs are

- 11 -
pictured in Figure 1.2.

Figure 1.2: Three-tier applications.

Certainly, four- and five-tier applications are possible. Often these applications are
grouped under the heading of n-tier applications because they combine numerous
computers into a potentially deep and complex relationship. Figure 1.3 demonstrates a
couple of n-tier application designs.

Figure 1.3: N-tier applications.

Although these examples provide only the basic design for an enterprise application, they
represent an important step in enterprise development. Before you can create an
enterprise application, it is important to decide what technologies will be used and to
determine the computers that will run them.

Security
Enterprise applications often require some form of security management. The Java
security model is undergoing some major changes from JDK 1.1 to Java 2. The new
model is beyond the scope of this book, but it does define the standard concepts that we
will rely on. First, security often uses the concepts of a username and password to
identify people. The Java enterprise technologies use these concepts as well. Users can
sometimes be organized into groups or roles. A role might represent the idea of a
manager. All managers, regardless of their usernames, might have the same
permissions. For example, perhaps all managers could submit a hiring request, while
employees cannot.

Java also relies on the concept of permissions. Permissions indicate what operations a
user or role can perform. So, in our example, employees do not have permission to make
hiring requests, while managers do. The configuration for these permissions is organized
into an Access Control List (ACL). In our examples, we will sometimes use ACLs to
control who can use a particular resource, such as an Enterprise JavaBean.

The remaining security concepts such as encryption and digital signatures are not used in
this book, although they are supported in JDK 1.1 and Java 2. Please refer to the Sun Web
site for more information on the latest security model.

- 12 -
Enterprise Technologies
This book discusses a number of Java technologies:

JDBC. The Java Database Connectivity (JBDC) libraries are used to connect a Java
program to a data source. The initial version of JDBC was targeted at relational
databases, but JDBC 2.0 extends this model to other types of data sources.

JNDI. The Java Naming and Directory (JNDI) interface, like JDBC, is really an enabling
technology more than a library for use all by itself. Many of the technologies discussed in
this book rely on JNDI to provide access to objects such as enterprise beans and JMS
destinations.

Servlets. Servlets extend Web servers. In an enterprise environment, servlets can be


used to solve a number of problems. First, servlets replace CGI in providing server-side
processing for HTTP requests. Second, servlets can extend dynamic Web pages using
server-side includes or JavaServer Pages. Third, servlets can be used to form a gateway
between Web clients and other services such as databases, Enterprise JavaBeans, and
JMS. Finally, the servlet interfaces can be used as a generic interface for services. For
example, programmers might use the service method as an interface for generic RMI
objects or even enterprise beans.

JavaServer Pages. JavaServer Pages (JSPs) are used to create dynamic Web pages
using Java as a scripting language. JSPs are primarily used in situations in which a Web
page changes with each request.

RMI and distributed objects. Like JDBC, Remote Method Invocation (RMI) is an enabling
technology. Many applications and libraries use RMI to form their network connections.
Other applications may rely on raw sockets or HTTP connections to interact with servlets
and the Web Server. The goal of RMI is to provide a transparent link between two Java
applications.

Enterprise JavaBeans. Enterprise JavaBeans (EJBs) are designed to be the standard


building blocks for corporate server applications. Whenever your application is
implemented on a server, definitely consider using an EJB host server, such as BEA
WebLogic Application Server, and Enterprise JavaBeans. This combination allows you to
rely on heavily tested services from the server provider and focus your resources on
application-specific programming. The component design of Enterprise JavaBeans
makes them a great way to encapsulate business rules and processes. They can also
form the foundation of a library that provides standard services that your enterprise
applications require, such as data processing or report generation.

Java Messaging Service. The Java Messaging Service (JMS) is designed to act as a front
end to messaging providers. Messaging as a technology is a great way to objectify the
communication between applications.

Creating an enterprise application involves combining these technologies to create a


complete solution to your business problems.

Creating an Enterprise Application with Java


Let’s get something clear. This isn’t a book on designing applications. There are
hundreds of books on that topic, and we couldn’t do the topic justice in this small space.
However, it is worthwhile to look at the basic steps used to create an application and see
how they apply to Java Enterprise development.

The first step for any project is to define the project and its goal. For an enterprise project,
this may involve defining functionality across multiple computers or domains. Don’t tie
yourself to a particular platform at this point. Instead, focus on what the entire application

- 13 -
needs to do, then separate the functionality in later steps.

Once you have a list of requirements, analyze them and design a set of components and
objects that fulfill the requirements. At this point, start thinking about application
boundaries, but don’t lock yourself into saying “This is an EJB” or “This is a JMS
message.” The main goal is to determine a clear set of client applications and the
components that support them. For this book, the design of client applications with a user
interface is left to the reader. Our focus is on the libraries that the client might use to
communicate with the supporting components. The one exception is the use of Web
pages as an interface to an application on the server. The book discusses these; you
should use good Web page and Web site design techniques to augment the discussion.
Keep in mind that some “client” applications may not have a user interface at all. They
may be report generators or the like.

Take the components that your programs need, and determine what communication
mechanism they should use to interact. Possible choices are HTTP, RMI, CORBA,
sockets, and JMS. HTTP is normally used for applications that use a Web page front end,
but it can also be useful in situations in which the components are separated by a
firewall. With the availability of RMI and CORBA, sockets should be used only when
required to support existing protocols or applications. RMI and CORBA both use the
concept of distributed objects, discussed in Chapter 12, “Overview of Distributed
Objects,” to enable interapplication communication. For applications being written in pure
Java, RMI provides the most native distributed object choice. Applications that need to
connect to programs written in other languages should probably choose CORBA. As
mentioned in the preface, this book doesn’t cover CORBA, but the discussions on RMI
are relevant. Finally, JMS allows programs to communicate via encapsulated messages.
Messages can be sent between Java programs and even to non-Java programs,
depending on the provider. More discussion on reasons you might choose JMS is
provided in Chapter 19, “What Are Messaging and the Java Messaging Service?”

Given the components and the protocols between them, the next step in the development
process is to determine what type of component to implement. JavaServer Pages and
servlets can be used to drive a Web page user interface. Servlets can also provide
generic services over HTTP or some other protocol. Enterprise JavaBeans are used to
define components that live on a server of some type, such as an application server.
Using EJBs, you can leverage services provided by the server, such as transaction
management and resource optimization. RMI and JMS can be used with EJBs or servlets
as well as in applets or applications. For example, you can create an application that
provides services by receiving JMS messages and sending JMS messages for the reply.

The final step in this process is to write the client applications and components and, of
course, test them. The goal for this book is to not only help you write the applications and
components but to provide some ideas and tips for making the decisions we’ve
discussed. In particular, the last three chapters in this book start with a review of the
technologies and describe situations in which they should be used. Then two large
examples are described in detailed, real-world applications.

Why Use Java for Enterprise Development?


Of course, you may still be asking the question, “Why should I use Java for my enterprise
development?” It is certainly a valid question, and we would be hard pressed to argue
that Java is perfect for every single programming situation. However, there are a number
of great reasons to use Java for enterprise development and development in general.
Let’s look at the specific advantages of the Java enterprise technologies.

First, despite some hiccups, Java is extremely portable. This doesn’t mean solely that the
programs can move between computers; it means that components such as Enterprise
JavaBeans can be moved between different application servers. Using EJB, you can
develop an application on one server and deploy it on another one. This can save cost,
because developers don’t all need a deployment environment available to them. Of
course, you should always test on the deployment environment before actually deploying

- 14 -
your enterprise applications. Sun has made “Write Once, Run Everywhere” a mantra and
is constantly improving the availability of Java and the enterprise Java technologies. With
companies such as IBM, BEA, Oracle, and Sybase on board as well as a host of
innovative startups such as Cloudscape, it isn’t hard to see how Java will continue to
grow and improve.

Second, the standards defined by the enterprise Java technologies reduce education
cost. Once you learn the specifications described in this book, you can apply them on
multiple servers. Compare this to the old way of doing things, when programmers
specialized in a particular database or transaction monitor.

Technologies such as Enterprise JavaBeans and servlets split an application into


components and centralize code. With RMI, JNDI, and JMS, it is easy to manage the
relationship between components. In other words, you can leverage technologies such as
application servers to help break your code into manageable components, both during
development and once they are running.

These technologies leverage the advantages of Java. For example, servlets allow you to
plug custom code into a Web server, as a plug-in or extension library would. However,
servlets have garbage-collection and exception-handling abilities, so a problem in a
servlet should not affect the server as a whole. The same is true for Enterprise
JavaBeans that may be deployed on a database or application server. In both cases,
Java is creating a safety zone around each enterprise application to protect it from
naughty neighbors.

And the list goes on. We could spend several pages talking about why you should use
these technologies. This list is a great start, though, and once you see what the
technologies are and how they work, you will be able to fill in the list for yourself. So let’s
get started.

Summary
As you work through the concepts and examples in this book, keep in mind the goals you
have for your Java applications. Consider where each technology can be used, and think
about which one is the right technology to use. Hopefully, as you reach the two final large
examples at the end of the book, you will begin to think of new ways to combine the
enterprise Java technologies. Ultimately, your success creating enterprise applications is
limited only by your ability to choose the right technology for the job and apply it
appropriately.

The next two chapters introduce JDBC. This introduction is provided for programmers who
have no JDBC experience and is not intended as a complete discussion of the Java
database access libraries. If you are already familiar with JDBC, you may want to skip
ahead to Chapter 4, “What Is JNDI?”

Chapter 2: What Is JDBC?


Overview
Java Database Connectivity (JDBC) is an application programming interface (API) that
describes a standard Java library for accessing data sources, primarily relational
databases, that use the Structured Query Language (SQL). JDBC is JavaSoft’s answer
to the corporate developer’s requirement for database access from an enterprise Java
application. Not only does JDBC provide a standard API for accessing data sources such
as relational databases, but it also provides a standard architecture for database vendors
so they can provide data source drivers. These drivers allow access to the vendor’s
products directly from your Java applications.

Note In May 1998, Sun released the JDBC 2.0 specification. The discussions in this

- 15 -
book mostly refer to the JDBC 1.0 specification because few drivers support
2.0 at this time. Where appropriate, JDBC 2.0 features and enhancements are
identified.

This chapter describes the goals and architecture of JDBC. It assumes you are familiar
with relational database theory and SQL. If you are not familiar with SQL, you may want
to read one of the many available SQL books such as A Visual Introduction to SQL by J.
Harvey Trimble and David Chappell.

Although SQL is an ANSI standard language used to manage relational databases, each
database vendor implements its own slightly unique version of SQL to take advantage of
database-specific features. In order for application developers to access various
databases without having to learn an entirely new database access library each time,
standard specifications for database access drivers (sometimes referred to as bridges)
have emerged. These standards allow developers to build applications that can access,
view, and modify data from multiple, diverse databases as well as easily port new and
existing applications to new data sources. The bridge acts as a translator that takes
generic database access calls and translates them to database-specific calls. The most
common driver specification is ODBC, or Open Database Connectivity. ODBC is a C-
based standard developed by the SQL Access Group (SAG) standards committee and
popularized by Microsoft. ODBC is based on the Call Level Interface (CLI) specification of
SAG.

Note In 1995, SAG joined with the X/Open Data Management Technical Committee
to form the X/Open SQL Access Group. This group is composed of
representatives from AT&T, Inprise, Computer Associates, IBM, Informix,
INTERSOLV, Microsoft Corporation, Oracle Corporation, Progress, Sybase,
Visigenic Software, and other vendors.

ODBC allows users to access data in a relational database management system. ODBC
provides a consistent interface for communicating with a database. However, the
standard suffers from several drawbacks. Developers have complained that ODBC is
slow and limited in functionality. Another problem is that ODBC drivers are platform
specific. This means if you write your application to run on multiple platforms, you will
need a separate driver for each platform, which makes your application less portable.

Note You can find more information on ODBC at www.microsoft.com/data/odbc/.

Like ODBC, JDBC uses drivers to create a bridge to a specific database. Because JDBC
drivers are still being developed for many databases, you can use the JDBC/ODBC
bridge from JavaSoft to act as your database driver until a native driver is available.

Note For a list of available JDBC drivers and vendors supplying them, check
http://java.sun.com/products/jdbc/jdbc.drivers.html.

ODBC is more mature than JDBC, so there are drivers for accessing most databases
through ODBC. The JDBC/ODBC bridge allows you to use a native Java interface to
access your database through ODBC without having to learn the ODBC specification. This
extra overhead does create some performance issues, and ODBC is not as rich a
specification as JDBC; therefore you can’t take full advantage of all of JDBC’s capabilities
with this driver. However, this technique will allow you to get up and running while the
vendors continue to develop and deliver database-specific JDBC drivers. For most of the
JDBC chapters in this book, we use the JDBC/ODBC bridge in our examples. It is free from
JavaSoft and will work with most databases you may have access to, provided you have
the appropriate ODBC driver.

Goals for JDBC


JDBC was modeled after the ODBC architecture. The JDBC developers had two high-
level goals for its design: adhering to common database standards and keeping the API

- 16 -
simple.

Support Common Database Standards


Java programmers use JDBC for most database access calls. The JDBC API is designed
to allow application programmers to communicate with any data source that has a JDBC
driver implementation. In order to talk to various data sources, the API must remain fairly
generic. However, the goal is to replace the functionality of ODBC in C-based
applications and specifically provide interaction with SQL databases. This goal is met in
three ways:

JDBC provides the ability to pass raw SQL statements directly to the data source.
By providing a facility for passing SQL to the database, JDBC allows the application
programmer to take advantage of database-specific features. However, this means that
the programmer has to be responsible for sending appropriate SQL commands. The
drivers provide metadata that can indicate what commands are valid for a particular data
source. Metadata is information about or documentation of other data managed within an
application or environment. For example, meta-data in a relational database describes
the database tables, including the name of each attribute, the type of each attribute, and
the size of each attribute. JDBC provides methods to return this information from the
driver.

JDBC drivers must at least support the ANSI SQL92 Entry Level standard in order
to be considered compliant. Sun has trademarked the term “JDBC Compliant” in order
to assure consumers that a vendor’s JDBC driver handles a minimum level of SQL
support. Just because a driver does not have this stamp does not mean it is not a good
driver. It simply indicates that the vendor chose not to conform to the SQL92 standard. In
most cases, it is probably worth asking the vendor why it chose not to provide this
minimum level of standard SQL support, because the industry expects this minimal level
of interoperability.

JDBC architecture must support the functionality of common database bridges


such as ODBC. JDBC developers realized it would take time for database vendors to
provide native JDBC drivers for all the various data sources developers needed. They
ensured that the JDBC API would provide a superset of the ODBC capabilities. JDBC
improves on the ODBC standard and is optimized to leverage the strengths of the Java
language. In general, Java programmers use JDBC for all database access calls. This
way, creating one driver for ODBC, the JDBC/ODBC bridge, enables developers to
immediately take advantage of JDBC with virtually any data source.

Supporting common standards enables enterprise developers to take advantage of the


latest Java technologies while maintaining a high degree of interoperability with other
industry-standard products. JDBC solves most relational database access requirements
from Java.

Keep It Simple
Combining database access with an application-level API often results in compromises
because the natures of the languages (SQL versus Java, for example) are quite different
in their design and common use. If a developer has to master and maintain applications
that utilize various combinations of these languages, the whole process becomes quite
complex. Emphasis on keeping JDBC simple has resulted in an API that works well with
the rest of JavaSoft’s Java class libraries. Clearly, the Java philosophy as outlined in
Sun’s white paper, “The Java Language Environment: A White Paper,” written by James
Gosling and Henry McGilton, has been considered in the design decisions for JDBC. In
order to keep JDBC simple, the designers considered the following guidelines:

Keep the common cases simple by providing specific APIs for standard activities such as
selecting data using parameters. By providing methods for common cases, application
programmers can write and maintain less code.

- 17 -
Leverage the style and benefits of the Java core classes as much as possible. By
consciously keeping Java’s strengths in mind while designing JDBC, JavaSoft has
created an API that application programmers can easily integrate into their Java projects.
This goal is realized in the JDBC 2.0 specification, which is geared toward working with
the JavaBeans component model as well as other Java technologies, such as the Java
Transaction Services (JTS) discussed in Chapter 22, “Transactions, JTA, and JTS.”

Create methods that map to specific functionality rather than relying on a small API with
multiple meanings based on parameter values. Rather than trying to keep the API small,
it makes more sense to provide a rich API that addresses most of the database tasks
with specific method calls. This approach makes it easier for new JDBC programmers to
understand the use of each method. Another approach would have been to create a
small API with few methods, each taking several parameters that allowed the
programmer to configure the method. For example, an execute method could be used for
all types of SQL calls, but it would take a parameter to specify whether the SQL call
expected a return result. This would have made it easier for a programmer to learn all of
the available methods but harder to learn how to use each method. For this reason, a rich
API was chosen. This decision is evident in JDBC 2.0, which has a significantly larger
API but methods that are relatively simple to understand and use.

Use strong, static typing wherever possible to provide compile-time checking. As


part of the Java philosophy, static typing is used wherever possible so that more errors
can be caught at compile time rather than run time. The challenge with JDBC is that
because raw SQL can be passed to the database and then interpreted at run time, the
programmer must contend with SQL being, by nature, a dynamic language. This means
that data types are often determined as the result of a query at run time, so warnings and
exceptions must be carefully monitored.

One of the best features of JDBC is that it allows Java programmers to quickly develop
database access strategies for their applications. With a very small amount of code, a
developer can create a connection to a database, query the database, and update values.
JDBC even supports a transaction model so developers can make several database
modifications and, if necessary, undo all of them as a single transaction. The next section
looks at the architecture of JDBC that provides these services.

JDBC Architecture
The basic architecture of JDBC is quite simple. A class called DriverManager provides a
service for managing a set of JDBC drivers. The DriverManager class attempts to load
the driver classes referenced in the jdbc.drivers system property. You can load drivers
explicitly using Class.forName(). For instance, to load the JDBC-ODBC bridge driver, call
the following:

Connection con = null;


Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
con = DriverManager.getConnection("jdbc:odbc:myDataSource);

Class.forName() loads the driver class. On load, the driver should register itself with the
DriverManager. The call DriverManager.getConnection() looks for a registered driver that
can handle the data source described by the URL that uses the jdbc: protocol and returns
an object from the driver that implements the connection interface. A connection
represents a session with the data source and includes methods for executing database
operations. It provides information about the data source, called meta-data, which
includes information about the data source structure.

The protocol used to specify the data source is in the format


jdbc:subprotocol:datasourcename, where datasourcename is, in this case, the name of a
registered ODBC data source, such as a Microsoft Access database or an Oracle
database. The getConnection() method takes an optional username and password for

- 18 -
registration with data sources that require these.

Typical Scenarios
There are several typical scenarios for using JDBC. These scenarios differ based on the
location of the database, the driver, the application, and the communication protocols
used. The main scenarios are:

• Standalone applications

• Applets communicating with a Web server

• Application and applets communicating with a database through the JDBC/ODBC


gateway

• Applications accessing remote resources using mechanisms such as the Java Remote
Method Invocation (RMI), discussed in Chapter 13, “Introduction to Java RMI.”

These scenarios can be grouped into two- and three-tier architectures. In a two-tier
architecture, the application resides on the same machine as the database driver. The
driver can access the database running on a database server. The database driver is
responsible for handling the networked communication. Figure 2.1 illustrates a simple
two-tier JDBC architecture. In this example, the Java application running on the client
machine uses a JDBC driver residing locally. The local driver, in turn, uses a vendor-
specific client library for accessing the database remotely, over the network. The Java
application accesses this resource transparently, meaning that it never has to deal with
network communication issues.

Figure 2.1: A two-tier JDBC architecture.

Note The database may reside on the same machine as the application and still be
considered a two-tier architecture because the application resides in a
separate address space from the database. The driver must still handle the
interprocess communication.

The three-tier architecture involves an application or applet running on one platform and
accessing the database driver on another. The database driver can be accessed through
a variety of mechanisms:

• An applet may access the driver through a Web server

• An application may access a remote server program that communicates locally with a

- 19 -
database driver

• An application may communicate with an application server that accesses the


database for you

Figure 2.2 illustrates a three-tier scenario in which an applet on the client Web browser
accesses a server application that accesses a database behind the firewall.

Figure 2.2: A three-tier JDBC architecture.

By placing all the database access logic in the JDBC driver, the driver vendor handles the
issues of communicating with the database and database vendor’s client library. This
means you can write applications that function in a two-tier or three-tier environment with
few or no changes to your code. The JDBC design allows any Java programmer to access
a relational database with little or no extra skill.

Summary
JDBC is a standard mechanism and API for Java programs to access relational databases
and other data sources. JDBC programs can send queries and updates to a data source as
well as ask the data source about information regarding the data source itself or the data it
contains. In order to make using JDBC simple and efficient, access to specific databases is
managed by drivers that implement a common set of Java interfaces. This allows the
database connectivity provider, or driver programmer, to use whatever mechanisms
needed to create a high-performance database connection while making it easy for
programmers to change databases without changing a lot of code. Chapter 3, “Basic JDBC
Programming,” looks at the actual JDBC API and builds a simple database application.

Chapter 3: Basic JDBC Programming


Overview
This chapter covers the basic use of JDBC. The goal of this chapter is to enable you to
create database connections and access database values via JDBC. This chapter
describes the steps to connect to a data source and query it using the JDBC API. The
data source example used in this chapter is a Microsoft Access database called
INV.mdb, found on the CD-ROM that accompanies this book. This is a small inventory
database with four user tables. The Access database was chosen because it is common
and can be accessed through the JDBC-ODBC driver. This means you don’t need a
special driver for the database, because Access comes with an ODBC driver.

Note If you do not have Microsoft Access for the examples in this chapter, the CD-ROM

- 20 -
included with this book includes a build script called Inv.script that can be used
and modified to create this schema in most relational databases.

About the Sample Database


The sample INV database used in the next few examples is a simple inventory database.
The tables involved are described in Tables 3.1 through 3.4.Figure 3.1 shows the
relationships among the tables. The examples in this chapter perform various queries on
these tables, such as looking up all the vendors that sell beverages. The tables include
the following :

Figure 3.1: INV database schema.

Table 3.1: Categories Schema

Field Type Notes

CategoryID AutoNumber (integer) Primary key

CategoryName Text

Table 3.2: Inventory Schema

Field Type Notes

ProductID AutoNumber (integer) Primary key

ProductName Text

ProductDescription Text

CategoryID Number (integer) Foreign key

Price Currency

- 21 -
ReorderLevel Number (integer)

Discontinued Yes/No (Boolean)

LeadTime Text

Quantity Number

Table 3.3: Supplier Schema

Field Type Notes

SupplierID Text Primary key

SupplierName Text

ContactName Text

ContactTitle Text

Address Text

City Text

PostalCode Text

StateOrProvince Text

Country Text

PhoneNumber Text

FaxNumber Text

PaymentTerms Text

EmailAddress Text

Notes Memo

Table 3.4: ProductSupplier Schema

- 22 -
Field Type Notes

SupplierID Number (integer) Primary key

ProductID Number (integer) Foreign key

Price Currency

Categories. A list of inventory types, such as Beverage, Produce, or Seafood.

Inventory. A list of inventory items such as Chef Anton’s Gumbo Mix and associated
information such as unit price.

Supplier. A list of vendors that sell the product in the inventory list.

ProductSuppliers. A list of inventory items provided by each supplier.

Note For this chapter, it is important that you understand SQL and have access to a
relational database. If you do not have Microsoft Access, make sure you modify
the INV.script so that it creates the appropriate tables on your database. In any
event, you will need to set up an ODBC data source called Inventory that refers to
your database. Check the documentation that comes with your ODBC driver for
information on how to create an ODBC data source. This relationship among the
ODBC driver, JDBC, and the ODBC data source should become more clear as
you examine the code used to make a database connection. For now, keep in
mind that to run the examples, you need a database. If you can't use the one used
here, you will need to alter the code provided on the CD-ROM to make it work with
this database.

JDBC Basics
The process for accessing a database using JDBC is fairly straightforward. This section
describes the steps necessary to create a connection to a data source and then access
it.

The first step in any enterprise project is to determine the environment in which the
project will be deployed. For example, are you writing an applet, an application, servlets,
or enterprise JavaBeans? If JDBC is part of the project, you will have a number of JDBC-
specific issues to decide. The main configuration issues you need to determine will be:

Which database should you choose? You might need to consider database-specific
issues, such as how to map custom data types to Java.

Which driver should you choose? There may be more than one driver available for a
given data source. Knowing the benefits of each driver will help you determine which is
right for your application. For example, if you choose the ODBC driver, you have more
flexibility as to which data source you connect to; however, if you choose a driver
specifically designed for Oracle, it may be more efficient for accessing an Oracle
database.

Where will the driver be located? Depending on where you locate the driver, you may
have performance considerations. For example, if you load the driver from a remote

- 23 -
server, you’ll need to handle standard Java security issues.

Regardless of the configuration you choose, the steps for accessing and interacting with
the data source are almost identical. First, load a JDBC driver. A JDBC driver should
implement the java.sql.Driver interface. When a driver class is loaded, it should create an
instance of itself and register it with the DriverManager. This means that a user can load
and register a driver by calling Class.forName(“package.DriverName”). Table A.3 in
Appendix A contains a partial list of the drivers currently available. The list is maintained
on the Sun Web site at www.javasoft.com/products/jdbc/jdbc.drivers.html.

Note When loading a driver, you must consider certain security issues. For
example, if you have an applet in a Web browser that loads a driver over the
network, you need to make sure the applet is from a trusted source. For more
information on this subject, check out java.sun.com/products/jdk/1.2/docs/
guide/jdbc/spec/jdbc-spec.frame5.html.

Next, create a connection to the data source. Create a URL that describes your data
source and call the getConnection() method of the DriverManager. This method searches
all the available drivers, looking for one that understands the URL passed in. Once the
method finds one that can handle your data source, it returns a connection to that data
source, as shown in Figure 3.2.

Figure 3.2: Selecting a driver.

If necessary, query the Connection for meta-data about the database structure. The
meta-data is returned from the Connections’ method getMetaData in an object
implementing the interface DatabaseMetaData. This interface contains a large number of
methods. The DatabaseMetaData methods are described in detail in Appendix A, “JDBC
Information.” The metadata is useful for figuring out unique features of a data source,
such as a data source’s ability to use the GROUP BY clause or whether the database
supports transactions.

Once you have your connection, create a SQL statement from that connection. To create
a statement, call the Connections method createStatement(). This returns an object
implementing the Statement interface. Use this Statement object to execute the query
that uses the statement. The Statement object can be used to execute a SQL query by
calling one of the methods listed in Table 3.5.

Table 3.5: Execute Methods of the Statement Interface

Method Function

- 24 -
boolean Execute a SQL statement that may return multiple result
execute(String sql) sets. This usually occurs when you call a stored
procedure that returns non-rectangular data. The method
returns true if the next result from this statement will be a
result table.

ResultSet Execute a SQL statement that returns a single result set.


executeQuery A result set is an interface that represents a set of data
(String sql) returned from a query.

int executeUpdate Execute a SQL INSERT, UPDATE, or DELETE


(String sql) statement. It returns a the number of affected rows.

Note JDBC 2.0 adds the method int[ ] executeBatch(), which can be used to submit
a batch of commands to the database for execution. This method returns an
array of update counts for each statement executed.

Once a statement is executed, check for any problems with the query by calling
getWarnings(). This returns an instance of the SQLWarning class.

If no warnings occurred or if you can ignore the warnings, process the results from the
query. Call getResults() to access ResultSet. Use this object to access all the return
values.

Finally, close the database connection. Database connections are usually a limited
resource for a database, so close the connection when you are finished.

This is the complete process for accessing a data source with JDBC. The next section
looks at the source code for a simple example that demonstrates all of these steps. You
can compile and run the version on your CD-ROM; the example is located in the Chapter 3
directory.

A Simple JDBC Example


This small example shows the steps to connect, query, and print the results of a
database call. To keep the example simple, little error checking is done. A Microsoft
Access database called INV.mdb is used as the data source; it is available on the CD-
ROM. The URL for the data source in this example is jdbc:odbc:inventory.

Note You must have the JDBC driver that you are using in your class path. For
example, this example uses the sun.jdbc.odbc.JdbcOdbcDriver.

Our query retrieves all columns from the Inventory table (SELECT * FROM Inventory), so
we need to determine the number of columns by looking at the metadata of the result set.
Bold lines indicate the typical code used for creating a connection, querying the
database, and querying the result set metadata to determine how many rows were
returned from the query. Figure 3.3 shows the results.

- 25 -
Figure 3.3: Example results.

import java.sql.*;

public class Example


{
public static void main (String args[])
{
try
{
/*
Load the JDBC-ODBC bridge,
it will register itself with DriverManager.
*/
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (Exception e)
{
System.out.println("JDBC-ODBC driver failed to
load.");
return;
}

try
{
/*
Pass the url to the DriverManager.
It should find our driver and return a database
connection to an ODBC data source called "inventory."
*/

Connection con =

DriverManager.getConnection("jdbc:odbc:inventory","","");

// Create a statement and use it to execute a query.


Statement stmt = con.createStatement();
/*
This returns a result set.
We will assume it is not null for simplicity.
*/
ResultSet rs =
stmt.executeQuery("SELECT * FROM inventory" +
"ORDER BY price");

/*
In order to determine the type and amount of data
returned
by our query, we access the metadata of the result
set.
*/

ResultSetMetaData rsmd = rs.getMetaData();


/*

- 26 -
From the metadata, we determine how many columns were
actually returned.
*/
int numberOfColumns = rsmd.getColumnCount();
int rowCount = 1;

/*
We will loop through the result set, printing out
values for each row.
*/
while (rs.next())
{
for (int i = 1; i <= numberOfColumns; i++)
{
System.out.print(rs.getString(i)+" ");
}
System.out.println("");
rowCount++;
}

// We are done with the statement, so we close it.


stmt.close();

// We are done with our connection, so we close it.


con.close();
} catch (Exception e)
{
System.out.println(e);
}
}
}

You can experiment with the SELECT statement by specifying which columns of Inventory
you want to retrieve. The next section looks at the specifics of connecting to any data
source.

Connecting to a Data Source


The last section provided a simple example of a complete JDBC application. This section
provides a detailed look at the process for choosing a driver and connecting to a data
source.

Any item with a JDBC driver, such as a database management system (DBMS), a file
system, or a text file, can be a data source. To connect to a data source, first identify the
data source you intend to connect to. For example, is it Oracle? Sybase? Is it another
relational database or some other type of data source such as a file system? Next, obtain
a JDBC driver for your data source. Typically, the data source vendor will provide the
driver; however, you can get a complete list of the registered vendors from the Sun Web
site.

Determine if the data source will be local to the application or remotely accessed. If the
driver will be remotely accessed, make sure you consider the standard Java security
issues involved in loading Java classes over the network, such as loading classes in an
applet. There are no unusual security issues for local access. Just make sure the driver is
in your CLASSPATH.

- 27 -
Note JDBC tracks the class loader that provides each driver. When the
DriverManager class opens a connection, it uses only drivers from the local
file system or from the same class loader as the code requesting the
connection.

Load the appropriate driver in your code. One way to load drivers is to put the name of
the driver in the jdbc.drivers System property. This will be checked when the
DriverManager class is initialized. If you want to load several drivers, define a list of
drivers, separated by colons. Your property entry might look something like this:

jdbc.drivers= sun.jdbc.odbc.JdbcOdbcDriver:another.typeof.driver

Another option is to use the Class.forName() method to load the class explicitly. When a
driver is loaded, it is expected to call the DriverManager method registerDriver(). This
registers the driver so it can be used later. If you want to know what drivers are available,
call the DriverManager method getDrivers().

Once you have loaded the driver, call DriverManager.getConnection() to get a connection
to your data source. There are several versions of the getConnection() method; they are
listed in Table 3.6. All of these versions take a URL parameter. The URL is a JDBC
protocol that specifies the data source to load. The URL takes the form

jdbc:sub-protocol:datasource-name;optional-parameter=value

Table 3.6: The getConnection Methods

Method Use

public static Connection url is of the form


getConnection(String url, jdbc:subprotocol:datasourcename
Properties info)
throws SQLException info is a list of keys/values that are useful for
the driver; the property liist contains at least a
username and password field

public static Connection url is of the form


getConnection(String url, jdbc:subprotocol:datasourcename
String user, user is the name of the database user making
String password) this connection password is the database
throws SQLException password used to connect with this user name

public static Connection url is of the form


getConnection(String url) jdbc:subprotocol:datasourcename
throws SQLException

Table 3.7 lists the meaning of each URL component.

Table 3.7: JDBC Protocol Structure

- 28 -
Protocol Component Meaning

jdbc Specifies that we are using a JDBC protocol.

Sub-protocol Indicates the type of data source. For example, the URL
could be jdbc:oracle:myDatabase or jdbc:odbc:myDatabase.
When DriverManager is looking for a registered driver to
connect to your data source, it passes this URL. The driver
can then determine if it can handle this data source.

Datasource-name The name your driver or your DBMS uses to identify the data
source. For example, with ODBC, the data source name is
the name registered with ODBC and could differ from the
name of the actual database it maps to; with Sybase, the data
source name could be the name of the configuration data
used to connect to the database over the network.

Optional- Can be used to pass extra information that is specific to the


parameter driver. The protocol can take zero or more optional
parameters in the format of key/value pairs. For example, a
URL might look like this:
jdbc:odbc:Inventory;user=admin;password=xyz

Each of the getConnection() methods returns a Connection object representing a


physical connection to the specified URL.

Note A driver developer can reserve a name to be used as the subprotocol in a


JDBC URL. JavaSoft acts as an informal registry for JDBC subprotocol
names. To register a subprotocol name, JavaSoft recommends you send e-
mail to [email protected].

Use the Connection returned by DataManager to access the data source. A Connection
object represents a session with a data source. Any number of SQL statements can be
executed over this Connection, as shown in Figure 3.4. An application can have one or
more connections to a single data source, or it can have connections to several
databases.

Figure 3.4: Connecting to a data source.

You now have a connection object that can be used to access the database, assuming

- 29 -
that you specified the URL correctly and that you have an appropriate driver. There are
four types (levels) of drivers today; they are discussed in the following section.

Driver Types
JavaSoft has segmented JDBC drivers into four categories. These categories, referred to
as levels, range from platform-specific bridges (level 1) to pure Java database
connections (level 4). The four levels are:

• JDBC-ODBC bridge plus ODBC driver

• Native-API partly Java driver

• JDBC-Net pure Java driver

• Native-protocol pure Java driver

JavaSoft believes that over time, most drivers will be level 3 or 4. This means that most
drivers should be pure Java and run on most platforms. Let’s explore each of the levels in
more detail:

1. JDBC-ODBC bridge plus ODBC driver. The JavaSoft bridge product provides
JDBC access via ODBC drivers. This is a simple JDBC driver that supports the
ODBC capabilities and doesn’t necessarily support all of the capabilities of a
particular data source. It is common that ODBC binary code, and in many cases
database client code, must be loaded on each client machine that uses this driver. If
you use this type of driver, you typically are not concerned with installing extra
software on your client machines. This type of driver is also useful in a server
application that is part of a three-tier architecture. In this case, the ODBC driver
software and possibly the database client code are installed on the server. Clients
connect to the application running on the middle tier. The application uses the JDBC-
ODBC bridge to communicate with ODBC, which in turn communicates with the
database running on the third tier. Performance is the main issue in this case. The
middle-tier server is hit with client requests that must be transported through the
bridge. A pure Java solution should be more efficient.

2. Native-API partly-Java driver. Most vendors provide platform-specific libraries for


communicating with their databases. These libraries are usually platform-specific
compiled libraries that are typically implemented in C or a similar language. These
libraries can take advantage of database-specific features. However, they must be
installed on client machines. The JDBC driver at this level is written in Java, but it
makes calls out to the database client library. Although this solution is not completely
portable, at least the JDBC driver can take advantage of all the features provided by
the database client library. This level of driver is useful in the same scenarios as level
1 drivers, except that this level can access more database-specific features.

3. JDBC-Net pure Java driver. Middleware products that allow you to remotely access
enterprise resources, such as databases, are emerging. Components of one new
class of middleware are referred to as application servers. These products often have
their own network protocol. By sending specific instructions to the application server,
it can map these database-independent calls to database-specific requests. These
products enable you to centralize issues like security, load balancing, and so on,
outside your application. The JDBC drivers for these products map JDBC calls to the
middleware-specific calls. A demonstration copy of BEA WebLogic Application Server
is included on this book’s companion CD-ROM. Examples later in this book will show
you how to use the application server to access enterprise resources.

4. Native-protocol pure Java driver. Database vendors are starting to support network
protocols that allow you to directly communicate with the database over the network.
The JDBC drivers can be completely implemented in Java and do not need to rely on

- 30 -
a specific client library. Rather, they send commands directly over the network to the
database. This is a flexible model, but each vendor has its own protocol. To access
multiple databases using this technique, you may need multiple drivers, because
each vendor may have its own unique driver for leveraging vendor-specific features
such as transaction management.

Connection Example
Once you have chosen a JDBC driver, you need to establish a connection to your
database. The following example shows the typical process for connecting to a data
source using JDBC. The first bolded line loads the JDBC-ODBC bridge. When the driver
loads, it registers itself with DriverManager in order to be located when a driver-
manageable request to connect to a data source is issued.

After loading the driver class, pass the URL to DriverManager in the getConnection
method. DriverManager should find the driver and return a database connection to an
ODBC data source called Inventory.

When the connection is no longer needed, close it. Even though the connection would be
collected as garbage, it is important to free connections as soon as possible. Because
most databases allow a limited number of connections, the connections are a valuable
resource.

import java.sql.*;

public class ConnectionExample


{
public static void main (String args[])
{
try
{
/*
Load the class for the driver.
Make sure the driver is in your CLASSPATH.
*/
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (Exception e)
{
System.out.println("JDBC-ODBC driver failed to
load.");
return;
}

try
{
/*
Specify the data source you want to connect to by
passing in the URL. If a driver is found that can
connect to this data source, you will get a
Connection object.
*/
Connection con =

DriverManager.getConnection("jdbc:odbc:Inventory",
"","");
// When you are done, close the connection.

- 31 -
con.close();
} catch (Exception e)
{
System.out.println(e);
}
}
}

Connecting with Properties

In the previous example, the driver and data source were “hard-coded” in the code.
Although this is a reasonable technique, it misses one of the dynamic features of JDBC.
JDBC allows you to determine what data source and driver to use at run time. This could
be important for load balancing or for easy configuration and installation of your
application in various environments. In order to select a driver and data source
dynamically, use a property list loaded from a text file.

Follow these steps to load the text file:

1. Open the text file using an InputStream object.

2. Create a java.util.Properties object.

3. Use the Properties object load() method to enter the values from the InputStream into
the Properties object.

Note The name of the property file is arbitrary, as are the keys and values stored.
These are all programmer defined.

In this example, a connection is made to the Inventory data source; however, the driver
name and the data source information are stored in a text file called datasource.config
instead of hard coding the information in the class file. The text file has the following
values:

datasource.driver=sun.jdbc.odbc.JdbcOdbcDriver
datasource.protocol=jdbc
datasource.subprotocol=odbc
datasource.name=Inventory
datasource.username=
datasource.password=

The example also uses the ClassLoader method getSystemResourceAsStream to return


an InputStream to our datasource.config file. This method searches the entire ClassPath
for our file. In most of our examples, we assume “.” is in your ClassPath. If it isn’t, place
the datasource.config file in a CLASSPATH directory.

import java.util.Properties;
import java.io.InputStream;
import java.sql.*;

public class PropertyExample


{
public static void main (String args[])
{
String dsDriver="";

- 32 -
String dsProtocol="";
String dsSubprotocol="";
String dsName="";
String dsUsername="";
String dsPassword="";

try
{
// Loads the configuration file
InputStream is =
ClassLoader.getSystemResourceAsStream
("datasource.config");
if(is != null)
{
Properties p = new Properties();
p.load (is);
dsDriver = p.getProperty("datasource.driver");
dsProtocol =
p.getProperty("datasource.protocol");
dsSubprotocol =
p.getProperty("datasource.subprotocol");
dsName = p.getProperty("datasource.name");
dsUsername =
p.getProperty("datasource.username");
dsPassword =
p.getProperty("datasource.password");
}
} catch (Exception e)
{
System.out.println("Unable to read property file");
return;
}

try
{
Class.forName(dsDriver);
}
catch (Exception e)
{
System.out.println("Failed to load driver.");
return;
}

try
{
String theURL =
dsProtocol+":"+dsSubprotocol+":"+dsName;
/*
Create a connection using the values parsed from
the configuration file.
*/
Connection con = DriverManager.getConnection
(theURL,dsUsername,dsPassword);
if(con != null)

- 33 -
{
System.out.println("Successfully connected to " +
dsName);
con.close();
}
}
catch (Exception e)
{
System.out.println("Failed to connect: " + e);
return;
}
}
}

Now either open a connection by hard coding the URL components or load them from a
configuration file. If you load the values from the property file, you need to realize that
these values may be changed, even though the class file does not. This means you should
take more precautions in preparing your query by checking the metadata of the connection
to verify that the database is appropriate for your query. For example, suppose the name of
the database is changed in the property file and you try to query it. If the tables you expect
to have in the database aren’t there, your application will fail. Rather than waiting for the
application to fail, you could examine the data source first to see what tables are available.
We follow this procedure in the next section.

Examining a Data Source


Once a connection has been established with a data source, processing of SQL can
begin. However, it is often useful to query the data source for information about its
structure, capabilities, and limitations. This will enable you to optimize your query and
prevent access errors such as trying to retrieve data that doesn’t exist. Before examining
the code for this process, let’s view the metadata using a tool called JDBCTest.

JDBCTest is a Java application written by Intersolv that loads and interacts with any
JDBC driver. It is a useful tool for looking at your data source and testing your JDBC
driver’s capabilities before you actually use it. After examining the data source with
JDBCTest, this section walks through an example program that demonstrates how you
might do this in your own code.

JDBCTest
If you do not have JDBCTest installed, you may download and install it from
http://java.sun.com/products/jdbc. When JDBCTest starts, the screen in Figure 3.5
appears.

- 34 -
Figure 3.5: Startup screen for JDBCTest.

Press the Press Here To Continue button to load the main JDBCTest window. This
window, shown in Figure 3.6, has a menu bar that allows you to connect to a data
source, explore its metadata, and even execute SQL commands. The main window has
four parts: a menu bar, a list of data source connections, result output from the driver,
and a box that displays the Java code executed to perform your requests. This last box is
valuable in learning JDBC because you can execute a command and then cut and paste
the code into your own application. It is a quick technique for learning the library.

Figure 3.6: JDBCTest main screen.

Follow these steps to try out JDBCTest:

1. From the Driver menu, select Register Driver. A panel with a text field appears.

2. Type sun.jdbc.odbc.JdbcOdbcDriver and press Return. This registers the JDBC-


ODBC bridge driver. The Java code generated is displayed in the Java code text box.

3. Now that you have registered the driver, select Connect to DB from the Connection
menu. A panel displays fields for you to type the URL for the data source as well as
the login name and password. In the Database field, type: jdbc:odbc:InventoryUse the
default name and password, so leave those fields blank.

- 35 -
4. Click the Connect button. Assuming you have a registered ODBC data source called
Inventory, you now have a connection. If the connection was established
successfully, the JDBCTest main screen indicates there were no problems, and the
Connection panel for your new connection is displayed. This panel, shown in Figure
3.7, allows you to interact with your data source. It displays feedback from the
connection and the Java source code for the executed commands.

Figure 3.7: JDBCTest connection screen.

5. Now issue a SQL statement on our data source. In the Connection panel, select
Create Statement from the Connection menu. Before executing SQL commands, a
Statement object to hold your SQL must be created. Observe the Java code
generated in the Connection window by executing this command.

6. After creating a statement, fill it with SQL. To do this, select Execute Stmt Query from
the Statement menu in the Connection panel. The Execute Query Panel displays. In
this panel, type the following query: SELECT * FROM INVENTORY. Then click the
Submit button. In the Connection panel you should see that the statement returned a
result set that contains a list of return values.

7. From the Results menu, select Show All Results. Figure 3.8 shows how the
Connection panel will look after you do so.

Figure 3.8: JDBCTest displaying results.

8. Now that you have looked at values in the database, finish by looking at the attributes

- 36 -
of the database itself. To do this, select Get DB Meta Data from the Connection menu
of the Connection panel. This creates a result set containing information about the
database.

9. To view the metadata, select Show Meta Data from the MetaData menu in the
Connection panel. In the top text box are all of the attributes of the database that are
available through the JDBC driver. In the bottom text box is the Java code used to
print these values. These windows are shown in Figure 3.9. Scroll down the list of
values to get a feel for the types of information available to you. If you have any
questions about the meaning of a value, refer to Appendix A, which lists the
DBMetaData methods and their meanings.

Figure 3.9: JDBCTest displaying metadata.

As you can see, JDBCTest offers a great deal of information about the data source that
you can use to optimize and configure queries. Using this information inside your
application allows you to dynamically configure data access routines. For example,
knowing if the data source supports the GROUP BY clause can help you configure how
to structure your queries. Let’s take a look at an example for accessing and processing
database metadata in your application.

The Connection object provides you with this information through an object implementing
the DatabaseMetaData interface. Once you have a Connection object, query it for
information about the structure of the underlying database by calling getMetaData(). This
method returns an object implementing the DatabaseMetaData interface. (Appendix A
lists the information you can retrieve about the database from the DatabaseMetaData
object.)

This example creates a connection to the Inventory database and then prints a list of all
the tables and their columns by requesting the database metadata from the Connection
object, then requesting a list of available tables by calling the DBMetaData method
getTables(). This returns a result set containing information about the tables in the
database.

The method getTables takes the parameters listed in Table 3.8. This method returns a
result set that consists of values returned from the database query. This result set is
described later in this chapter. It contains the columns described in Table 3.9. All results
in this result set are strings.

Table 3.8: The getTables() Parameters

- 37 -
Parameter Use

String catalog “” retrieves those without a catalog; null means drop


catalog name from the selection criteria

String schemaPattern A schema name pattern; “” retrieves those without a


schema

String tableNamePattern A table name pattern

String[ ] types A list of table types to include; null returns all types

Table 3.9: The getTables() Result Set Values

Column Name Value

1. TABLE_CAT Table catalog (may be null)

2. TABLE_SCHEM Table schema (may be null)

3. TABLE_NAME Table name

4. TABLE_TYPE String Table type such as TABLE, VIEW, ALIAS

5. REMARKS Comments about the table

In the next example, the table names and the table types for all tables (columns 3 and 4)
are printed to the console. The results are shown in Figure 3.10.

Figure 3.10: Displaying metadata.

- 38 -
import java.sql.*;

public class MetaDataExample


{
public static void main (String args[])
{
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (Exception e)
{
System.out.println("JDBC-ODBC driver failed to
load.");
return;
}

try
{
Connection con = DriverManager.getConnection
("jdbc:odbc:Inventory","","");

/*
Once you have a Connection object, use it to get
the
DatabaseMetaData of the Connection (the
datasource).
Use the getTables method to get a list of all
tables
accessible from the datasource.
*/
DatabaseMetaData dmd = con.getMetaData();

ResultSet rs = dmd.getTables(null,null,null,null);

System.out.println("Table Name\ tTable Type");

while (rs.next())
{
/*
Print the results by iterating through
the result set and printing out the default
string
*/
System.out.println(rs.getString(3) +
"\ t" + rs.getString(4));
}
con.close();
} catch (Exception e)
{
System.out.println(e);
}
}
}

- 39 -
Using this technique, you can read all the attributes of the data source. Use this information
to determine the capabilities of the data source before you access it. Now that you have
this information, you are ready to access the database.

Accessing the Database


The connection with the database is used to send commands and SQL statements to the
database. The connection acts as direct link to the database driver. You request a SQL
statement object from the connection and put your SQL in this object. Think of the
Statement object as an envelope into which you put your message. The Connection
object is the transport that sends your message. When you pass SQL to the connection,
it forwards this information to the driver. The driver forwards the SQL to the database and
then returns results. The results come back to you in the form of a result set.

The Connection object has three methods that return objects representing a database
call. These methods are as follows:

createStatement(). Returns an object that implements the Statement interface. Use this
for executing single SQL calls on the database.

prepareStatement(). Returns a PreparedStatement object that extends the Statement


interface. Use this for sending SQL that contains parameterized values (referred to as IN
parameters). This type of statement may be more efficient if you plan to call a specific
SQL statement multiple times, because it may be precompiled.

prepareCall(). Returns a CallableStatement object that extends the PreparedStatement


to handle OUT parameters. Use this to execute stored procedures that have both IN
parameters and OUT result values.

A Statement object has three methods for sending SQL to the database and executing
database calls:

executeQuery(). Queries the database for a single table of result values. Usually used
for simple SELECT statements.

executeUpdate(). Updates values in the database. Usually used for INSERT, UPDATE,
DELETE, or data definition commands such as CREATE. This method returns a count of
rows affected by the command.

execute(). Queries the database for potentially multiple tables of result values. Use this
for calling a stored procedure that returns multiple results. Under some (uncommon)
situations, a single SQL statement may return multiple result sets and/or update counts.
Normally, you can ignore this method, unless you’re executing a stored procedure that
may return multiple results or you’re dynamically executing an unknown SQL string. If
you use this method, call the Statement method getMoreResults() to access subsequent
result sets.

Note JDBC 2.0 adds a fourth method: executeBatch(). This method allows you to
submit a batch of commands to the database for execution. executeBatch()
returns an array of update counts for each command in the batch. The counts
are ordered based on the order in which the commands were inserted into the
batch.

In the following example, a new table is created in the database. This example can be
run only once because it does not check for the existence of the table before executing
the CREATE statement. This means that on subsequent calls, a JDBC exception is
thrown that tells us the table exists. The database build script in Chapter 25, “A Four-Tier
Online Store,” shows how you can handle this exception, allowing the database to be
recreated if necessary.

- 40 -
This example creates a new table in our database called SalesHistory. To create the
table, first create a statement, then use it to create a new table. The data types
NUMBER, CURRENCY, and DATE in the CREATE statement are database dependent.
If the table already exists, an exception is thrown so that the success message prints only
when we actually add the table. Close the statement when done. Although closing the
statement in this case is unnecessary because the program is about to end anyway, in
some cases you want to immediately release a statement’s resources instead of waiting
for this to happen when it is collected as garbage.

import java.sql.*;

public class ExecuteExample


{
public static void main (String args[])
{
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (Exception e)
{
System.out.println("JDBC-ODBC driver failed to
load.");
return;
}

try
{
Connection con =

DriverManager.getConnection("jdbc:odbc:Inventory","","");

// Use a connection to create a new statement.


Statement stmt = con.createStatement();

// Use this statement to execute a specific SQL


statement.
stmt.execute("CREATE TABLE SalesHistory" +
"(ProductID NUMBER," +
" Price CURRENCY, " +
"TrnsDate DATE)");

System.out.println("Created Sales History table");

/*
As you do for connections, you should close the
statement when you are done.
*/
stmt.close();

con.close();
} catch (Exception e)
{
System.out.println(e);
}
}

- 41 -
}

The Statement object allows you to fetch and modify data. The next section looks at how
you can process the results returned from executing a SQL statement.

Fetching Data
Selecting data from a single table is done by using the Statement method
executeQuery(). The steps for fetching data are as follows:

1. Create a statement from a connection.

2. Execute the query.

3. Fetch a result set from the Statement object.

For example, the following call returns a list of all inventory items:

...
Connection con =
DriverManager.getConnection("jdbc:odbc:Inventory",
"","");
Statement stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM Inventory");
...

A database query returns a result set. If no values are returned, ResultSet will not contain
values.

Working with a Result Set


Once you have fetched a result set from a database call, you will want to process it.
ResultSet is organized into logical rows and columns of data. It maintains a cursor to the
current row. You can instruct ResultSet to move to the next row by calling next().

Note JDBC 2.0 contains methods for traversing to the next, previous, first, and last
row of ResultSet. It has methods for deleting current row, jumping to the insert
row, and so on. In general, ResultSet is much more feature rich. However,
with some work, most of these features can be duplicated in JDBC 1.0.

The columns of ResultSet can be accessed in any order and as often as needed.
Columns can be accessed by their positions in the column list or by name. For instance,
if you had the following SQL:

SELECT name, address, city FROM Customers

you could access the address attribute by name, address, or by index, 2. To get values
from ResultSet, use the getXXX methods included in the ResultSet object. Table 3.10
lists these methods.

Table 3.10: ResultSet getXXX Methods

Index getXXX method Name getXXX method Use

- 42 -
InputStream InputStream Get a column value as a
getAsciiStream(int getAsciiStream (Stringstream of ASCII
columnIndex) columnName) characters.

BigDecimal BigDecimal Get the value of a column


getBigDecimal(int getBigDecimal(String as a java.lang.BigDecimal
columnIndex, int scale) columnName, int scale) object.

InputStream InputStream Get a column value as a


getBinaryStream(int getBinaryStream(String stream of bytes.
columnIndex) columnName)

Boolean getBoolean(int boolean getBoolean(String Get the value of a column


columnIndex) columnName) as a Boolean integer.

byte getByte(int byte getByte(String Get the value of a column


columnIndex) columnName) as a byte.

byte[] getBytes(int byte[] getBytes(String Get the value of a column


columnIndex) columnName) as an array of bytes.

Date getDate(int Date getDate(String Get the value of a column


columnIndex) columnName) as a java.sql.Date object.
Date is a thin wrapper
around java.util.Date.

double getDouble(int double getDouble(String Get the value of a column


columnIndex) columnName) as a double.

Float getFloat(int float getFloat(String Get the value of a column


columnIndex) columnName) as a float.

int getInt(int int getInt(String Get the value of a column


columnIndex) columnName) as an int.

Object getObject(int Object getObject(String Get the value of a column


columnIndex) columnName) as an Object. The mapping
is based on the JDBC spec
and is listed in Appendix A.

short getShort(int short getShort(String Get the value of a column


columnIndex) columnName) as a short.

String getString(int String getString(String Get the value of a column


columnIndex) columnName) as a String.

Time getTime(int Time getTime(String Get the value of a column


columnIndex) columnName) as a java.sql.Time object.
Time is a wrapper around
java.util.Date that adds
formatting and parsing
operations to support the
JDBC escape syntax for
time values.

Timestamp Timestamp Get the value of a column


getTimestamp(int getTimestamp(String as a java.sql.Timestamp
columnIndex) columnName) object. Timestamp is a

- 43 -
wrapper around
java.util.Date that adds the
ability to hold the SQL
TIMESTAMP nanos value
and provides formatting
andparsing operations to
support the JDBC escape
syntax for timestamp
values.

Note If two columns have the same name, the first one matching your column
name will be returned. For this reason, it is recommended that you use the
position or index to reference the column. This way, programmers can
guarantee they are returning the correct column.

The next example fetches a list of all suppliers and the beverage products they sell from
the ProductSuppliersView view in the INV database. Once the list is loaded, the program
prints these values. To get data from the database, this example uses the executeQuery
method, which returns a ResultSet. Because the names of the returned data are known,
the actual column names are used to retrieve the values. Initially, the ResultSet cursor is
positioned before the first row of results. Each time rs.next() is called, the ResultSet
places its cursor on the next row and returns true until it reaches the end of the results.
Then it returns false. Figure 3.11 illustrates what your results will look like.

Figure 3.11: ResultSetExample output.

This example fetches all BEVERAGE products and their associated suppliers as well as
the prices charged for the product, then prints this list, sorted by product. The ResultSet
rs is returned with its cursor set before the first record. This is convenient for starting a
while loop with rs.next().The first time through, the cursor is on the first record. Inside the
loop, the values for the associated row columns are referenced by name. Use the getInt()
method for the price. We didn’t mind losing some data for our simple display purposes.
You could have used getFloat() or getDouble() and then formatted the output.

import java.sql.*;

public class ResultSetExample


{
public static void main (String args[])
{
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (Exception e)
{
System.out.println("JDBC-ODBC driver failed to
load.");
return;

- 44 -
}

try
{
Connection con =
DriverManager.getConnection(
"jdbc:odbc:Inventory",
"","");

Statement stmt = con.createStatement();

// Execute a query and retrieve ResultSet.


ResultSet rs = stmt.executeQuery("SELECT
SupplierName," +
" ProductName, Price " +
"FROM ProductSuppliersView " +
"WHERE CategoryName " +
"LIKE '%BEVERAGES%' " +
"ORDER BY ProductName");
/*
Now iterate through ResultSet and print the values
of each attribute in the row. ResultSet maintains
a
cursor pointing to its current row of data.
Initially,
the cursor is positioned before the first row.
The
next method moves the cursor to the next row.
*/
while(rs.next())
{
String supplier = rs.getString("SupplierName");
String product = rs.getString("ProductName");
//ResultSet has methods to map values to Java
types.
int price = rs.getInt("Price");

System.out.println(supplier + " sells " + product


+
" for $" + price);
}

stmt.close();
con.close();
} catch (Exception e)
{
System.out.println(e);
}
}
}

The results of a query are returned in a result set. ResultSet stores the results in rows
and maintains a pointer to the current row. Each value within a row can be accessed by
its name or by its position. For example, instead of the line:

- 45 -
int price = rs.getInt("Price");

the following could have been used:

int price = rs.getInt(3);

This works only because we know that Price is the third column in the preceding query.
You could also find this out by looking at the metadata of ResultSet. The next section
explores the ResultSetMetaData class.

Working with ResultSetMetaData


In the previous example, the query was hard coded, so using the column names was a
reasonable choice for fetching the data. However, what happens when you don’t know
the column names before accessing ResultSet? Each ResultSet can return an object that
implements the ResultSetMetaData interface. This object contains information about the
returned results, including the number of columns returned and the names and types of
the columns. This information can be used to dynamically display values. Table 3.11 lists
the ResultSetMetaData values.

Table 3.11: ResultSetMetaData Methods

Type and Method Use

String getCatalogName(int column) Returns a column’s table catalog name.

int getColumnCount() Returns the number of columns in


ResultSet.

int getColumnDisplaySize(int column) Returns the column’s preferred maximum


width in characters.

String getColumnLabel(int column) Returns the suggested column title for


displaying the column.

String getColumnName(int column) Returns a column’s name.

int getColumnType(int column) Returns a column’s SQL type.

String getColumnTypeName(int column) Returns a column’s data source-specific


type name.

int getPrecision(int column) Returns a column’s number of decimal


digits.

int getScale(int column) Returns a column’s number of digits to


right of the decimal point.

String getSchemaName(int column) Returns the name of a column’s table’s


schema.

String getTableName(int column) Returns a column’s table name.

- 46 -
boolean isAutoIncrement(int column) Returns whether this field is automatically
generated. If it is automatically generated,
it should be considered a read-only set of
values.

boolean isCaseSensitive(int column) Returns true if the column’s case matters


to the data source.

boolean isCurrency(int column) Returns whether the column is a monetary


value.

boolean isDefinitelyWritable(int column) Returns whether a write on the column


definitely will succeed.

int isNullable(int column) Returns true if you can put a NULL in this
column.

boolean isReadOnly(int column) Returns true if the column definitely is not


writable.

boolean isSearchable(int column) Returns true if the column can be used in a


WHERE clause.

boolean isSigned(int column) Returns true if the column represents a


signed number.

boolean isWritable(int column) Returns true if it is at least possible for a

In this next example, the user is asked to type in SQL SELECT statements. The program
will dynamically make requests against the database and display the results. The
program will continue until the user types the word “exit.” In this example, only SQL
statements that begin with the word SELECT are executed. This example could easily be
extended to handle any SQL calls.

The example uses a java.io.BufferedReader to read values from the console, then loops
while the variable notDone is true. This variable is set to false when the user types “exit”
at the prompt. Assuming the user types a valid SELECT statement, we call
executeQuery() because we expect a result set back.

Note The Statement method executeQuery() always returns a result set, even if
there are no values returned. In that case, ResultSet would have no rows, but it
would contain metadata related to the query.

To find out what columns are returned, first access ResultSetMetaData, then use that to
find the column count. Now iterate through the columns and print each column name.
After that, iterate through each row of data, printing the values. As an added example of
using metadata, check the data source column type and print a $ if the type of the column
is CURRENCY, a Microsoft Access-specific data type. You could also have used the
ResultSetMetaData method isCurrency(). This would have been more database
independent. getColumnType was used for example purposes only, as shown in Figure
3.12. If the name of a column, but not its index, is known, the method findColumn() can
be used to find the column number.

- 47 -
Figure 3.12: ResultSetMetaDataExample output.

import java.sql.*;
import java.io.*;
public class ResultSetMetaDataExample
{
public static void main (String args[])
{
try
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
} catch (Exception e)
{
System.out.println("JDBC-ODBC driver failed to
load.");
return;
}

try
{
Connection con =
DriverManager.getConnection("jdbc:odbc:Inventory",
"","");

Statement stmt = con.createStatement();

boolean notDone = true;


String sqlStr = null;
/* In this example we use a buffered reader to read
in an
SQL statement from the command line. This allows
the
user to type in any arbritrary SQL statement.
Try SELECT * FROM INVENTORY
*/
BufferedReader br = new BufferedReader(
new InputStreamReader(System.in));

// We iterate until the user types "exit"


while(notDone)
{
System.out.print("Enter SELECT Statement:");

- 48 -
sqlStr = br.readLine();

if(sqlStr.startsWith("SELECT") ||
sqlStr.startsWith("select"))
{
// If this is a SELECT statment, then process
ResultSet rs = stmt.executeQuery(sqlStr);
ResultSetMetaData rsmd = rs.getMetaData();

/* Since we don't know how many columns will


come
back from an abritrary query, we need to
check
the metadata of ResultSet.
*/
int columnCount = rsmd.getColumnCount();

// Print all the column names.


for(int x =1;x<=columnCount;x++)
{
String columnName =
rsmd.getColumnName(x);
System.out.print(columnName +"\ t");
}
System.out.println("");

// Now print each row of data.


while(rs.next())
{
for(int x =1;x<=columnCount;x++)
{
if(
/*
The metadata returns the data
type
so we can adjust our display
for
certain types such as currency.
*/
rsmd.getColumnTypeName(x).
compareTo("CURRENCY") == 0
)
System.out.print("$");
String resultStr = rs.getString(x);
System.out.print(resultStr +"\ t");
}
System.out.println("");
}
}
else if(sqlStr.startsWith("exit"))
notDone = false;
}
stmt.close();
con.close();

- 49 -
} catch (Exception e)
{
System.out.println(e);
}
}
}

As this example shows, there is more to fetching data than just retrieving values. You
may also need to query the results themselves for metadata. Use this information to
determine the validity of your data and its format.

The next section looks at the process of modifying values and sending data to the data
source.

Handling Data
Besides fetching information from a data source, you will want to insert, update, delete,
and modify the structure of a data source. All these activities can be accomplished using
the executeUpdate() method of a Statement object. This method returns the number of
rows affected by the call. For example, the following code creates a table and inserts
values into it, updates the values, and then deletes the rows and drops the table.

...
Statement stmt = con.createStatement();

stmt.executeUpdate("CREATE TABLE Temp" +


"(id int, name varchar(25), value float)");

int insCount = stmt.executeUpdate("INSERT INTO Temp" +


"VALUES (1,"test1", 5.0)");
System.out.println("Inserted " + insCount + "rows");

insCount = stmt.executeUpdate("INSERT INTO Temp " +


"VALUES (2,"test2", 10.0)");
System.out.println("Inserted " + insCount + "rows");

int updateCount = stmt.executeUpdate("UPDATE Temp SET


value=1.0");
System.out.println("Updated " + insCount + "rows");

int deleteCount = stmt.executeUpdate("DELETE FROM Temp");


System.out.println("Deleted " + insCount + "rows");

Stmt.executeUpdate("DROP TABLE Temp");

Stmt.close();
...

Prepared Statements
As shown in the last example, it is common to execute the same statements repeatedly,
with changes to only the value attributes. Each time you construct a statement string, it
must be compiled and the driver must map the values in your string to the SQL
understood by the underlying database. If you execute enough identical statements, this
can become inefficient. JDBC provides an interface called a prepared statement that

- 50 -
implements the Statement interface. A prepared statement represents a “compiled”
statement that is fairly static. You can get a prepared statement from a connection by
calling the prepareStatement() method. The only things that change in the
PreparedStatement string are the parameters to the SQL string that the statement
contains. These parameters are represented in the original string by question marks (?).
To set the values for these parameters, PreparedStatement has setXXX methods that
take two parameters. The first parameter is the index (starting at 1) of the parameter you
want to modify; the second parameter is the value. Consider the previous example. Here
is what the PreparedStatement version of that example might look like:

...
Statement stmt = con.createStatement();
PreparedStatement pstmt = con.prepareStatement(
"INSERT INTO Temp VALUES (?,?,?)"
);
stmt.executeUpdate("CREATE TABLE Temp " +
"(id int, name varchar(25), value int)");

//First paramter is index, second is value of specified type


pstmt.setInt(1,1);
pstmt.setString(2,"test1");
pstmt.setFloat(3,5.0);
int insCount = pstmt.executeUpdate();
System.out.println("Inserted " + insCount + "rows");

pstmt.setInt(1,2);
pstmt.setString(2,"test2");
pstmt.setFloat(3,10.0);
insCount = pstmt.executeUpdate();
System.out.println("Inserted " + insCount + "rows");
int updateCount = stmt.executeUpdate("UPDATE Temp SET
value=1.0");
System.out.println("Updated " + insCount + "rows");

int deleteCount = stmt.executeUpdate("DELETE FROM Temp");


System.out.println("Deleted " + insCount + "rows");

Stmt.executeUpdate("DROP TABLE Temp");

Stmt.close();
...

Note Stored procedures can be called from the Statement and PreparedStatement
objects. However, a connection has a method named prepareCall() that returns an
object that implements the CallableStatement interface. This interface extends the
PreparedStatement interface by adding methods that allow you to get the values
coming back from a store procedure. Typically, these values are marked as OUT
parameters in the stored procedure declaration. Stored procedure handling is
beyond the scope of this section, but the principle is similar to the topics discussed
so far.

PreparedStatement is beneficial when you intend to call the same SQL multiple times. It
saves on the time necessary to compile a query. For a one-time query, use the
Statement object. In each case, you are handling one SQL statement at a time.

The next section looks at how to control multiple SQL calls in a single transaction.

- 51 -
Managing Transactions
A transaction is a collection of consecutively executed database commands that are
considered linked as one business process. If any part of a transaction fails, the
programmer has the option of retrying the transaction or saving the current state of the
transaction as permanent. To retry a transaction, it must be rolled back. Rollback
transaction is a common term for resetting the state of the database to the point before
the transaction was executed. To save the transaction, execute a commit transaction
function. This saves all the changes made to the database as permanent changes. The
important point is that all statements executed as part of a transaction are considered a
group. Whatever you do to the transaction affects all statements in that transaction. For
example, if you deleted a row, changed the value in another row, and then rolled back the
transaction, both the delete and the update would be undone.

By default, a connection is set to auto commit. This means that every statement executed
is considered to be in its own transaction. So, if the statement is successful, it is
committed. To have several statements executed as a single transaction, turn off auto
commit by calling the Connection method setAutoCommit() and passing false. Then use
the Connection methods commit() and rollback() to control the status of your transaction.
Not all databases support transactions; even the ones that do support them at different
levels.

Most JDBC drivers support transactions. In fact, a JDBC-compliant driver must support
transactions. DatabaseMetaData supplies information describing the level of transaction
support a DBMS provides. This level is called the transaction isolation level. The higher
this level, the more careful the database will be in preventing multiple users from
overwriting each other’s transactions. However, the higher the isolation level, the slower
the database may be. This is due to the degree of locking the database will perform. To
find out the isolation level of your database, call the Connection method
getTransactionIsolation(). Calling setTransactionIsolation can set level(). Be careful
changing the isolation level during a transaction, because this causes the method commit
to be called, which will cause any changes up to that point to be made permanent.

The isolation levels are listed and explained in Table 3.12.

Table 3.12: Transaction Isolation Levels

Transaction Level Meaning

TRANSACTION_NONE Transactions are not supported.

TRANSACTION_READ_COMMITTED Dirty reads are prevented; non-repeatable


reads and phantom reads can occur.

TRANSACTION_READ_UNCOMMITTED Dirty reads, non-repeatable reads, and


phantom reads can occur.

TRANSACTION_REPEATABLE_READ Dirty reads and non-repeatable reads are


prevented; phantom reads can occur.

TRANSACTION_SERIALIZABLE Dirty reads, non-repeatable reads, and


phantom reads are prevented.

- 52 -
Most databases support transactions; however, some do not. In these cases, you may
find that the driver ignores your transaction calls. You can usually find out how the driver
will handle transactions by checking the metadata and checking the property
supportsTransactions. In some situations, an exception may even be raised.

The next section details the error-handling mechanisms of JDBC.

Exception-Handling /SQL Warning


Notice the previous examples were catching SQLExceptions. This is the generic
exception type for JDBC methods. JDBC provides three types of exception handling:

• SQLException

• SQLWarning

• DataTruncation

Let’s take a look at each of these in more detail.

SQLExceptions

SQLException is a subclass of exception consisting of a string error message, a string


containing the SQL state as defined by the XOPEN SQL state specification, and a driver-
specific int used as an additional error code. Here is an example:

try
{
Statement s = con.createStatement();
} catch (SQLException e)
{
System.println.out(e);
System.println.out("Database Driver/Source error code: " +
e.getErrorCode());
System.println.out("XOPEN SQL State: " + e.getSQLState());
}

If multiple errors were generated, multiple SQLException instances can be chained


together. To get access to the next error, call getNextException() on the SQLException
object. To add to the chain, call setNextException().

SQLWarnings

The SQLWarning class is a subclass of SQLException, but it is typically used for non-
critical errors. Usually, it is not thrown as an exception. Rather, it is typically up to the
program to query for SQLWarnings by calling the getWarnings() method of any
Connection, Statement, or ResultSet object. Each time these objects are used, they clear
out the warnings by calling clearWarnings(). This means that if you do not poll for
messages, you will not get them.

Data Truncation

The DataTruncation class is a subclass of SQLWarning. A data truncation occurs when


you perform a query and do not or cannot process all of the results. For example, you

- 53 -
might get a data truncation warning if you perform a query that returns 15 rows, and you
process only the first one with your result set before closing it. DataTruncation warnings
are issued as part of a chain of SQLWarning messages. This means that as you process
a SQLWarning chain of messages, you must look specifically for DataTruncation objects
if you want to call DataTruncation-specific methods. Finding a DataTruncation requires
you to perform an instance of DataTruncation check on each SQLWarning.

The following example illustrates this process:

SQLWarning warning = stmt.getWarnings();


while (warning != null)
{
System.out.println(warning.getMessage());
if(warning instanceof DataTruncation)
{
DataTruncation dt = (DataTruncation)warning;
if(dt.getParameter())
System.out.print("The parameter had ");
System.out.print("A trunction error in column: " +
dt.getIndex() +
"should have tranfered " +
dt.getDataSize() + "bytes. " +
"Actually transfered " +
dt.getTransferSize() + "bytes");

if(dt.getRead())
System.out.print(" while reading.");
else
System.out.print(".");

System.out.println("");
}

warning = warning.getNextWarning();
}

Notice that getWarnings continue to be called until null is returned. Null signifies that
there are no more errors.

The next section puts together the concepts discussed in this chapter by demonstrating a
larger application.

Putting It Together
The final example for this chapter is a complete Java application. This application uses
the Java Foundation Classes (JFC), which are included with Java 2. If you are unfamiliar
with JFC, you may want to read our book, Programming with JFC (John Wiley & Sons,
1998). This example is not meant to demonstrate the optimal use of all JDBC features.
Rather, this is a combination of JDBC features that have been put in one application to
demonstrate several capabilities of JDBC 1.0. The example illustrated in Figure 3.13
displays a list of inventory items from our INV database. Some columns are calculated. At
the bottom of the main screen is a series of buttons:

- 54 -
Figure 3.13: JDBCTable application.

Query Database. This button loads the records from the database and displays them.

Delete Current Row. Deletes the row from the database currently selected in the JTable.

Insert Row. Inserts a new row in the JTable.

Start Transaction. Starts a transaction. After this, all operations are in the transaction
scope until the transaction is committed or rolled back.

Commit Transaction. Commits the transaction if there is one in progress

Rollback Transaction. Rolls back the transaction if there is one in progress.

The example consists of three classes:

JDBCTable. A subclass of JPanel that displays our UI, including a table of data.

JDBCTableModel. A subclass of AbstractTableModel that implements all of the


database access routines and keeps track of the current selection from the database.

WindowCloser. A subclass of WindowAdapter used as a convenience class for quitting


the application when the user closes the main window.

Let’s look at each of these classes in detail.

JDBCTable

The following code defines the JDBCTable class. The JDCTable class displays the main
JPanel and places a JTable and six buttons in the table. JDBCTable handles the action
for each button. The JDBCTable main method starts by creating a new
JDBCTableModel, which takes a URL, a driver class name, a user name, and a
password as parameters. The table uses a JDBCTableModel to connect to the database.
Once the model connects to the database, the JDBCTable installs this model in the
JTable.

As part of this example, we check the results of requesting the model commit or rollback
transaction because these could fail if the database doesn’t support transactions, if the
records are locked by someone else, and so on. The start transaction isn’t checked
because in this case it won’t affect much. However, you could easily add that feature.

import java.awt.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.io.*;

- 55 -
import java.util.*;

public class JDBCTable extends JPanel implements ActionListener


{
JDBCTableModel model;
JTable table;

static String queryStr = "Query Database";


static String deleteStr ="Delete Current Row";
static String insertStr = "Insert Row";
static String startTranStr = "Start Transaction";
static String commitTransStr = "Commit Transaction";
static String rollbackStr = "Rollback Transaction";

public JDBCTable()
{
File root;
Font f;
JPanel tmpPanel;

setLayout(new BorderLayout());

// Instantiate the model passing in URL for the JDBC


driver.
model = new JDBCTableModel("jdbc:odbc:Inventory"

,"sun.jdbc.odbc.JdbcOdbcDriver"
,""
,"");

table = new JTable();


table.setModel(model);
table.createDefaultColumnsFromModel();
table.setRowSelectionAllowed(true);
add(JTable.createScrollPaneForTable(table),"Center");

// Create set of buttons for controlling the interface.


tmpPanel = new JPanel();
tmpPanel.setLayout(new GridLayout(2,3));

this.addButtonWithTitleToPanel(queryStr,tmpPanel);
this.addButtonWithTitleToPanel(deleteStr,tmpPanel);
this.addButtonWithTitleToPanel(insertStr,tmpPanel);

this.addButtonWithTitleToPanel(startTranStr,tmpPanel);
this.addButtonWithTitleToPanel(commitTransStr,tmpPanel);
this.addButtonWithTitleToPanel(rollbackStr,tmpPanel);

add(tmpPanel,"South");
}

// Convenience method to install button and action.


private void addButtonWithTitleToPanel(String title,

- 56 -
JPanel panel)
{
JButton button = new JButton(title);
button.addActionListener(this);
panel.add(button);
}

// Handle action based on button pressed.


public void actionPerformed(ActionEvent evt)
{
String ac = evt.getActionCommand();
if(ac.compareTo(queryStr) == 0)
model.executeQuery();
else if(ac.compareTo(insertStr) == 0)
model.executeInsert();
else if(ac.compareTo(deleteStr) == 0)
model.executeDelete(table.getSelectedRow());
else if(ac.compareTo(startTranStr) == 0)
{
model.startTransaction();
JOptionPane.showMessageDialog(this,
"Transaction is now in progress.",
"", JOptionPane.INFORMATION_MESSAGE);
}
else if(ac.compareTo(commitTransStr) == 0)
{
// Make sure commit succeeds first.
if(model.commitTransaction())
JOptionPane.showMessageDialog(this,
"Transaction has been commited. ",
"", JOptionPane.INFORMATION_MESSAGE);
else
JOptionPane.showMessageDialog(this,
"Transaction could not be commited. ",
"", JOptionPane.INFORMATION_MESSAGE);
}
else if(ac.compareTo(rollbackStr) == 0)
{
if(model.rollbackTransaction())
JOptionPane.showMessageDialog(this,
"Transaction has been rolled back. ",
"", JOptionPane.INFORMATION_MESSAGE);
else
JOptionPane.showMessageDialog(this,
"Transaction could not\ n be rolled back. ",
"", JOptionPane.INFORMATION_MESSAGE);
}
}
public Dimension getPreferredSize()
{
return new Dimension(600, 300);
}

- 57 -
public static void main(String s[])
{
JFrame frame = new JFrame("JDBC Table Example");
JDBCTable panel = new JDBCTable();

System.setErr(System.out);

frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setForeground(Color.black);
frame.setBackground(Color.lightGray);
frame.getContentPane().add(panel,"Center");

frame.setSize(panel.getPreferredSize());
frame.setVisible(true);
frame.addWindowListener(new WindowCloser());
}
}

JDBCTableModel

JDBCTableModel class does all the database access. Let’s take this class one method at
a time. First, initialize some strings and variables, including static variables for the queries
used in this example.

import java.util.*;
import java.util.*;
import java.sql.*;
import java.awt.swing.table.*;
import java.awt.swing.event.*;

public class JDBCTableModel extends AbstractTableModel


{
//JDBC objects
Connection connection;
Statement statement;
PreparedStatement delStatement;
ResultSet resultSet;
ResultSetMetaData metaData;
//Default strings used for accessing the Access databases.
static String queryStr = "SELECT ProductId, ProductName, " +
"ProductDescription, Price, " +
"ReorderLevel, quantity, " +
"IIf(quantity - reorderlevel > 0," +
"'NO','YES') AS " +
"[Need To Reorder] FROM Inventory";
static String insertStr = "INSERT INTO Inventory(ProductName)
" +
"Values ('New Product')";
// Table objects
Vector names;
Vector types;
Vector data;

- 58 -
JDBCTableModel

The constructor for JDBCTableModel creates a connection with the database. This
connection persists until the application ends. By keeping the connection and the
associated resources open, database resources are being used up, but keeping them
open makes the example shorter. If the connection is not expected to be open long or if
you are not concerned about resource constraints, it is reasonable to leave it open for the
duration of your application. After creating a connection, create PreparedStatement for
deleting records. This object passes a single value, the product id, and deletes the
associated row. After creating these resources, executeQuery is called, which initially
loads the records from the database and displays them in the table.

public JDBCTableModel(String url, String driverName,


String user, String passwd)
{
data = new Vector();
names = new Vector();

try
{
//load the driver
Class.forName(driverName);
connection = DriverManager.getConnection(url, user,
passwd);

/*
Create a statement for use in all the methods of this
object.
In general, it would be better to create and close the
statement more often. However, keeping it open makes
the example shorter.
*/
statement = connection.createStatement();
// Create a compiled statement for executing deletes.
delStatement =
connection.prepareStatement("DELETE FROM "
+ "Inventory WHERE ProductId = ?");

// Initialize the table.


this.executeQuery();
}
catch (Exception exp)
{
System.out.println("Error connecting: "+exp);
}
}

executeQuery

The executeQuery method queries the database and returns ResultSet containing
Inventory items. These records have some calculated fields. The product id and the
calculated fields are not editable, but the rest of the fields are. ResultSet is loaded into a
set of internal vectors that represent the data in the JTable. Calling fireTableChanged()
notifies the table that the data has changed, and it reloads the data from these vectors.

- 59 -
public void executeQuery()
{
int i,max;
Vector rowData;
int curType;

try
{
data = new Vector();
names = new Vector();

resultSet = statement.executeQuery(queryStr);
metaData = resultSet.getMetaData();
max = metaData.getColumnCount();

//get the column names


for(i=0;i<max;i++)
{
//adjust for meta data index start at 1
names.addElement(metaData.getColumnLabel(i+1));
}

//load the data


while (resultSet.next())
{
rowData = new Vector();

for (i=0;i<max;i++)
{
rowData.addElement(resultSet.getObject(i+1));
}
data.addElement(rowData);
}
fireTableChanged(null);
}
catch (Exception exp)
{
System.out.println("Error performing query: "+exp);
exp.printStackTrace();
}
}

executeInsert

When the Insert button is pressed, a record is inserted in the underlying database. The UI
is updated to reflect the change. This could be made more efficient by not actually
fetching the data again, instead keeping track of the inserted records manually until the
next fresh query.

Note JDBC 2.0 contains methods for directly manipulating ResultSet. This would
more elegantly solve the problem of refetching values.

- 60 -
public void executeInsert()
{
if(statement != null)
{
try
{
statement.executeUpdate(insertStr);

// refresh
this.executeQuery();
} catch(SQLException e)
{
System.out.println(e);
return;
}
}
}

executeDelete

When the Delete Current Row button is pressed, the executeDelete method takes the
row id and deletes that row by using a prepared statement that takes one parameter, the
product id. Because the index for the JTable starts at 0, you need to offset to 1. This is
because JDBC index also starts at 1.

public void executeDelete(int row)


{
if(row > -1)
{
// Get the product ID.
Object id = this.getValueAt(row,0);

if(statement != null)
{
try
{
/*
Set the value of the first paramter in
this compiled delete statement.
*/
delStatement.setObject(1,id);

// Execute the statement.


delStatement.executeUpdate();

//Refresh. In JDBC 2.0, there is a refresh


method.
this.executeQuery();
} catch(SQLException e)
{
System.out.println(e);
return;
}

- 61 -
}
}
}

setValueAt

When a user types in an editable table field, the setValueAt method is called. The text the
user typed in and the position of the cell are passed in. If the value has changed,
determine the type of field in the database, then use the appropriate syntax to store it.

public void setValueAt(Object value, int row, int col)


{
Vector rowData = (Vector)data.elementAt(row);
Object o = rowData.elementAt(col);

if(o != value)
{
try
{
String s = "UPDATE INVENTORY SET " +
metaData.getColumnName(col+1) + "=";
int curType = metaData.getColumnType(col+1);

switch(curType)
{
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
case Types.DATE:
s += "'"+value.toString()+"'";
break;
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
case Types.BIGINT:
case Types.FLOAT:
case Types.DOUBLE:
s += value.toString();
break;
default:
s += "'"+value.toString()+"'";
}

s += " WHERE PRODUCTID = " +


rowData.elementAt(0)+";";
statement.executeUpdate(s);

// refresh
this.executeQuery();
} catch(SQLException e) { System.out.println(e);}
}
}

- 62 -
startTransaction

When the Start Transaction button is pressed, the startTransaction method is called. In
order to start a transaction, turn auto commit off. You cannot do this on some drivers if
you have an open ResultSet. So, close ResultSet, then reestablish the query after turning
on the transaction.

public void startTransaction()


{
/*
Turn off AutoCommit; this makes all commands fall
into same transaction.
*/
try
{
/*
Must close existing result set before
changing transaction state.
*/
if(resultSet != null)
resultSet.close();
connection.setAutoCommit(false);
// refresh
this.executeQuery();
} catch(SQLException e)
{
System.out.println(e);
}
}

commitTransaction

When a transaction is committed, it saves all current statements to the database. The
commitTransaction method in JDBCTableModel handles the case in which a commit may
fail, as in the case of no current transaction or if a database error occurs—for example, if
someone else locks the record we are trying to change.

public boolean commitTransaction()


{
boolean commitIsOk = false;

try
{
/*
Check if autocommit is on.
If it is, don't commit manually.
*/
commitIsOk = !connection.getAutoCommit();

if(commitIsOk)
{
// Commit transaction
connection.commit();

- 63 -
// Turn on AutoCommit; this is default.
connection.setAutoCommit(true);
}
} catch(SQLException e)
{
System.out.println(e);
commitIsOk = false;
}

//refresh
if(commitIsOk)
this.executeQuery();

return commitIsOk;
}

rollbackTransaction

The rollbackTransaction method works similarly to the commitTransaction() method. If


this method is successful, the current transaction rolls back and all changes done within
the scope of the transaction are undone.

public boolean rollbackTransaction()


{
boolean rollbackIsOk = false;

try
{
//Check if auto commit is on. If it is, don't roll back.
rollbackIsOk = !connection.getAutoCommit();

if(rollbackIsOk)
{
// rollback transaction
connection.rollback();

//Turn on Auto Commit; this is default.


connection.setAutoCommit(true);
}
} catch(SQLException e)
{
System.out.println(e);
rollbackIsOk = false;
}

//refresh
if(rollbackIsOk)
this.executeQuery();

return rollbackIsOk;
}
}

- 64 -
Miscellaneous Table-Related Methods

The following are housekeeping methods required by the JDBCTableModel.

The close method is called to close the connection to the database and all associated
resources.

public void close() throws SQLException


{
resultSet.close();
statement.close();
connection.close();
}

The close method is called from finalize(), which has been overridden to call close.

protected void finalize() throws Throwable


{
close();
super.finalize();
}

The getColumnName method is a convenience method that returns the name of a


column based on its positional index.

public String getColumnName(int col)


{
String retVal;

retVal = (String) names.elementAt(col);

if(retVal == null)
retVal = "";

return retVal;
}

The method isCellEditable returns true if the column is one of the first five. This method is
a sort of “hack,” so we don’t have to add too much code for processing the logic of each
individual column. The intent is to keep the example straightforward:

public boolean isCellEditable(int row, int col)


{
/*
Only the first four columns after the product id are
updateable in this example (adjust for index difference).
*/
return ((col < 6) && (col > 0));
}

The getColumnCount method returns the number of columns in the table.


public int getColumnCount()

- 65 -
{
return names.size();
}

The getRowCount method returns the number of rows in the table.

public int getRowCount()


{
return data.size();
}

The getValueAt method returns the value of a specific cell of the table.
public Object getValueAt(int row, int col)

{
Vector rowData = (Vector)data.elementAt(row);
return rowData.elementAt(col);
}

WindowCloser

WindowCloser is used to respond to a windowClosing event. It quits the application.

class WindowCloser extends WindowAdapter


{
public void windowClosing(WindowEvent e)
{
Window win = e.getWindow();
win.setVisible(false);
System.exit(0);
}
}

This example highlights the basic use of JDBC 1.0. Sun has recently released the
specification for JDBC 2.0, which is even more powerful. The next section highlights some
of the features that will be available when JDBC 2.0 product is released in full.

JDBC 2.0 Features


JDBC 2.0 adds many new features to the overall architecture of JDBC. When drivers
start supporting these features, database access activities will be even easier and more
flexible. In general, JDBC 2.0 supports the goals of JDBC 1.0, which include ease of use
and good integration with the rest of the Java 2 platform, and Java philosophy of software
design.

The specific goals of JDBC 2.0 include the following:

Leverage the strengths of the JDBC 1.0 and Java APIs. JDBC 1.0 goals are still
important for the further development of JDBC.

Maintain compatibility with JDBC 1.0 applications and drivers. This is an important
goal because it ensures that as JDBC improves, your current applications won’t stop
working. In fact, your application is guaranteed to work with JDBC 2.0 drivers.

- 66 -
Leverage JavaBeans. JavaSoft believes it is important for JDBC as a core data access
technology to leverage the JavaBeans component model. JDBC 2.0 supports JavaBeans
by implementing a RowSet object capable of tracking a set of records, even when not
connected to the data source. This object is a serializable JavaBean, which can be used
as a data container in any tool or application that supports JavaBeans.

Provide advanced database features. JDBC 1.0 does a good job of providing
capabilities equivalent to ODBC. JDBC 2.0 takes a hard look at database-specific
features that add more power and provides mechanism for tapping into these features.
Features include more support for Binary Large Objects (BLObs) and scrollable cursors.

JDBC 2.0 is designed so that JDBC 1.0 programmers can easily adopt its
functionality. JDBC 2.0 extends the capabilities of JDBC 1.0.

Database Enhancements
There are many improvements in the JDBC 2.0 specification. The most notable
improvements include these:

Scrollable cursors. These are implemented using ResultSet, which maintains an


internal pointer called a cursor. The cursor indicates the row in the result set that is
currently being accessed. In JDBC 2.0, these cursors may be used to move backward as
well as forward. This allows the user to manipulate a record and come back to it later
without having to requery the database.

Advanced data type support. JDBC 2.0 provides better mapping support between Java
objects and SQL3 data types such as BLObs. Additionally, there is support for user-
defined types.

Non-SQL database support. JDBC 2.0 provides some support for non-SQL databases
so that drivers for data sources like file systems and non-SQL databases are easier to
use.

Batch updates. Batch updates allow you to store several database calls, then execute
them all at once. If auto commit is off, these statements can be grouped in a single
transaction, or they can be executed as a set of separate transactions.

Persistence of Java objects. Support for type mapping and better object support allow
drivers to ease the process of storing Java objects and custom types in a database.

Connection pooling. Connection pooling may be implemented on top of the JDBC driver
layer, allowing for a connection cache that works between JDBC drivers used by your
application. This is a performance feature that enables you to share a connection to a
database, allowing you to avoid the high cost of creating and destroying database
connections.

JDBC 2.0 is more powerful because it allows you more flexibility in how you access and
manipulate data. It is also easy to use because it simply extends the features of JDBC
1.0, which means everything you have learned here will apply to JDBC 2.0. However,
there are some design changes, as discussed in the next section.

Design Changes
In JDBC 2.0, the API has been split into two packages. The first package, java.sql, is
referred to as JDBC 2.0 Core API. A new package, javax.sql, is called the JDBC 2.0
Standard Extension. The JDBC 2.0 Core API consists of all of the classes discussed in
this chapter plus the list in Table 3.13.

Table 3.13: JDBC 2.0 Core API Class Additions

- 67 -
Class/Interface Use

ArrayLocator An interface that represents reference during a transaction


to an array of values in the database.

BatchUpdateException A subclass of SQLException that provides information on all


statements within the batch being processed.

BlobLocator An interface that represents a reference to a binary large


object in the database.

ClobLocator Similar to a BlobLocator except for Character Large Objects


(CLObs).

Ref An interface that can be saved to persistent storage and


represents a reference to a SQL structured value in the
database.

SQLData A Java class registered for type mapping implements this


interface. It enables the driver to populate the object with
values from the database or write the class’s values to the
database.

SQLInput Streams containing values that represent SQL values


implement this interface.

SQLOutput Streams containing values that represent SQL values


implement this interface.

SQLType This interface provides some information about a SQL type.

Struct Extends the SQLData interface for mapping of structured


data.

StructLocator This interface represents a reference during a transaction to


a structure in the database.

The javax.sql package contains the parts of the JDBC 2.0 API that are related to other
Java standard extensions, such as the Java Naming and Directory Interface (JNDI) and the
Java Transaction Service (JTS), discussed later in this book. This package includes
features such as connection pooling and row sets. At the time of this writing, the
specification was not available. For more information on javax.sql, check the Sun Web site
at www.javasoft.com/products/jdbc/index.html.

Summary
JDBC provides a solid architecture for accessing a data source in a platform-independent
fashion. Most significant is that JDBC leverages the benefits of the Java design
philosophy by keeping the API simple and easy to use. As new drivers appear, they will
continue to fall more and more into the level 3 and 4 categories, which means vendors of
databases, application servers, and other middleware products will have a tendency to

- 68 -
provide pure Java solutions. Drivers that will support JDBC 2.0 features will still work with
your JDBC 1.0 applications. This means you should develop for JDBC today and think
about how the JDBC 2.0 features will help you tomorrow.

When developing applications that access a database, the most critical issues are:

• Accessing the data and features of the specific data source

• Performance of the gateway used to access this database

• Ease of new features that can be added to the application for supporting advanced
database calls

• Database resources should almost always be considered limited and valuable in your
design

JDBC does a good job of providing a baseline API for accessing your data source and
allowing driver suppliers to extend the API so you can access specific database features.
However, if the driver you are using is limited, be prepared to make some database calls
through other means, such as vendor-specific gateways or libraries. Just make sure you
encapsulate these calls in a separate method or class so that as more advanced drivers
come out, you can easily move all of your calls to JDBC-only solutions. JDBC-only
solutions give you more flexibility and make future enhancements easier because the
vendors will update the data access features for you. Most important, remember that
connections to the database represent real network and database resources. You should
disconnect as soon as possible or at least minimize the number of open connections a
client holds, because most databases can only handle a limited number of these. One
solution to this problem is to use an intermediate product such as an application server.
These products can provide you with access to your database through shared
connections to your database.

JDBC provides a standard API for Java developers and makes it possible to write
database applications using a pure Java API. An enterprise developer can use JDBC to
connect all its heterogeneous database systems. Many of the services provided by Sun
enable Java programmers to access standard repositories of information. The next
chapter looks at another type of data repository called a naming and directory service. A
naming and directory service provides information about resources on the network, such
as printer locations, employee e-mail address lookup, and so on. The name of the Java
API for accessing these services is called the Java Naming and Directory Interface, or
JNDI.

This chapter covered the basics of connecting to a database using JDBC 1.0. For a more
comprehensive look at this topic, you may want to consider JDBC Database Access With
Java: A Tutorial and Annotated Reference (Java Series) by Graham Hamilton, Rick Cattell,
and Maydene Fisher, published by Addison-Wesley. Also check out the Sun Web site at
www.javasoft.com/products/jdk/1.1/docs/guide/jdbc/getstart/introTOC .doc.html. If you are
interested in taking a course on this topic, check www.pri.com for a Java course schedule.

Chapter 4: What Is JNDI?


Overview
Internet and intranet applications rely heavily on naming and directory services for
accessing network resources. These services provide networkwide sharing of information
related to users, machines, other network services, and remote applications. Java
Naming and Directory Interface (JNDI) is an API that describes a standard Java library
for accessing naming and directory services such as Domain Naming Service (DNS),
Lightweight Directory Access Protocol (LDAP), Remote Method Invocation (RMI), or even
the file system on your computer. JNDI is powerful because it allows various services to

- 69 -
be linked through a single API, enabling Java applications that use JNDI to navigate
seamlessly across file systems, databases, servers, and so on. For example, you could
look up a specific RMI registry by requesting the information from an LDAP server, then
use the same JNDI API to request a specific object from the registry and save a
reference to the object in your file system using the JNDI file system service provider API.

This chapter describes the terminology, goals, and architecture of JNDI.

Terminology
In order to understand when and how to use JNDI, you must be familiar with the two
types of services it supports: directory and naming services. Directory services typically
provide access to a hierarchical tree of objects, such as the directories in the file system
on your computer. A naming service allows access to objects by name. For example, a
corporate LDAP server could allow you to find an e-mail address by entering the person’s
last name and department number. DNS, on the other hand, allows programs to look up
a computer’s IP address by name. JNDI for directory and naming services enables the
Java developer to build applications that leverage the features of the Java object model
and, at the same time, integrate well with most commercial enterprise applications. In
order to use JNDI, you should also be familiar with at least one service provider with
which JNDI works, such as LDAP. Both the file system service provider and the LDAP
service provider are used in the examples in Chapter 5, “Using JNDI.” Table 4.1, which
appears later in this chapter, lists the services that are currently available.

Table 4.1: Service Providers

Service Provider Description

LDAP The LDAP service provider currently supports versions 2 and 3 of


the LDAP protocol. For more information on the LDAP protocol,
see www.umich.edu/~dirsvcs/ldap/.

NIS The Network Information Service (NIS) service provider allows


access to UNIX machines running NIS.

NIS+ NIS+, an enhanced version of NIS, provides better security,


scalability, and dynamic updates.

COS Naming COS Naming provides access to Common Object Request Broker
Architecture (CORBA) naming services through the standard JNDI
interface.

File System The File System service provider uses java.io.File to represent files
in your file system. Using this provider, you can access your file
system in a platform-independent way and even store object
references in your file system.

RMI Registry The RMI Registry allows you to use JNDI to find objects in a
Remote Object Invocation (RMI) Registry.

SLP The Service Location Protocol (SLP) provides a dynamic


framework for selection of network services.

Novell Novell provides access to NetWare 3X’s Bindery, the Novell File

- 70 -
Systems, and other Novell services such as Extended NCP.

WebLogic JNDI The WebLogic JNDI service provider interface is a naming service
for Java application server services, including RMI, JDBC, EJB,
and so on. WebLogic JNDI includes toolkits for building custom
naming and directory providers.

Naming Service
A naming service provides a method for mapping unique identifiers, or names with a
specific value, a range of values, or an object that is referenced by the name service. For
example, Common Object Request Broker Architecture (CORBA) allows you to associate
a name with an object so that you can remotely access the object by requesting it by
name. When you save a file on your computer, you name the file so that you can refer to
it and access it later.

The following terms are important to understand when using a naming service: binding,
namespace, compound name, composite name, and service providers. These terms are
discussed in more detail in the following sections.

Binding

The mapping between a name and a unique object is referred to as a name binding. One
obvious computer naming service that performs binding is the file system on your
computer. Each file and directory has a name that identifies it. When you create a file,
you give it a name. This is the name binding for your data; it can be retrieved by that
name later. When you rename a file, you are actually rebinding the file to a new name.

Namespace

A namespace is a set of names in which all names are unique. If you think of a directory
on your computer as a namespace, you realize each file within the directory must have a
unique name. However, you can have two files with the same name if each is in a
different directory, or namespace. With multiple namespaces, you can reuse names
without conflicts.

Compound Name

A compound name is a sequence of names that conform to the naming convention of a


namespace. In a file system, you may have a compound name such as /usr/tmp/
myfile.txt./, usr, tmp, and mysfile.txt—these are all names of objects in the file system.
The symbol, or name /, maps to the root directory; the name usr maps to a subdirectory
under /; and the name tmp maps to the subdirectory called tmp under usr. The name
myfile.txt maps to a file. Together they create a compound name that is, for example,
different from /usr/local/myfile.txt.

Composite Name

Composite names span multiple namespaces. Composite names are common on the
World Wide Web. A URL usually consists of at least three parts: protocol name, server
name, and resource name. For example, in the URL http://www.wiley.com/index.html, the
protocol name is http; the name of the server is //www.wiley.com; and the name of the
resource is index.html. Ultimately, it is hoped that JNDI will support highly composite
names. For example, you might use the first part of a name as a URL to find an LDAP
server, use the next part of the name to find a resource on the LDAP server, and use the
results of the LDAP query and the final part of the name to find an object using RMI.

- 71 -
Service Providers

Although JNDI provides a common, unified API for name services, each name service
requires a service provider that maps JNDI into the specific operations supported by the
name service. Name services differ in description of names, organization of the name
space, schema description (if any), and the list of operations supported by inserting and
searching for objects and values in the service. This mapping of an API to a service
provider’s code is similar to the mapping between the JDBC interfaces and driver
implementations discussed in Chapter 2, “What Is JDBC?” and Chapter 3, “Basic JDBC
Programming.”

DNS and RMI are good examples of naming services because, although they each
organize information differently, they still use unique names in a namespace to identify
their objects. A list of currently available service providers appears in Table 4.1.

DNS

The Domain Name System (DNS) is an Internet naming service that maps the Internet
address of computer systems, such as 192.42.172.21, with simple names like pri.com.
These names allow users direct access to remote computer systems through
recognizable names.

RMI

Remote Method Invocation (RMI) is an example of a distributed object management


system that provides a naming service for mapping names to objects residing on remote
computers. The client could request the service of an object in another address space
simply by referring to it by name. For example, your application could request a printing
object from an RMI repository to print a specific type of report. The code might look like
this:

Printer p = (Printer)rmiService.lookup("Report_Printer");
p.print(myReportObject);

Naming services provide a good mechanism for mapping objects to identifiable names.
There are many mechanisms for organizing and storing these named objects, such as
LDAP or RMI or even your computer’s file system. JNDI provides a consistent API for
accessing all of these types of naming services. JNDI also allows you to manipulate and
traverse the organization of more complex services known as directory services.

Directory Services
Directory services are types of naming services that provide structure to some set of
objects or data values. You can think of a phone book as a sort of directory service that
organizes collections of names, phone numbers, and addresses based on region, city,
state, or country. Typically, the structure of a directory service is hierarchical. For
example, most computer file systems start with a root directory and have a series of
subdirectories, each containing files. Although this directory structure is common, other
attributes of the resources within a directory service namespace may vary greatly. One
directory service may be capable of setting information such as the file access
permissions or the modified date, while other directory services, such as LDAP, allow the
definition of arbitrary attributes on a resource.

Each node or directory structure can be considered the root of the directory tree below it,
hierarchically. Clients of the directory set their focus on the specific subdirectory they are
interested in accessing. An example of this would be the act of changing directories in
your file system and then accessing a file. If you wanted to access the file
/usr/tmp/myFile.txt, you could change directories to /usr/tmp and then access the file by
referring to it by its name, myFile.txt. Otherwise, if you were at the root directory, you

- 72 -
would have to refer to the file by the name /usr/tmp/myFile.txt. Like files in the file system,
contents of directories are referred to by name, relative to the current directory. This
current directory is called a context.

Context

Each node of a directory structure can be referred to as a context. Directory services


allow you to read and modify attributes attached to contexts and have the ability to
search a context using those attributes as a filter. For example, you could search an
LDAP directory service for all people working in the marketing department named Joe,
where Joe is the value of the name attribute of the person you are searching for. In this
structure, you could have people listed in your company directory that have the names of
their assistants associated with their listing, while others do not even have an assistants
attribute.

Service Providers

Each directory service requires a service provider that maps from JNDI into the specific
operations supported by the directory service. Directory services may vary greatly in how
they organize information, search directories, and modify attributes. However, JNDI
provides a consistent interface, so your Java program can retrieve and modify values in a
directory with a single API. One of the most prominent directory services that you will
want to access in your enterprise will be an LDAP server. This service is discussed in
Chapter 5, “Using JNDI,” to show how to use the directory features of JNDI. A list of
currently available service providers is presented in Table 4.1.

Note For the most recent list of service providers, check the URL
http://java.sun.com/products/jndi/serviceproviders.html.

LDAP

Lightweight Directory Access Protocol (LDAP) directory service provides a lightweight


version of X.500 directory service that can run on the TCP/IP protocol stack. This protocol
is becoming popular and is a common service integrated in products such as Netscape’s
Enterprise Server. The protocol allows networked users access to information and
resources in a consistent fashion by providing a simple searching facility that allows you to
search and modify items based on their position in the directory hierarchy, referred to as a
context. For example, you could search LDAP for all the people in the marketing
department that have a fax number in the 415 area code. Once you located them, you
could change their area code to 650. The search could ignore anyone that did not have a
fax number attribute associated with their information. The attributes associated with a
context are variable, which provides flexibility but does require extra discipline by
administrators to adhere to a consistent naming structure. In other words, LDAP does
define some standard in how to organize information, but it is fairly free-form, so the
administrator must take care when naming attributes and deciding where attributes belong
in the directory. For example, one person could have an attribute called telephone while
another person could have an attribute called phone. This is legal in LDAP but would make
it difficult to search for people by their telephone numbers, because you would need to
search for the existence of either attribute.

Goals for JNDI


Understanding the design goals that the architects of JNDI had in mind may help you
understand and appreciate how the JNDI designers intended JNDI to be used. This
section is based on the JNDI specification from Sun Microsystems and provides a brief
insight into these goals.

Keep it consistent and intuitive. One of the design goals for JNDI was to make sure
the API made sense. That means that they tried to use classes and methods that added
the maximum benefit and didn’t overlap too much with services of other APIs. Great effort

- 73 -
was taken to avoid duplicating the capabilities of objects in the Java class libraries. By
keeping the number of classes to a minimum and leveraging other object classes, the
design of JNDI could be made simple yet powerful. Even within JNDI, this principle was
followed. For example, the API design for the directory service functionality is simply an
extension of the more subset functionality of the naming service API. In this way, the
directory service part of the API is easy to learn and use, once you understand the
fundamentals of the naming portion of the API.

Pay for what you use. The term “pay for what you use” means that the designers didn’t
want you to have to learn or use more of the features and API than you needed to do the
task at hand. This reduces your learning curve and reduces the overall size and
complexity of your program. For example, if you want to look up a file on your computer,
you need to use only a few classes and interfaces from the javax.naming package. You
don’t need to look at the javax.naming.directory package at all to get your job done.

Implementable for common directory and naming service protocols. Obviously, it is


important that a standard interface for accessing naming and directory services support
the common protocols. However, the designers are making a more significant
commitment to keep the API as implementation independent as possible. This means
you should not find too many methods that don’t apply to your service provider. In other
words, the API provides just what you would expect for most services. At the same time,
you can still leverage the unique features of a particular service by adding your own
functionality or by using a service provider that extends the capabilities of JNDI to access
these unique features.

Enable seamless and dynamic services. The goal to enable directory services to
seamlessly plug in behind JNDI greatly benefits the programmer using JNDI. By designing
JNDI with a dynamic interface for accessing services, you can offload, until run time,
choices about which protocol or technique to use for accessing your service. This gives
your application more flexibility and lowers the cost of maintaining and enhancing your
application over time. For example, today you could access files on your local machine
and, with very few or no code changes, access a remote file via some combination of
LDAP, RMI, and so on. The ability to seamlessly modify the source of the data without
changing your application provides great flexibility and better long-term maintenance of
your code, which results in lower total cost.

JNDI Architecture
The JNDI API is a Java extension API and is contained in the packages javax.naming,
javax.naming.directory, and javax.naming.spi.

• The package javax.naming is the basic API used typically to look up objects and
values in a naming service, such as an RMI object registry.

• The package javax.naming.directory contains a slightly more sophisticated API used


for filtered searching and modifying of hierarchical values in directory and naming
services such as LDAP.

• The package javax.naming.spi provides a set of interfaces and objects used by service
provider developers. Most enterprise developers will not need to use this API, because
it is used for mapping JNDI calls to a specific service provider. Typically, you will use
the higher-level calls in the javax.naming and javax.naming.directory packages.

Clients of a service provider should be familiar with the JNDI interface. Clients interested
in writing a service object class should be familiar with the service provider interfaces and
classes in the javax.naming.spi package. Service providers create libraries using the
javax.naming.spi package, which contains classes and interfaces that define the behavior
of a JNDI service provider. Figure 4.1 illustrates the JNDI architecture.

- 74 -
Figure 4.1: JNDI architecture.

Note Because most enterprise Java programmers use services rather than creating
their own providers, this book does not cover the javax.naming.spi.

Most application developers will use just the classes and interfaces defined in the
javax.naming and javax.naming.directory pages, as shown in Figure 4.1. Refer to Table
4.1 for the available service providers.

Note Service providers exist for many of the major directory and naming services. If you
can’t find a provider for a service you want to access, you can use SPI to create a
JNDI solution that integrates seamlessly with other naming and directory services.

Summary
JNDI enables all of the various resources in an enterprise, including files, printers, servers,
security services, databases, and business processes, to work together. Most directory
services have the ability to manage a set of resources. JNDI allows you to leverage the
strengths of many services. Because of JNDI’s design, you need to learn a fairly small API
that allows your Java application access to a great variety of enterprise resources. The
next chapter explores APIs and looks at code examples for using specific naming and
directory services.

Chapter 5: Using JNDI


Overview
This chapter covers the basic use of JNDI. The goal of this chapter is enable you to set
up and create directory and naming service connections to access contexts via JNDI.
This chapter discusses JNDI 1.1. For information on future versions of JNDI, browse the
Sun Web site at http://java.sun.com/products/jndi.

JNDI provides a consistent model for accessing and manipulating enterprise resources
such as the file system, CORBA, a directory service, or an application server, through

- 75 -
Java. JNDI provides the additional benefit of allowing you to bind a Java object into these
services. Figure 5.1 illustrates the JNDI architecture. At the top of the JNDI architecture is
the Java application that wants to use JNDI. This application uses the javax.naming and
javax.naming.directory packages to access JNDI services. The two client packages use
the NamingManager, or DirectoryManager, to create the appropriate JNDI context for an
application. A context represents a connection to a JNDI service provider. The code for a
context is written by a JNDI service provider and implements the appropriate
javax.naming.spi interfaces as well as the required interfaces from the client API.
Ultimately, a service provider’s implementation talks to a data source, like LDAP, the file
system or the RMI registry. Most of these connections are hidden from the user behind a
set of properties used to create the context.

Figure 5.1: JNDI architecture.

This chapter focuses on the services available in the javax.naming and the
javax.naming.directory packages. The service provider package is not discussed in detail
because it will be used only by the few programmers creating JNDI service providers.

Setup Requirements
In order to program with JNDI, you should have the following libraries accessible from
your computer:

• JDK 1.1.2 or higher.

• JNDI package—your CLASSPATH should include jndi.jar; otherwise, the path to


jndi.jar must be included on the command line for each example in this chapter.

• A service provider package for each service you access through JNDI.

You will need additional resources such as an LDAP server to use some of the later
examples in this chapter. The CD-ROM contains the Java components you need to run
the examples in this chapter; however, the final versions of the two service providers we
have used were not released at publication time. To find a list of available providers and
download the latest implementations provided by Sun, browse http://java.sun
.com/products/jndi/serviceproviders.html. In addition, an LDAP configuration file is used
with this chapter. You don’t have to use this configuration file. Using another
configuration won’t affect the effectiveness of the examples, but it will produce results
that differ from the examples discussed in this chapter.

If you intend to use JNDI with other directory and naming services such as CORBA, you
will require additional service provider packages. The service provider is created by
implementing the interfaces in the javax.naming.spi package for the specific naming
service. In order to use any provider, you must put it in your CLASSPATH. For example,
to use the file system service provider, you must have fscontext.jar in your CLASSPATH.
The jar fscontext.jar contains the implementation of the javax.naming.spi specifically for
accessing the file system.

- 76 -
About the Sample Service Provider
The service provider used for most of the examples in this chapter is the file system
provider called FsContext. This provider allows you to access your file system as a set of
file system context objects that represent directories and as file objects that represent the
files in those directories. Because files are represented with java.io.File, this provider is
platform independent and can be used to access the directory structure on any platform
on which the Java application runs.

FsContext provides naming services and associated names, file paths, with File objects.
After the chapter explores the basic features of the naming services, it examines some of
the advanced features of JNDI implemented by directory services, such as attribute
manipulation and attribute queries. These features require the use of another provider.
We chose the LDAP service provider. If you do not have LDAP, you should be able to
follow along with the examples presented here and get an understanding of how directory
services can be used.

Note If you do not have access to an LDAP server, you can download one from the
University of Michigan Web site at www.umich.edu/~dirsvcs/ldap/ ldap.html.

The goal of this chapter is to provide an overview of JNDI. Because the JNDI interface is
consistent across all service providers, little time is spent on LDAP specific issues. Instead,
the focus is on general use of JNDI. If you do have LDAP and choose to load the example
configuration from your CD-ROM, you should have schema checking turned off for some of
the later examples. We recommend turning it off because the schema we built is not
necessarily self-consistent; it was designed for educational value and not production use.

The Naming Package


The naming package provides a set of classes and interfaces that are used to access
any naming service for which you have a provider. Once you have a provider, you can
perform the following functions using the naming package:

• Access a context, including listing and searching

• Rename, move, add, replace, or remove a binding for an object

• Store references to Java objects

This section steps through the components of the javax.naming package necessary to
perform the procedures listed above.

Access a Context
The javax.naming package defines the Context interface. A context represents a starting
point for a naming or directory service. The file system context in the examples for this
chapter uses the set of files and directories stored on the local file system as a context.
Figure 5.2 shows the directory structure used for the examples in this chapter. Because
this structure represents the context in which the following naming examples are written,
you should familiarize yourself with it. The CD-ROM contains a script to build the sample
directory structure if you want to run the provided examples.

- 77 -
Figure 5.2: Example directory structure.

Once created, an object implementing the Context interface represents the root for all
naming operations performed. The initial context can be thought of as the root for your
file path; however, it is not always the same as the root directory in your file system. For
example, it could be a subdirectory in your file system. Figure 5.3 illustrates how the
marketing directory could be the root for a context.

Figure 5.3: Setting a context.

To create a Context object, perform the following steps:

1. Set up the environment. JNDI uses a set of environment variables to tell it which
service providers to access and how to initialize them. If you don’t set one of these
values, the System properties are used. Table 5.1 lists these properties. The simplest
way to assign the environment is to load a property file as described for JDBC in
Chapter 3, “Basic JDBC Programming,” or create a hash table containing the
environment settings. There are many properties that can be used to create a new
context; study Table 5.1 carefully. However, as demonstrated in the examples in this
chapter, only a few of these properties are required. In particular, the initial factory
and provider URL will normally be required to create a context.

Table 5.1: Environment Variables for JNDI

Environment Properties Description Constant Defined

Program Configuration

java.naming.factory.initial Class name of initial Context.INITIAL_CONTEXT_

- 78 -
context factory to use. FACTORY
When unspecified,
determined by the
java.naming.factory.initial
system property.

java.naming.factory.object Colon-separated list of Context.OBJECT_FACTORIES


class names of object
factory classes to use.
When unspecified,
determined by the
java.naming.factory.object
system property.

java.naming.factory.url.pkgs Colon-separated list of Context.URL_PKG_PREFIXES


package prefixes to use
when loading in URL
context factories. When
unspecified, determined by
the
java.naming.factory.url.pkgs
system property.

Access Configuration

java.naming.provider.url Specifies configuration Context.PROVIDER_URL7


information for provider
to use. When
unspecified, determined
by the
java.naming.provider.url
system property. When
system property is
unspecified, the URL is
determined by the
provider using its own
configuration.

java.naming.dns.url Specifies the DNS Context.DNS_URL


host and domain
names to use for the
JNDI URL context.
When unspecified it
is determined by the
java.naming.dns.url
system property.

Service-Related

java.naming.authoritative Specifies the Context.AUTHORITATIVE


authoritativeness of
the service
requested. True
specifies that the
most authoritative
source is to be used.

- 79 -
False specifies that
the source doesn’t
need to be
authoritative. When
unspecified, defaults
to false.

java.naming.batchsize Specifies the Context.BATCHSIZE


preferred batch size
to use when
returning data via
the service’s
protocol. This is a
hint to the provider
to return the results
of operations in
batches of the
specified size so the
provider can
optimize its
performance. When
unspecified,
determined by
provider.

java.naming.referral Specifies that Context.REFERRAL


referrals
encountered by the
service provider are
to be followed
automatically. If
“follow,” follow
referrals
automatically. If
“ignore,” ignore
referrals
encountered. If
“throw,” throw
ReferralException
when a referral is
encountered. When
unspecified,
determined by
provider.

Security

java.naming.security.protocol Security protocol Context.SECURITY_PROTOCOL


to use for
service. When
unspecified,
determined by
provider.

java.naming.security.authentication Use “none,” Context.SECURITY_


“simple,” “strong,” AUTHENTICATION
or a provider-
specific string.
When

- 80 -
unspecified,
determined by
provider.

java.naming.security.principal Identity of Context.SECURITY_PRINCIPAL


principal for
the
authentication
scheme. When
unspecified,
defaults to the
identity of user
running the
application.

java.naming.security.credentials Principal’s Context.SECURITY_


credentials for the CREDENTIALS
authentication
scheme. When
unspecified,
obtained by the
provider on behalf
of the user. This is
done using the
security system
available to the
provider, such as
passwords or
certificates.

Internationalization

java.naming.language Specifies a colon- Context.LANGUAGE


separated list of
preferred language
to use with this
service, such as “en-
US: ja-JP-kanji”.
Languages are
specified using tags
defined in RFC
1766. When
unspecified, the
language preference
may be determined
by the provider.

2. Create an initial context. JNDI requires that you have an initial context for all
operations. This context is considered the root of your naming operations. For
example, consider the directory structure in Figure 5.2. If an initial context is created
at /, the path to report1.txt is /tmp/marketing/reports/report1.txt. However, if an initial
context is created at /tmp/marketing/, the path is /reports/report1.txt.

The class java.naming.InitialContext implements the Context interface and can be used
to create context objects. The following code defines a method that creates an
InitialContext from command line arguments. The code used to create the context and
assign the configuration properties is in bold. This example sets the user and password

- 81 -
for the context, if one is provided. Later examples in this chapter skip this step to make
them more readable. However, the examples using JNDI with Enterprise JavaBeans rely
on this name and password to gain access to the application server. The values for these
security properties are provider specific. Be sure to read the documentation for your
provider to determine the values appropriate for the security parameters and the effects
of setting these parameters.

static public Context getInitialContext(String[] args) throws


Exception
{
Properties p = new Properties();
String url = "t3://localhost:7001";
String user=null;
String password=null;

if ((args != null) && (args.length > 1))


{
for (int i = 0; i < args.length; i++)
{
if (args[i].equals("-url"))
url = args[++i];
else if (args[i].equals("-user"))
user = args[++i];
else if (args[i].equals("-password"))
password = args[++i];
else if (args[i].equals("-start"))
start = Long.parseLong(args[++i]);
else if (args[i].equals("-max"))
max = Integer.parseInt(args[++i]);
}
}

p.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.T3InitialContextFactory");

p.put(Context.PROVIDER_URL, url);

if (user != null)
{
p.put(Context.SECURITY_PRINCIPAL, user);

if (password == null) password = "";

p.put(Context.SECURITY_CREDENTIALS, password);
}

return new InitialContext(p);


}

Once a context has been defined, it can be accessed using methods defined in the
Context interface. These methods allow you to perform several operations, including
listing the contents of a context or searching for a specific entry in the context.

List and Search a Context

- 82 -
The Context interface defines several methods for searching for objects in a naming or
directory service. Table 5.2 lists the searching methods provided by a context. These
methods allow you to retrieve any type of object located by a naming service, provided
the provider can access it. The fsContext provider locates files and maps them to
java.io.File. It locates directories and maps them to contexts that can, in turn, be
accessed.

Table 5.2: Search Methods of Context

Method Use

list(Name), Returns a NamingEnumeration of the names and the


class

list(String) names of their bound objects in the named context.

ListBindings(Name), Returns a NamingEnumeration of the names and their

ListBindings(String) bound objects in the named context.

lookup(Name), Retrieves the named object.


lookup(String)

LookupLink(Name), Retrieves the named object, following links except for


LookupLink(String) the terminal atomic component of name.

Every method in the Context interface that requires a name as an argument has a
version that accepts a String name argument and a version that accepts a Name object.
Name is an interface that represents an ordered sequence of zero or more components.
Name is used by a context to represent a composite name so that you can name an
object with a name that spans multiple namespaces. If you intend to manipulate names
or deal with multiple namespaces, this ability is a convenient way of referencing multiple
namespaces in a single object. For simple operations with services such as the file
system, strings are just as useful.

For example, given a context, ctx, for the marketing department files, you can view the
available reports to print by specifying:

NamingEnumeration ne = ctx.list("Reports");

where the result of calling list is a javax.naming.NamingEnumeration containing the


objects in the Reports context.

NamingEnumeration extends Enumeration to allow exceptions to be thrown after the


enumeration. By calling the NamingEnumeration’s hasMore() method instead of the
Enumeration method hasMoreElements(), the NamingEnumeration throws an exception
when no more elements are available. This allows you to handle exceptions on a list.
Suppose you limited the size of a search to 10 elements. If the search returned 11
elements, the exception SizeLimitExceedException would be thrown when you tried to
access the 11element. The method hasMoreElements() would not throw an exception; it
would just return false. A complete list of naming exceptions is shown in Table 5.16 at the

- 83 -
end of this chapter.

As you can see in Table 5.2, two mechanisms are provided for searching a context:
listing and lookups. Listing returns a list of the items in the context named in the methods
argument. In the previous example, the call to list returned all of the objects in the
Reports context. The lookup methods return a single object, with the provided name, in
the current context.

Listing can be accomplished with one of two methods: list() and listBindings(). The
method list() returns a NamingEnumeration containing a list of names and the class
names they are bound to. The method listBindings() returns a NamingEnumeration
containing a list of names and the objects they are bound to. In each case, you are
expected to pass in the name of the context relative to the initial context to search. If you
pass in “”, the initial context is listed. Passing in null causes a NullPointerException.

When you perform a list operation on a service, the returned NamingEnumeration


consists of NameClassPair instances. Table 5.3 lists the methods of this class.
NameClassPair contains an object’s name and the name of the object’s class. Use this
information to process the results of a lookup. This is especially handy if you are writing a
directory browser because it allows you to get the listing of the names of the files without
actually loading all of the objects in the directory.

Table 5.3: NameClassPair Methods

Method Use

NameClassPair(String name, This constructor creates an instance of a


String className) NameClassPair and sets the name and
class name associated with it.

NameClassPair(String name, This constructor creates an instance of a


String className, boolean NameClassPair and sets the name and
isRelative) class name associated with it. If isRelative
is set to true, the name is set relative to the
target context of the listing. If isRelative is
set to false, the name is a complete URL
string.

String getClassName() Returns the class name of the object


bound to this NameClassPair instance.

String getName() Returns the name of this binding.

boolean isRelative() Returns true if the name of this binding is


relative to the target context.

void setClassName(String name) Sets the class name for this binding.

void setName(String name) Sets the name associated with this


binding.

void setRelative(boolean r) Sets whether the name of this binding


should be considered relative to the target
context.

- 84 -
The listBindings method returns an enumeration of objects of the Binding class. Table 5.4
lists the methods of this object. Binding extends NameClassPair and represents a name
to object binding. Although listBindings() provides the same information, it is potentially a
much more expensive operation because it actually retrieves the underlying objects. Use
list if you only need the name of the object.

Table 5.4: Binding Methods

Method Use

Binding(String name, Object This constructor creates an instance of a


obj) binding with specific non-null name relative
to the target context and the bound object,
which can be null.

Binding(String name, Object This constructor creates an instance of a


obj, boolean isRelative) binding with specific non-null name and the
bound object, which can be null. If
isRelative is set to true, name is
considered to be relative to the target
context. If isRelative is false, name is
considered to be a complete URL.

Binding(String name, String This constructor creates an instance of a


className, Object obj) binding with specific non-null name relative
to the target context. If className, the
class name of object (obj, the possibly null
object bound to name) is returned using
getClassName(). If obj is also null,
getClassName() returns null.

Binding(String name, String This constructor creates an instance of a


className, Object obj, boolean binding with specific non-null name. If
isRelative) className, the class name of object (obj,
the possibly null object bound to name) is
returned using getClassName(). If obj is
also null, getClassName() returns null. If
isRelative is set to true, name is
considered to be relative to the target
context. If isRelative is false, name is
considered to be a complete URL.

String getClassName() Returns the class name of the object


bound to the name of this binding.

Object getObject() Returns the actual object bound to the


name of this binding.

Void setObject(Object obj) Sets the object associated with this binding
to obj.

- 85 -
The code listing that follows for a class List demonstrates how these methods work. This
example program uses an initial context of /tmp/marketing and lists the contents of the
context and the bindings for each object in the context. The JNDI elements of this
example are in bold.

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.*;

// example use: java List

public class List


{
public static void main(String[] args)
{
// Create a list of environment settings for our example.
Hashtable env = new Hashtable();

/*
By specifying we are using refFSContextFactory, we will
have the ability to access the file system and lookup
and
store objects as well.
*/
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");

// Specify the file system to search in this example.


env.put(Context.PROVIDER_URL,"file:/tmp/marketing");

try
{
// Place holder for items in a context.
Object item = null;

// Based on our environment, set an initial context.


Context initCtx = new InitialContext(env);
NamingEnumeration nl = initCtx.list("reports");

System.out.println("*** Printing context list ***");


if (nl == null)
System.out.println("\ nNo items in name list");
else
while (nl.hasMore())
{
item=nl.next();
System.out.println("item's class is " +
item.getClass().getName());
System.out.println(item);
System.out.println("");

- 86 -
}

System.out.println("*** Printing context" +


" list bindings ***");

nl = initCtx.listBindings("reports");
if (nl == null)
System.out.println("\ nNo items in name list");
else
while (nl.hasMore())
{
item=nl.next();
System.out.println("item's class is " +
item.getClass().getName());
System.out.println(item);
System.out.println("");
}
} catch(Exception e)
{
System.out.println(e);
e.printStackTrace(System.out);
}
}
}

Figure 5.4 shows what would so far be returned from our example, assuming the
previous code above was executed.

Figure 5.4: Results of calling list() and listBindings().

In addition to accessing a list of objects in a context, you can also access a specific
object by using the lookup or lookupLink methods. These methods look for a specific
object in the current context and return the object bound to the name you provide. Here is
the code for an example, Print, that uses the lookup operation to retrieve the file
report1.txt based on the directory structure in Figure 5.2 and displays the file.

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.*;

- 87 -
// example use: java Print

public class Print


{
public static void main(String[] args)
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");

env.put(Context.PROVIDER_URL,"file:/tmp/marketing");

try
{
Context initCtx = new InitialContext(env);

// We assume we are reading in a file.


File f =(File)initCtx.lookup("reports/report1.txt");

//Print the file located


if(f != null)
{
BufferedReader br =
new BufferedReader(new FileReader(f));
String l = null;
while((l = br.readLine()) != null)
System.out.println(l);
}
}
catch(Exception e)
{
System.out.println(e);
e.printStackTrace(System.out);
}
}
}

The bold code above performs the lookup on the current context looking for
reports/report1.txt. If the context had been / instead of /tmp/marketing, a lookup would
have to explicitly be set to tmp/marketing/reports/report1.txt to perform the same lookup
operation. All of these searching operations rely on the concept of object bindings, which
we examine next.

Object Bindings
A binding is the name of an object, the name of the object’s class, and the object itself.
Context provides methods for manipulating these bindings. Table 5.5 lists these methods.

Table 5.5: Binding-Related Methods of Context

- 88 -
Method Use

bind(Name, Object), Binds the name to the object in the context. Throws an
bind(String, Object) exception if the name is already in use.

createSubcontext(Name), Creates and binds a new context to the name.


createSubcontext
(String)

DestroySubcontext Destroys the named context and removes it


(Name),destroySubcontext(String) from the namespace.

rebind(Name, Object), Binds the name to the object, overwriting any existing
rebind(String, Object) binding.

rename(Name oldName, Binds newName to the object bound to oldName and


Name newName), unbinds oldName.
rename(String oldName,
String newName)

unbind(Name), Unbinds the named object from the namespace.


unbind(String)

The examples in this section revolve around the fsContext service provider, but it is
important to keep in mind that these same operations can be used for removing an object
from a CORBA repository or adding personal contact information to an LDAP directory
server. All the functions described in this section are useful with any provider. How that
provider actually implements each function depends on the provider. The documentation
on each provider will tell you how these functions work for that specific provider. For
example, unbind deletes a file when using the file system provider, but with RMI it simply
removes an object reference from the remote registry. Let’s look at the unbind function
first.

Deleting an Object Binding

To delete report2.txt from the marketing reports directory, execute the command “java
Delete report2.txt” to initiate the following code:

import javax.naming.*;
import java.util.Hashtable;

// Deletes files from the marketing directory


// example use: java Delete filename

public class Delete


{
public static void main(String[] args)
{
// Define a starting point for operation.
String initalContextString = "/tmp/marketing/reports";

if(args.length < 1)
{

- 89 -
System.out.println("Usage: java Delete filename");
System.exit(-1);
}
System.out.println("This program assumes the context is "
+
initialContextString);

Hashtable env = new Hashtable();


env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");
env.put(Context.PROVIDER_URL,"file:" +
initalContextString);

try
{
Context initCtx = new InitialContext(env);

// Delete file or other object...


System.out.println("Attempting to unbind " +
args[0]);

initCtx.unbind(args[0]);

System.out.println("Done.");
} catch(NamingException e)
{
System.out.println(e);
e.printStackTrace(System.out);
}
}
}

The Delete program sets an initial context of /tmp/marketing/reports and requests that the
user enter the name of a file to delete in that directory. The unbind method is called to
unbind or delete the selected file. The initial context is set for safety as this program
deletes files; we wouldn’t want you to accidentally delete important files on your
computer.

Note The fsContext provider will not delete directories, it will only delete files
through the unbind method. Other providers may perform differently.

Renaming and Moving an Object

Using the rename method of a context allows you to change the name bound to the
object. In the case of the file system provider, this is equivalent to changing the name of a
file or directory. However, there is one difference: If the path to the named object differs
from the path to the new name, the object is explicitly moved in the context. For example,
if you rename /tmp/marketing to /tmp/products, the directory marketing will be renamed
products. However, if you rename /tmp/marketing/reports/report1.txt to /tmp/report1.txt,
the report1.txt file (object) will be moved into the subcontext /tmp. The program Rename
in the code listed below takes two command line inputs: a file or directory name or path
and the new path name. Try renaming/moving files and directories to get a feel for how
this works.

import javax.naming.*;

- 90 -
import java.util.Hashtable;

// Rename filename1 to filename2


// example use: java Rename filename1 filename2
public class Rename
{
public static void main(String[] args)
{
//This program renames any file you access, so be
careful!
String initialContextString = "/";

if(args.length<2)
{
System.out.println(
"Useage: java Rename filename1 filename2");
System.exit(-1);
}

// Create a list of environment settings for our example.


Hashtable env = new Hashtable();

/*
By specifying we are using refFSContextFactory, we will
have the ability to access the file system and lookup
and store objects as well.
*/
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");

// Specify the file system to search in this example.


env.put(Context.PROVIDER_URL,
"file:" + initialContextString);

try
{
// Based on our environment, set an initial context.
Context initCtx = new InitialContext(env);
System.out.println("Renaming " + args[0] +
" to " + args[1]);
initCtx.rename(args[0],args[1]);
System.out.println("Done.");

} catch(NamingException e)
{
System.out.println(e);
e.printStackTrace(System.out);
}
}
}

The boldface code in the preceding listing renames the object bound to the name
described by args[0]. The new binding for this object may be in a different part of the

- 91 -
directory structure, based on the args[2] parameter. For example, the following code
actually moves the reports directory (context) to /reportdir. This is because the initial
context defined in the Rename program was set to /. If the initial context had been set to
/tmp, reports would have been moved to /tmp/reportdir.

java Rename /tmp/marketing/reports reportdir

One thing to note is that if the initial context had been /tmp instead of / in the example, it
would not have worked. Remember that the file paths are relative to the initial context.
Because there is no file /tmp/tmp/marketing/reports, you would get an exception. Try
various combinations of changing the initial context and the file paths to rename files and
directories and move them relative to the initial context.

Replacing and Adding Objects

Another feature of the context is the ability to add objects. By combining the renaming
feature and the ability to add objects, you can replace an object in a context. The
following code listing describes a program Changes that does this.

import javax.naming.*;
import java.util.Hashtable;

// Rename the report directory and create a new report directory.


// example use: java Changes
public class Changes
{
public static void main(String[] args)
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");

env.put(Context.PROVIDER_URL,"file:/tmp/marketing");

try
{
Context initCtx = new InitialContext(env);

// Rename the reports directory.


initCtx.rename("reports","oldreports");

// Create new reports directory.


initCtx.createSubcontext("reports");
} catch(NamingException e)
{
System.out.println(e);
e.printStackTrace(System.out);
}
}
}

All the operations in this section equate to simple file operations.

Next, let’s look at how to use the binding operations to bind a Java object to the context.
It is important to realize that while the file system has been used as the naming service

- 92 -
for the examples, you can simply replace the service provider and, with little else, perform
the same operations against an LDAP server, an RMI registry, or other service. For
example, given a context for an LDAP server, you can store the report file object from
your computer in LDAP using the following code:

...
ldapCtx.bind("cn=MyFileName", theFile);
//To retrieve the file object from LDAP execute:
File theFile = (File)ldapCtx.lookup("cn=MyFileName");
...

Note cn=MyfileName is a convention of LDAP and has nothing to do with JNDI


specifically.

Because the File class supports serialization, the LDAP service provider serializes the
object into the directory server. However, not all services or service providers support
serializing. For example, the file system service provider used thus far will not serialize
objects. In this case, you can store objects that implement the Referenceable interface
using a class called a reference. A referenceable object is an object that implements the
method public Reference getReference() throws NamingException. This method returns
information describing this particular object precisely enough that it could be
reconstructed later by a factory class. Before exploring a referencing example, let’s look
at what a reference actually does for us.

Storing Java Objects as References


Objects are stored in naming and directory services in different ways. Some services can
store serialized versions of your Java object. In such a case, you could simply serialize
your object and store it for later retrieval. However, some naming and directory services
do not support the storing of Java objects. In addition, other applications using the
service may not be able to read serialized objects. Therefore, there are cases when a
serialized Java object might not be the most appropriate representation of your data.

To handle the cases in which a serialized object cannot be stored, JNDI defines the
reference class. A reference contains information on how to construct a copy of your
object. JNDI attempts to turn references looked up from the directory into the Java
objects they represent. Reference objects provide a way of recording address information
about objects that themselves are not directly bound to the naming and directory service.
Figure 5.5 illustrates the use of references for retrieving an object. Using references, you
can seamlessly store and retrieve objects from your service without focusing on the
representation of the data in a particular directory and naming service.

Figure 5.5: Storing and retrieving a Java object reference.

A reference consists of information to assist in creating an instance of the object to which


this reference refers. This information includes:

- 93 -
• The class name of the object being referenced

• A vector of RefAddr objects representing the addresses

• The string name of the factory for the object and the location of the factory object

A reference contains the class name of the object as well as the class name and location
of a factory object used to reproduce the object. These factories are discussed in detail in
this section. Each address in the list identifies a communications end point for the same
conceptual object. For example, an address could be a URL pointing at the data
necessary to reconstruct the object. The order of the addresses in the list may be of
significance to object factories that interpret the reference. Addresses are stored as
instances of RefAddr.

RefAddr is an abstract class that defines an association between a string type and an
object. This object is called the content. To access the type and content, you can use
these methods:

public String getType()


public Object getContent()

The content is used to store information required to rebuild an object; and the type is
used to identify the purpose of the content. For example, a reference for a Car object
might store the value “red” under the type “color” and the value “Saturn” under the type
“make.” RefAddr also overrides the equals and hash code methods to ensure that two
references are equal if their contents and type are equal.

JNDI defines two concrete subclasses of RefAddr. StringRefAddr stores a string, and
BinaryRefAddr stores an array of bytes. These classes provide constructors to create the
reference by specifying the content and type as arguments.

References are not synchronized. Threads that need to access a single reference
concurrently should synchronize and provide locking.

In the case of the file system service provider, the object references are stored for a
specified context in a file named .bindings. In the following example, a set of Car objects
are created and bound to the file system. The main class, Bind.java, creates an initial
context is set to the presentations subdirectory. Then several Car objects are
instantiated, and they are bound into the file system by calling Context’s rebind method.
By using rebind, we can run the program repeatedly, replacing existing bindings with new
ones. If the bind method was used and the example ran twice, an exception would be
thrown. Finally, the car belonging to Cheryl is located using the lookup method of
Context. The following code defines the Bind class.

import javax.naming.*;
import java.util.Hashtable;
import javax.naming.spi.ObjectFactory;

public class Bind


{
public static void main(String[] args)
{
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.fscontext.RefFSContextFactory");

// Specify the filesystem to search in this example.

- 94 -
env.put(Context.PROVIDER_URL,
"file:/tmp/marketing/presentations/");

try
{
Context initCtx = new InitialContext(env);
/*
Bind each customer into the file system.
Use rebind in case they are already there.
This way, earlier entries will be overwritten.
Otherwise, if we used bind(), an exception would
be thrown if we run this twice.
*/
initCtx.rebind("Susan", new Car("Toyota","Camry"));
initCtx.rebind("Cheryl", new Car("Saturn","Coupe"));
initCtx.rebind("Nicole", new Car("Ford","Bronco"));

Car c = (Car)initCtx.lookup("Cheryl");
System.out.println(c);
} catch(Exception e)
{
System.out.println(e);
e.printStackTrace(System.out);
}
}
}
Here is what the /tmp/marketing/presentations/.bindings file
contains after running the Bind program:
#This file is used by the JNDI FSContext.
#Sun Oct 11 19:50:11 PDT 1998
Susan/ClassName=Car
Nicole/RefAddr/0/Type=Car Description
Susan/RefAddr/0/Type=Car Description
Nicole/RefAddr/0/Encoding=String
Nicole/ClassName=Car
Cheryl/RefAddr/0/Type=Car Description
Nicole/FactoryName=CarFactory
Susan/FactoryName=CarFactory
Cheryl/ClassName=Car
Nicole/RefAddr/0/Content=Ford:Bronco
Cheryl/FactoryName=CarFactory
Cheryl/RefAddr/0/Content=Saturn:Coupe
Susan/RefAddr/0/Encoding=String
Susan/RefAddr/0/Content=Toyota:Camry
Cheryl/RefAddr/0/Encoding=String

The information in this file is generated by the service provider and tells the provider what
object mappings have been made for the subcontext, presentations. This .bindings file is
FSContext specific, but it shows how the context can store reference information for the
caller.

The following code example is for the Car object. Notice that the class must implement
the Referenceable interface, meaning it must implement a method called getReference(),
which returns a Reference object. The Reference object should contain the name of the

- 95 -
class, a string representation for reconstructing the object, the name of the factory class
that reconstructs the object, and, if necessary, the location of the factory object. In this
case, the make and model of the car are used to identify it. This information is stored in a
StringRefAddr under the type “Car Description.” If the Car class was used to define a
remote object, you may want to include the server name where the object resides.

import javax.naming.*;

/*
This class is referenceable class that can be stored by
service
providers like the file system provider.
*/
public class Car implements Referenceable
{
String make;
String model;

public Car(String mk, String md)


{
make = mk;
model = md;
}

public Reference getReference() throws NamingException


{
String cName = Car.class.getName();
StringRefAddr cRef = new StringRefAddr("Car Description",
make + ":" + model);
String cfName = CarFactory.class.getName();
Reference ref = new Reference(cName, cRef, cfName, null);
return ref;
}

public String toString()


{
return (make+" "+model);
}
}

When the Car object is bound, the service provider stores the reference values in the
appropriate format for the service. Calling lookup causes the service provider to load the
reference bound to the lookup name and use it to access the factory class responsible for
reconstructing the object from the information provided by the reference. The CarFactory
class code that follows implements the ObjectFactory interface, which is defined in
javax.naming.spi. This interface includes the getObjectInstance method, which returns a
newly constructed object based on the Reference object, obj. If a new object can’t be
reconstructed based on the parameters passed in, the factory is supposed to return null.

import java.util.Hashtable;
import javax.naming.*;
import javax.naming.spi.ObjectFactory;

/*
This is an object factory that when given a reference for a

- 96 -
Car
object, will create an instance of that Car.
*/
public class CarFactory implements ObjectFactory
{
public CarFactory()
{
}

public Object getObjectInstance(Object obj, Name name,


Context ctx, Hashtable env) throws Exception
{
if (obj instanceof Reference)
{
Reference ref = (Reference)obj;
if (ref.getClassName().equals(Car.class.getName()))
{
RefAddr addr = ref.get("Car Description");
if (addr != null)
{
String s = (String)addr.getContent();
int n = s.indexOf(":");
String make = s.substring(0,n);
String model = s.substring(n+1);
return new Car(make,model);
}
}
}
return null;
}
}

The method getObjectInstance parses the reference’s components and tries to construct
a reference that describes a Car object. The factory may ignore non-relevant attributes of
the reference. For example, there could be an IP address of a remote Car object; this
factory could ignore it and just create a new Car instead. If the factory fails to construct a
car from the information in the reference, it can return null or throw an exception. The
values of a reference object are interpreted by the factory object, so it is possible to have
two different factory objects that construct completely different objects based on the
same reference. The reference could be valid in both cases. For example, the car factory
creates a simple Car object and returns it based on the make and model. However, the
same reference could contain other information about the car such as the plant in which it
is physically being built as well as the order id. A second factory class could read this
extra information out of the reference and create an object that checks on the status of
the car at the assembly plant.

The naming package provides quick access to objects provided by naming services. The
next section looks at how directory packages can be used to perform more sophisticated
queries.

The Directory Package


The directory package javax.naming.directory extends the javax.naming package to
provide functionality for accessing directory services in addition to naming services. This

- 97 -
package allows applications to do the following:

• Search a directory for objects based on specific attributes or values

• Control and optimize the search results

• Modify attributes of an object

The directory package is an extension to the naming package and leverages the
functionality of the naming context operations. The directory package extends this
functionality by providing more sophisticated searching and modification facilities that
reflect the nature of directory services and tend to be more complex than a simple
naming service. However, all of the searching methods described for naming services
also apply to directory services.

Search for Objects


In order to search for objects in a directory, first create an object that implements the
javax.naming.directory.DirContext interface, such as the class InitialDirContext. Once the
context has been initialized, the methods listed in Table 5.6 can be used to execute
several types of searches. DirContext extends the Context interface. This means any
directory object can also be treated as a naming context.

Table 5.6: DirContext Searching Methods That Return NamingEnumeration

Method Use

search(String name, Attributes Searches a context for objects that contain


attrs) or search(Name name, a set of attributes.
Attributes attrs)

search(String name, Attributes Searches a context for objects that contain


attrs, String[] returnAttrs) or a set of attributes and retrieves the
search(Name name, Attributes attributes specified in String[].
attrs, String[] returnAttrs)

search(String name, String Searches in the named context for entries


filter, SearchControls sc) or that satisfy the search filter.
search(Name name, String
filter, SearchControls sc)

search(String name, String Searches in the named context for entries


filter, Object[] filterArgs, that satisfy the search filter.
SearchControls sc) or
search(Name name, String
filter, Object[] filterArgs,
SearchControls sc)

There are several techniques for searching using the methods of DirContext. The LDAP
example configuration and the LDAP service provider are used for the examples in this
chapter.

- 98 -
The code snippet that follows Table 5.6 defines a class DirectoryExample that accesses
an LDAP directory server. The provider URL references an LDAP server by using the
ldap: protocol. The host, MyHost, should be replaced with the name of your LDAP server.
The example assumes that there is a context called JNDIExample in your directory
server.

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;

public class DirectoryExample


{
public static void main(String[] argc)
{
Hashtable env = new Hashtable(11);

env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");
try
{
DirContext dctx = new InitialDirContext(env);
...

The DirContext interface defines methods for examining and updating attributes
associated with an object in the directory context. The rest of this section describes these
search techniques in more detail.

Searching with Attributes

The simplest technique for searching with DirContext is to search for all objects that
contain a certain set of attributes. To do this:

1. Set the environment properties as you did for the Naming context.

2. Create a DirContext as demonstrated in the beginning of the “Search for Objects”


section.

3. Specify the attributes you are looking for by defining javax.naming.directory .Attribute
objects and storing them in a javax.naming.directory.Attributes array.

4. Execute the search by calling the search method of DirContext.

5. Process the results returned in a NamingEnumeration.

The directory package encapsulates an object’s values in a


javax.naming.directory.Attributes object. The javax.naming.directory.BasicAttributes class
provides an implementation of the Attributes interface. Table 5.7 lists the methods
defined in the Attributes interface.

Table 5.7: Attributes Interface Methods

- 99 -
Method Use

Object clone() Returns a copy of Attributes. The new Attributes object


contains the same attributes as the original set. However,
the attributes themselves are not cloned.

Attribute get Returns the attribute with the given attribute id from the
(String id) attribute set.

NamingEnumeration Returns an enumeration of all attributes in the attribute


getAll() set.

NamingEnumeration Returns an enumeration of the ids of all attributes in the


getIDs() attribute set.

boolean Returns true if the attribute set ignores the case of


isCaseIgnored() attribute identifiers when retrieving or adding attributes.

Attribute Adds a new non-null attribute to the attribute set and


put(Attribute attr) returns the Attribute that was stored in the Attributes set
with the same id. If there was no other object with the id of
this Attribute, null is returned.

Attribute put Adds a new non-null attribute to the attribute set and sets
(String id, Object the id of the attribute to the non-null id parameter value.
value) The method returns the Attribute that was stored in the
Attributes set with the same id. If there was no other
object with the id of this Attribute, null is returned.

Attribute remove Removes the attribute with the attribute id from the
(String id) attribute set and returns this attribute or null if the attribute
did not exist in the Attributes set.

int size() Returns the number of attributes in the attribute set.

Note Don’t confuse Attributes, an interface for managing collections of attributes,


with Attribute, an interface for an object that represents a single attribute from
a directory service.

Attributes objects manage collections of objects that implement another interface called
Attribute. Objects that implement the Attribute interface map Java objects to specific
values from a directory service. The class javax.naming.directory.BasicAttribute
implements the Attribute interface.

Table 5.8 lists the methods of the Attribute interface.

Table 5.8: Attribute Interface Methods

Method Use

- 100 -
boolean add(Object Adds a value to the attribute.
value)

void clear() Deletes all values from this attribute.

Object clone() Copies the attribute and references to its values. The
values themselves are not cloned.

boolean contains Returns true if the value parameter is a value of this


(Object value) attribute.

Object get() Returns one of this attribute’s values. Null may be


returned if it is a value of the attribute.

NamingEnumeration Returns an enumeration of all of the attribute’s values.


getAll()

DirContext getAttribute Returns the attribute’s schema definition as a


Definition() DirContext.

DirContext Returns the syntax definition associated with the


getAttributeSyntax attribute as a DirContext.
Definition()

String getID() Returns the non-null id of this attribute.

boolean remove Deletes the value object from this attribute.


(Object value)

int size() Returns the number of values in this attribute.

When the BasicAttributes class needs to create an Attribute, it uses a BasicAttribute.


Table 5.9 lists the constructors for BasicAttributes.

Table 5.9: Basic Attributes Constructors

Method Use

BasicAttributes() Constructs a new instance of BasicAttributes.

BasicAttributes Constructs a new instance of BasicAttributes and if


(boolean ignoreCase) ignoresCase is set to true, the cases of each identifier
of the attributes added are ignored.

BasicAttributes Constructs a new instance of BasicAttributes, with


(String id, Object one attribute with the non-null id and the object value
value) specified. If value is null, null is stored as the object
value.

- 101 -
BasicAttributes Constructs a new instance of BasicAttributes, with
(String id, Object one attribute with the non-null id and the object value
value, boolean specified. If value is null, null is stored as the object
ignoreCase) value. If ignoresCase is set to true, the cases of each
identifier of the attributes added are ignored.

For example, here is the code for searching for all people who have an e-mail address
and a Web site attribute:

...
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://MyHost/o=JNDIExample");
try

{
DirContext dctx = new InitialDirContext(env);

Attributes attrs = new BasicAttributes(true);


attrs.put(new BasicAttribute("email"));
attrs.put(new BasicAttribute("website"));
NamingEnumeration result = dctx.search("ou=People", attrs);

while (result.hasMore())
System.out.println(result.next());
...

In this code, BasicAttribute is created with a constructor that takes a boolean. By passing
true into the constructor, the BasicAttributes object that is instantiated ignores the case of
attributes it manages. If false was passed in, the BasicAttributes object would be case
sensitive. This case sensitivity affects how searching will interpret the contents of the
BasicAttributes. After the BasicAttributes object is created, two attributes of type
BasicAttribute are added to the BasicAttributes object. After BasicAttributes is created
and attributes have been inserted, it is passed to the search method of the context, which
searches for all objects that have both a Web site and an e-mail attribute. The result of
the search is a NamingEnumeration, which can be processed as it was in the Naming
section. The output resembles Figure 5.6.

Figure 5.6: Searching with attributes.

By setting values for these searches, you can look for all people who have an e-mail

- 102 -
address and the Web site www.pri.com. In the following code you will see that a value of
www.pri.com has been added to the construction of the Web site BasicAttribute. The
attributes and their values affect the choice of objects that are retrieved as part of the
search. An Attributes list can be reused. For example, you could update it to include new
values and re-execute the search. In this way you could narrow a search by filling in more
and more details, or widen a search by removing attributes from the Attributes collection.

...
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://MyHost/o=JNDIExample");
try
{
DirContext dctx = new InitialDirContext(env);

Attributes attrs = new BasicAttributes(true);


attrs.put(new BasicAttribute("email"));
attrs.put(new BasicAttribute("website","www.pri.com"));

NamingEnumeration result = dctx.search("ou=People", attrs);

while (result.hasMore())
System.out.println(result.next());
...

Note Updates to BasicAttributes such as removing an attribute do not affect the


corresponding representation in the directory. Updates to the directory can be
affected only using modification operations in the DirContext interface.

The output will resemble Figure 5.7.

Figure 5.7: Results of fetching with attribute values.

A collection of attributes can be used to build a wide or narrow search criteria.


Regardless of the directory service you are connected to, a collection of objects that
represents matches for the search is returned. However, often you are not interested in
getting back a collection of objects; rather, you would like only certain attributes from the
objects. For example, you may want only the telephone numbers for all the customers
rather than retrieving all the information you have on the customers. The next section will
show you how to do this.

Retrieving Specific Attributes

Another variation of the search method allows us to specify which attributes to fetch from
the directory. In other words, rather than retrieving all attributes of all objects returned, we
can specify specific attributes to be returned, thus optimizing the search. For instance, if

- 103 -
we wanted to retrieve only the name and e-mail of each person in the last example, the
code can be modified like this:

...
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://MyHost/o=JNDIExample");
try
{
DirContext dctx = new InitialDirContext(env);

String[] attrIDs = { "cn", "email"} ;


new BasicAttributes(true);
attrs.put(new BasicAttribute("email"));
attrs.put(new BasicAttribute("website","www.pri.com"));

NamingEnumeration result =
dctx.search("ou=People", attrs, attrIDs );

while (result.hasMore())
System.out.println(result.next());
...

The output resembles Figure 5.8. Compare the results with Figure 5.7. In Figure 5.8, only
the values specified in the attrIDs array were retrieved. In both cases, a set of objects
that are stored in a NamingEnumeration were retrieved. The next section discusses how
to process these objects.

Figure 5.8: Results of fetching specific attributes.

Processing a Search Result

The result returned from these searches is actually a subclass of Bindings called a
search result. Table 5.10 lists the SearchResult methods.

Table 5.10: SearchResult Methods

Method Use

SearchResult(String name, This constructor creates a search result with


Object obj, Attributes the non-null name of the search item relative
attrs) to the context, the object (which can be null
bound to the name), and the list of attributes

- 104 -
to return from the search.

SearchResult(String name, This constructor creates a search result with


Object obj, Attributes the non- null name of the search item, the
attrs, boolean isRelative) object (which can be null bound to the name),
and the list of attributes to return from the
search. If isRelative is true, the name of the
search item is relative to the context of the
search. If isRelative is false, name is a
complete URL.

SearchResult(String name, This constructor creates a search result with


String className, Object the non- null name of the search item relative
obj, Attributes attrs) to the context, the name of the class of the
object bound to name, the object (which can
be null bound to the name), and the list of
attributes to return from the search. If
className is null, the class name for obj is
returned with getClassName(). If obj is null,
getClassName() will return null.

SearchResult(String name, This constructor creates a search result with


String className, Object the non- null name of the search item, the
obj, Attributes attrs, name of the class of the object bound to
boolean isRelative) name, the object (which can be null bound to
the name), and the list of attributes to return
from the search. If isRelative is true, the name
of the search item is relative to the context of
the search. If isRelative is false, name is a
complete URL. If className is null, the class
name for obj is returned with getClassName().
If obj is null, getClassName() will return null.

Attributes getAttributes() Returns the attributes of this search result.

void Sets the attributes for this search result to


setAttributes(Attributes attrs.
attrs)

String toString() Returns a string representation of this binding.

Using a search result, you could you could use the following program to print the results:

import java.util.Hashtable;
import javax.naming.*;
import javax.naming.directory.*;

public class AttributeExample


{
public static void main(String[] argc)
{
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");

// Replace MyHost with the host name of your LDAP server.

- 105 -
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");
try
{
DirContext dctx = new InitialDirContext(env);
Attributes attrs = new BasicAttributes(true);
attrs.put(new BasicAttribute("email"));
attrs.put(new
BasicAttribute("website","www.pri.com"));

NamingEnumeration result =
dctx.search("ou=People", attrs);

// Print each search result.


while (result.hasMore())
{
SearchResult sr = (SearchResult)result.next();
System.out.println("Result = " + sr.getName());
Attributes srchAttrs = sr.getAttributes();

NamingEnumeration attributes =
srchAttrs.getAll();

// For each result, print the attributes.


while(attributes.hasMore())
{
Attribute attr =
(Attribute)attributes.next();
System.out.println("Attribute: " +
attr.getID());
NamingEnumeration values = attr.getAll();

// Attributes can have multiple values


while(values.hasMore())
{
Object value = values.next();
System.out.println("Value = " + value);
}
}
}
} catch(Exception e)
{
System.out.println(e);
}
}
}

In this code listing, the directory package is imported, and then a context is initialized, as
usual. Then the attributes are inserted in a BasicAttributes object and passed into the
search method. The search method returns a NamingEnumeration containing a list of
search results. Iterate through each search result, obtaining a list of attributes by calling
the SearchResult method getAttributes. This method returns another
NamingEnumeration containing a list of attributes associated with the current search
result. An attribute can have more than one value associated with it, just as a person can
have more than one home phone number, so we iterate through these by calling the

- 106 -
Attribute method getAll, which returns yet another NamingEnumeration containing a list of
values for this attribute. Each of these values is then printed. The output resembles
Figure 5.9.

Figure 5.9: Processing SearchResults.

The next section discusses how filters can be used to create even more robust searching
criteria by using logical operators and other commands to create specific search criteria.

Searching with Filters

The search methods of DirContext can take a filter string. The string follows the search
expression patterns in RFC 2254. For example, to search for the person with
www.pri.com as a Web site and an e-mail address starting with “kerry,” you would use
“(&(email=kerry*)(website=www.pri.com))”.

Each item in the filter contains an attribute identifier and an attribute value or symbols
representing the attribute value. Table 5.11 lists the expression symbols. In the example
above, (website=www.pri.com) would denote an attribute called website that must have
the value www.pri.com. The expression (email=kerry*) means that the attribute email
must exist and have a value starting with kerry. It doesn’t matter if the full e-mail address
is [email protected], [email protected], or some other value. To check if the object has an
attribute called phone with any value, use the expression (phone=*). This means that
phone attribute must exist, but its value does not matter.

Table 5.11: Search Filter Symbols

Symbol Description

& Logical and. All items in the list must be true, as in (&(a=1)(b=2) ).

| Logical or. One or more of the items must be true, as in (|(a=1)(b=2) ).

! Not. The negated item must be false, as in (!(3=4)) is true.

- 107 -
= Checks for equality based on the matching rule of the attribute.

~= Checks for approximate equality based on the matching rule of the attribute.

>= Checks that the attribute is greater than the value.

<= Checks that the attribute is less than the value.

=* Checks for existence of the attribute.

* In an equality test; represents a wildcard representing zero or more


characters at that position, as in (name=Alb*).

\ Used for escaping *, (, and ) inside an attribute value.

Each set of attribute values must be enclosed in parentheses. Parenthetical


attribute/value pairs can be combined using the logical operators in Table 5.11. To
combine multiple operators, use parentheses. For example, to create a filter to find all
people whose name began with the letter E or who have a phone number, an e-mail
address, and a Web site, specify (|(cn=E*)(&(phone=*)(website=*))).

The following code list for a program called FilterExample returns a NamingEnumeration
of people with a name beginning with E or having an account with more than $1,005 in it.

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;

public class FilterExample


{
public static void main(String[] argc)
{
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");

try
{
DirContext dctx = new InitialDirContext(env);

String filter = "(&(cn=E*)(account>1005))";

NamingEnumeration result =
dctx.search("ou=People", filter, null);

while (result.hasMore())
{
SearchResult sr = (SearchResult)result.next();
System.out.println("Result = " + sr.getName());
}

- 108 -
} catch(NamingException e)
{
System.out.println(e);
}
}
}

This code creates an initial context on our LDAP server, then creates a filter string. The
string is passed as a parameter to the search method, which searches the named context
(people) relative to the initial context for objects meeting the filter criteria. Then the
resulting NamingEnumeration is processed and the results are printed. The output
resembles Figure 5.10, which shows that two people fit the filter criteria. Both have
names starting with E, and both have an account with a balance greater than $1,005 in
the server.

Figure 5.10: Results of using a filter.

All of the search examples thus far have ignored performance issues because the
example LDAP server has very few records. Consider what would happen if a search
was performed for all records in the U.S. phone directory. Obviously, limiting the search
results is important. The next section discusses how to deal with this and other
performance issues within JNDI.

Configure the Search with SearchControls


Recall that in the last example, the search method required an additional parameter that
was set to null. This parameter is a SearchControls object. SearchControls allows you to
fine-tune the performance of your search. The SearchControls methods are listed in
Table 5.12.

Table 5.12: SearchControls Methods

Method Use

SearchControls() This constructor creates an instance of


SearchControls with defaults set to search one
level, with no maximum return limit for search
results, with no time limit for a search, that returns
all attributes associated with objects that satisfy
the search filter, and returns the name and class
but not the named object itself. Links are not
dereferenced during search.

SearchControls(int scope, This constructor creates an instance of


long climit, int tlimit, SearchControls with scope set to
String[] attrs, boolean OBJECT_SCOPE, ONELEVEL_SCOPE, or
retobj, SUBTREE_SCOPE. The parameter tlimit sets the

- 109 -
boolean deref) maximum number of milliseconds to wait before
returning if the search is taking awhile. If tlimit is
set to 0, the search will wait indefinitely, if
necessary. The parameter deref specifies that the
search should dereference links during search if
deref is set to true. The parameter climit specifies
the maximum number of entries to return from the
search. If climit is set to 0, return all entries that
satisfy the filter. The parameter retobj specifies
that the result should return the object bound to
the name of the entry if retobj is set to true. The
parameter attrs contains the identifiers of the
attributes to return. If attrs is null, all attributes are
returned. If attrs is not null but contains no
identifiers, no attributes will be returned.

long getCountLimit() Returns the maximum number of entries that will


be returned as a result of the search.

boolean Returns true if links will be dereferenced during


getDerefLinkFlag() the search.

String[]getReturningAttributes() Returns the array of attributes that will be


returned as part of the search.

boolean Returns true if object values will be returned as


getReturningObjFlag() part of the result.

int getSearchScope() Returns the search scope for these search


controls. The search scope is OBJECT_SCOPE,
ONELEVEL_SCOPE, or SUBTREE_SCOPE.

int getTimeLimit() Returns the maximum time limit in milliseconds


that the search controls will wait for results.

void setCountLimit(long Sets the maximum number of entries to be


limit) returned as a result of the search.

void setDerefLinkFlag( Sets the search controls such that if on is true, the
boolean on) links will be dereferenced during the search.

void setReturning Specifies the attributes that will be returned as part


Attributes(String[] of the search.
attrs)

void setReturningObjFlag( Sets the search controls such that if on is true, the
boolean on) object values will be returned as part of the search
results.

void setSearchScope(int Sets the search scope to one OBJECT_SCOPE,


scope) ONELEVEL_SCOPE, or SUBTREE_SCOPE.

void setTimeLimit(int ms) Sets the maximum time limit in milliseconds that
the

Using the SearchControls, you can limit the number of objects returned from the search.

- 110 -
Note search(name, filter, null) is equivalent to search(name, filter, new
SearchControls()).

The default SearchControls constructor sets the following values:

• Search one level

• No maximum return limit for search results

• No time limit for search

• Return all attributes associated with objects that satisfy the search filter

• Do not return named object (return only name and class)

• Do not dereference links during search

A SearchControls object is not synchronized, so multiple threads trying to access and


modify a single SearchControls instance should lock the object. Table 5.13 lists the
values stored when a SearchControls instance is serialized.

Table 5.13: SearchControls Serialized Values

Field Purpose

String[] Contains the list of attributes to be returned in SearchResult


AttributesToReturn for each matching entry of search. If attributesToReturn
returns null, all attributes are to be returned.

long countLimit Contains the maximum number of SearchResults to return.

boolean derefLink Indicates whether JNDI links are dereferenced during


search.

boolean returnObj Indicates whether the object is returned as part of the


SearchResult.

int searchScope Contains the scope to apply to the search. The scope is one
for the following: ONELEVEL_SCOPE, OBJECT_SCOPE,
or SUBTREE_SCOPE.

int timeLimit Contains the maximum number of milliseconds to wait


before returning from search.

The SearchControls can be used to control the attributes returned as well as the objects
returned.

Return Selected Attributes Using SearchControls

- 111 -
The search methods of DirContext in Table 5.6 that take a SearchControls parameter do
not accept an attribute list. In cases in which you are using SearchControls, you do not
need that extra parameter. The SearchControls method setReturningAttributes allows
you to specify an array of attribute names. This array specifies which attributes will be
returned as a result of the search.

Note If not all of the attributes listed in the returning attributes array are available,
the available attributes are returned. The purpose of the
setReturningAttributes method is to limit the scope of the results.

Here is what the code for retrieving a set of attributes using a SearchControls may look
like:

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;
public class SearchControlsExample
{
public static void main(String[] argc)
{
Hashtable env = new Hashtable(11);

env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");

try
{
DirContext dctx = new InitialDirContext(env);

String filter = "(&(cn=S*)(account>1005))";

String[] attrIDs = { "cn", "email"} ;


SearchControls sc = new SearchControls();
sc.setReturningAttributes(attrIDs);

NamingEnumeration result =
dctx.search("ou=People", filter, sc);

while (result.hasMore())
{
SearchResult sr = (SearchResult)result.next();
System.out.println("Result = " + sr.getName());
}
} catch(NamingException e)
{
System.out.println(e);
}
}
}

In this example, a SearchControls object is instantiated, and its method


setReturningAttributes is passed an array of attribute names. This SearchControls object

- 112 -
is passed into the search method and controls the result by limiting the attributes returned
to name (cn is an LDAP convention standing for common name) and email. This process
results in a query similar to passing in an attributes list. The output of the example
resembles Figure 5.11.

Figure 5.11: Results of fetching specific attributes.

As you can see, only the attributes specified in SearchControls were retrieved.

Control the Scope of a Search

By default, using SearchControls when searching a directory sets your search scope to
SearchControls.ONELEVEL_SCOPE. Only the currently named context is searched for
matches to your filter. Other options include:

• SearchControls.SUBTREE_SCOPE. Searches the named context and all


subcontexts.

• SearchControls.OBJECT_SCOPE. Searches the named object only to determine if it


matches the search filter.

Here is an example of using a SUBTREE_SCOPE:

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;

public class SearchScopeExample


{
public static void main(String[] argc)
{
Hashtable env = new Hashtable(11);

env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");

try
{
DirContext dctx = new InitialDirContext(env);

String filter = "(&(cn=S*)(account>1000))";

String[] attrIDs = { "cn", "email"} ;


SearchControls sc = new SearchControls();
sc.setReturningAttributes(attrIDs);

- 113 -
sc.setSearchScope(SearchControls.SUBTREE_SCOPE);

NamingEnumeration result =
dctx.search("ou=People", filter, sc);

while (result.hasMore())
{
SearchResult sr = (SearchResult)result.next();
System.out.println("Result = " + sr.getName());
}
} catch(NamingException e)
{
System.out.println(e);
}
}
}

This code searches the initial context of the named object (People) and any subcontexts
beneath it. Figure 5.12 shows how the results differ if the SUBTREE_SCOPE is replaced
with OBJECT_SCOPE or ONELEVEL_SCOPE. Use OBJECT_SCOPE to search only
the named object, ONELEVEL_SCOPE to search the entire context of the named object,
and SUBTREE_SCOPE to search the context and all its subcontexts. Obviously,
SUBTREE_SCOPE could be a costly search for a large directory structure.

Figure 5.12: SearchControls scopes.

Search controls are powerful tools for helping you narrow your search and improve
performance. However, search controls can do much more, as the next few sections will
show.

Traverse Links

Services may support the notion of a link. A link is an entry in a context that references
another entry. This is similar to a symbolic link in a file system. Your search can be
configured to either traverse these links, looking for matches to a search, or it can be
configured to ignore the path of the link. To do this, call the SearchControls method
setDerefLinkFlag and pass a Boolean. Here is an example:

...
SearchControls sc = new SearchControls();
// Traverse links
sc.setDerefLinkFlag(true);
...

By setting the dereference link flag to true in this code, a search traverses links in the
directory and continues the search. A setting of false would prevent such a traversal and
thereby shorten the search.

- 114 -
Retrieve Objects

SearchControls allows you to specify whether you want to retrieve objects that are bound
to your directory or just the name of the object and its class. By bringing back only the
names of the object and class, you can greatly improve the performance of a query. To
do this, call the SearchControls method setReturningObjFlag and pass a Boolean. Here
is an example:

...
SearchControls sc = new SearchControls();
// Retrieve the actual object from the directory
sc.setReturningObjFlag(true);
...

By setting the return object flag to true in this code, the search returns a set of objects
from the directory. This results in a slower search but returns much more data. Setting
this flag to false only returns the names of the objects, which is a much faster operation.
Set this flag to false if you only want to browse the directory rather than actually fetching
and manipulating the objects in it.

Control the Number of Results

At times, you search a directory and your query returns a large number of results. It is
often a good idea to control the number of results so your program does not get
overwhelmed by gigabytes of data. To do this, call the SearchControls method
setCountLimit and pass in the number of results you want. Then your search will not
return more than that many results. If there are more results than are returned, the
exception SizeLimitExceededException is thrown by the NamingEnumeration. To retrieve
all results, set the SearchControl’s count limit to 0. Here is an example:

...
try
{
DirContext dctx = new InitialDirContext(env);

String filter = "(&(cn=E*)(account>1000))";

SearchControls sc = new SearchControls();


sc.setCountLimit(5);

NamingEnumeration result =
dctx.search("ou=People", filter, sc );

while (result.hasMore())
System.out.println(result.next());
} catch(SizeLimitExceededException se)
{

System.out.println("More than five objects returned...");


} catch(NamingException ne)
{
...

By setting the count limit to 5 in this code, only the first five results are returned.

- 115 -
Set a Time Limit on Retrieving Results

SearchControls allows you to limit the amount of waiting time for results. This is useful
when you don’t want your program to block for a long time on searches that are taking
awhile due to the performance of the server, the network, and so on. To do this, call the
SearchControls method setTimeLimit and pass in the number of milliseconds you are
willing to wait. Then the search returns results within that span of time, or the exception
TimeLimitExceededException is thrown. Set the time limit to 0 to wait forever. Here is an
example:

...
try
{
DirContext dctx = new InitialDirContext(env);

String filter = "(&(cn=E*)(account>1000))";

SearchControls sc = new SearchControls();


// Wait 2 seconds
sc.setTimeLimit(2000);

NamingEnumeration result =
dctx.search("ou=People", filter, sc );

while (result.hasMore())
System.out.println(result.next());
} catch(TimeLimitExceededException te)
{

System.out.println("More than two seconds have passed...");


} catch(NamingException ne)
{
...

By setting the time limit, you force a search to return in a fixed amount of time. In this
code, the setting of 2000 means that if the query cannot return due to a large number of
results or a network failure, the search will be canceled after two seconds, which means
the user is not subjected to a long wait for a response.

Modify Attributes
Now that values have been retrieved from the directory, let’s look at techniques for
modifying the values of an object in the directory. The DirContext method
modifyAttributes can be used for this purpose. Table 5.14 lists the variations of this
method.

Table 5.14: DirContext modifyAttributes Method Variations

Method Use

- 116 -
void modifyAttributes(Name Modifies the attributes contained in attrs of the
name, int mod_op,Attributes object described by the non-null name. The
attrs) operation to perform is ADD_ATTRIBUTE,
REPLACE_ATTRIBUTE, or
REMOVE_ATTRIBUTE. The operations occur
in no particular order.

void modifyAttributes(Name Performs the modifications of the attributes as


name, ModificationItem[] specified in mods of the object described by
mods) the non-null name. The operations occur in the
order they are defined in the ModificationItem
array.

void modifyAttributes(String Modifies the attributes contained in attrs of the


name, int mod_op,Attributes object described by the non-null name. The
attrs) operation to perform is ADD_ATTRIBUTE,
REPLACE_ATTRIBUTE, or
REMOVE_ATTRIBUTE. The operations occur
in no particular order.

void modifyAttributes(String Performs the modifications of the attributes as


name,ModificationItem[]mods) specified in mods of the object described by
the non-null name. The operations occur in the
order they are defined in the ModificationItem
array.

The simple form of the modifyAttributes method takes a named object (Name or String), a
modification operation (listed below), and a list of attributes to modify (instance of
Attributes).

The modification operation can be one of the following constants:

• DirContext.ADD_ATTRIBUTE

• DirContext.REPLACE_ATTRIBUTE

• DirContext.REMOVE_ATTRIBUTE

For example, given a person named John, suppose that you want to add an e-mail
address [email protected] and an empty website address attribute. The code below uses the
modifyAttributes method of DirContext to specify that a set of attributes should be added
to the object John in the current context.

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;

public class Modify1Example


{
public static void main(String args[])
{
Hashtable env = new Hashtable(11);

env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");

- 117 -
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");
try
{
DirContext dctx = new InitialDirContext(env);
Attributes attrs = new BasicAttributes(true);
attrs.put(new
BasicAttribute("email","[email protected]"));
attrs.put(new BasicAttribute("website"));

dctx.modifyAttributes("cn=John, ou=People",
DirContext.ADD_ATTRIBUTE,
attrs);
} catch(Exception e)
{
System.out.println(e);
}
}
}

Using this technique, you can add, modify, and delete attributes. The only problem is that
each modify call can perform only a single operation on a set of attributes.

Create a Modification List

The previous modifyAttributes example is useful when you want to perform one atomic
operation on one or more attributes. However, it is often more useful to perform several
operations at once. For example, let’s say a person named Karen moved from the
marketing department to the sales department. The program will need to change Karen’s
attributes to reflect the move. Karen may also require a sales quota attribute with an
initial value. Maybe she doesn’t have an assistant anymore, so that attribute should be
removed. Rather than executing three modifyAttributes calls, you use a ModificationItem
list to specify these changes. A ModificationItem consists of one of the operation
constants in the preceding example specifying the operation, as well as an Attribute to
modify. Modifications are applied in the order in which they appear in the list; either all of
the modifications are executed, or none are. The following example performs the position
transfer for Karen.

import javax.naming.*;
import javax.naming.directory.*;
import java.util.Hashtable;

public class Modify2Example


{
public static void main(String args[])
{
Hashtable env = new Hashtable(11);

env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,
"ldap://MyHost/o=JNDIExample");
try
{
DirContext dctx = new InitialDirContext(env);

- 118 -
ModificationItem[] mods = new ModificationItem[3];
mods[0] = new ModificationItem(
DirContext.REPLACE_ATTRIBUTE,
new BasicAttribute("department", "sales"));
mods[1] = new ModificationItem(
DirContext.ADD_ATTRIBUTE,
new BasicAttribute("quota", "$1,000,000"));
mods[2] = new ModificationItem(
DirContext.REMOVE_ATTRIBUTE,
new BasicAttribute("assistant"));

dctx.modifyAttributes("cn=Karen, ou=People", mods);


} catch(Exception e)
{
System.out.println(e);
}
}
}

In this code, a ModificationItem array is created, and all the necessary changes for Karen
are added to it. The changes take the form of ModificationItems containing an operation
to perform; an attribute to add, remove, or change; and an optional value to which the
attribute is set.

Using ModificationItem, you can perform operations that affect the types of attributes
contained in the server. This potentially changes the schema of the directory server. The
next section shows the features of JNDI that allow you to explore the schema of a directory
service.

Schema
For dynamic applications that need to read the structure of a directory, the schema is an
important resource. The schema describes rules regarding the structure of the
namespace and the attributes stored in it. The schema specifies the types of objects that
can be added to the directory, the mandatory and optional attributes that are possible,
and where the objects can be added in the schema. JNDI supports the notion of schemas
expressed as an information tree. Programs can retrieve the schema associated with a
directory object if the underlying context provides the appropriate support. The DirContext
method getSchema is used to retrieve the root DirContext of the schema tree associated
with the directory. The children of the schema root describe the rules that define the
structure and attributes of the directory context. These children themselves are each of
type DirContext. These contexts have a method getSchemaClassDefinition that contains
information about a particular directory in the schema. You can also find out information
about an attribute of an object by calling the Attribute methods getAttributeDefinition and
getAttributeSyntaxDefinition. These methods are described in Table 5.15.

Table 5.15: Schema-Related Methods

Method Use

Schema methods defined in

- 119 -
DirContext

DirContext Returns the schema associated with the named


getSchema(Name name) object.

DirContext getSchema Returns the schema associated with the named


(String name) object.

DirContext Returns the schema object class definition for the


getSchemaClassDefinition(Name named object. This object defines how the objects
name) are actually represented in the directory.

DirContext Returns the schema object class definition for the


getSchemaClassDefinition(String named object. This object defines how the
name) objects are actually represented in the directory.

Schema methods defined in


Attribute

DirContext getAttribute Returns the attribute’s schema definition.


Definition()

DirContext getAttribute Returns the syntax definition associated with the


SyntaxDefinition() attribute. This is the directory-dependent attribute
description rather than the Java description of the
attribute. For example, a Java object attribute might
have a syntax definition of “money.”

Using the schema methods of DirContext, you can identify the structure of a directory
service and create dynamic queries and updates. For example, you could find out if the
directory service supported a certain type of currency, then do your calculations in that
currency. Schema access provides you a way to create truly dynamic applications that
leverage many directory services with a consistent API.

Exception Handling
The JNDI defines a class hierarchy for exceptions that can be thrown in the course of
performing naming and directory operations. The JNDI exceptions are listed in Table
5.16.

Table 5.16: JNDI Exceptions

Exception Superclass Description

Defined in
javax.naming

AuthenticationException javax.naming. This exception is thrown when an


NamingSecurity authentication error occurs while
Exception accessing the naming or directory
service.

- 120 -
AuthenticationNot javax.naming. This exception is thrown when the
SupportedException NamingSecurity particular flavor of authentication
Exception requested is not supported.

CannotProceed javax.naming. This exception is thrown to indicate


Exception NamingException that the operation reached a point
in the name where the operation
cannot proceed any further.

Communication javax.naming. This exception is thrown when the


Exception NamingException client is unable to communicate
with the directory or naming
service.

Configuration javax.naming. This exception is thrown when


Exception NamingException there is a configuration problem.

ContextNotEmpty javax.naming. This exception is thrown when


Exception NamingException attempting to destroy a context
that is not empty.

Insufficient javax.naming. This exception is thrown when


ResourcesException NamingException resources are not available to
complete the requested operation.

InterruptedNamingException javax.naming. This exception is thrown when


NamingException the naming operation being
invoked has been interrupted.

InvalidName javax.naming. This exception indicates that the


Exception NamingException name being specified does not
conform to the naming syntax of a
naming system.

LimitExceeded javax.naming. This exception is thrown when a


Exception NamingException method terminates abnormally due
to a user- or system-specified limit.

LinkException javax.naming. This exception is used to describe


NamingException problems encountered while
resolving links.

LinkLoop javax.naming. This exception is thrown when a


Exception LinkException loop is detected in attempting to
resolve a link, or when an
implementation-specific limit on
link counts has been reached.

MalformedLink javax.naming. This exception is thrown when a


Exception LinkException malformed link is encountered
while resolving or constructing a
link.

NameAlready javax.naming. This exception is thrown by


BoundException NamingException methods to indicate that a binding
cannot be added because the
name is already bound to another
object.

NameNotFound javax.naming. This exception is thrown when a

- 121 -
Exception NamingException component of the name cannot be
resolved because it is not bound.

Naming java.lang.Exception This is the superclass of all


Exception exceptions thrown by operations
in the Context and DirContext
interfaces.

NamingSecurity javax.naming. This is the superclass of security-


Exception NamingException related exceptions thrown by
operations in the Context and
DirContext interfaces.

NoInitiaContextException javax.naming. This exception is thrown when no


NamingException initial context implementation can
be created.

NoPermission javax.naming. This exception is thrown when


Exception NamingSecurity attempting to perform an operation
Exception for which the client has no
permission.

NotContext javax.naming. This exception is thrown when a


Exception NamingException naming operation proceeds to a
point at which a context is required
to continue the operation, but the
resolved object is not a context.

OperationNot javax.naming. This exception is thrown when a


SupportedException NamingException context implementation does not
support the operation being
invoked.

PartialResult javax.naming. This exception is thrown to indicate


Exception NamingException that the result being returned or
returned so far is partial and that
the operation cannot be
completed.

ReferralException javax.naming. This abstract class is used to


NamingException represent a referral exception,
which is generated in response to
a referral such as that returned by
LDAP v3 servers.

ServiceUnavailable javax.naming. This exception is thrown when


Exception NamingException attempting to communicate with a
directory or naming service and
that service is not available.

SizeLimitExceeded javax.naming. This exception is thrown when a


Exception LimitExceeded method produces a result that
Exception exceeds a size-related limit.

TimeLimitExceeded javax.naming. This exception is thrown when a


Exception LimitExceededException method does not terminate
within the specified time limit.

AttributeInUseException javax.naming. This exception is thrown when an


NamingException operation attempts to add an

- 122 -
attribute that already exists.

AttributeModification javax.naming. This exception is thrown when an


Exception NamingException attempt is made to add, remove,
modify an attribute, its identifier, or
its values that conflicts with the
attribute’s (schema) definition or
the attribute’s state.

InvalidAttribute javax.naming. This exception is thrown when an


IdentifierException NamingException attempt is made to add or create
an attribute with an invalid attribute
identifier.

InvalidAttributes javax.naming. This exception is thrown when an


Exception NamingException attempt is made to add or modify
an attribute set that has been
specified incompletely or
incorrectly.

InvalidAttributeValue javax.naming. This class is thrown when an


Exception NamingException attempt is made to add to an
attribute a value that conflicts with
the attribute’s schema definition.

InvalidSearchControls javax.naming. This exception is thrown when the


Exception NamingException specification of SearchControls for
a search operation is invalid.

InvalidSearchFilter javax.naming. This exception is thrown when the


Exception NamingException specification of a search filter is
invalid.

NoSuchAttribute javax.naming. This exception is thrown when


Exception NamingException attempting to access an attribute
that does not exist.

SchemaViolation javax.naming. This exception is thrown when a


Exception NamingException method in some ways violates the
schema.

The root of this class hierarchy is NamingException. Programs interested in dealing with a
particular exception can catch the corresponding subclass of the exception. Otherwise,
programs should catch NamingException.

Summary
JNDI is a powerful tool in your Java arsenal. It provides a seamless interface for
accessing all types of information resources. The benefit of a single API for accessing
multiple data sources is that you have less to learn and can even change the data source
you are accessing by simply changing the values of a few environment variables. Speed
of development and maintenance of your code are thus enhanced.

One consideration is performance of using JNDI rather than a native library for accessing
your particular directory or naming service. Consider the performance of the JNDI service
provider you are using and keep in mind the effects of network traffic and server
performance when determining the causes of any performance issues. Even though
performance probably won’t be a reason not to use JNDI, you may need to consider the

- 123 -
limits of the JNDI searching facility. You probably don’t need a more sophisticated
searching mechanism for a naming and directory service than the ones provided with
JNDI, but if you do, this may be a limitation of JNDI that you have to work around.

You need to be aware of the security issues for the environment in which your program
will run. For example, if you want to create an applet and allow it to connect to directory
server running on another machine, you probably need to sign both your applet and all
the JNDI-related jar files that your applet will use. The documentation for your service
provider should discuss any specific issues for that provider. Also, read the
documentation for your service provider to determine which, if any, of the security-
oriented properties can be used and the effects of using them.

Remember that it is up to the service providers to support various capabilities of the


JNDI. Make sure you carefully read the documentation that comes with the service
provider package to see what features are enabled and what security considerations are
handled. For example, the file system provider from Sun supports only naming services.
The LDAP provider allows you to serialize objects, but others may not. The RMI provider
supports only a flat directory. It also supports the notion of referenceable objects being
bound into the RMI registry.

At this point, we have looked at how you can use Java to access enterprise data from a
variety of services. The next chapter begins to explore how you can create services of your
own, beginning with Web server services in the form of a new application structure called a
servlet.

Summary
JNDI is a powerful tool in your Java arsenal. It provides a seamless interface for
accessing all types of information resources. The benefit of a single API for accessing
multiple data sources is that you have less to learn and can even change the data source
you are accessing by simply changing the values of a few environment variables. Speed
of development and maintenance of your code are thus enhanced.

One consideration is performance of using JNDI rather than a native library for accessing
your particular directory or naming service. Consider the performance of the JNDI service
provider you are using and keep in mind the effects of network traffic and server
performance when determining the causes of any performance issues. Even though
performance probably won’t be a reason not to use JNDI, you may need to consider the
limits of the JNDI searching facility. You probably don’t need a more sophisticated
searching mechanism for a naming and directory service than the ones provided with
JNDI, but if you do, this may be a limitation of JNDI that you have to work around.

You need to be aware of the security issues for the environment in which your program
will run. For example, if you want to create an applet and allow it to connect to directory
server running on another machine, you probably need to sign both your applet and all
the JNDI-related jar files that your applet will use. The documentation for your service
provider should discuss any specific issues for that provider. Also, read the
documentation for your service provider to determine which, if any, of the security-
oriented properties can be used and the effects of using them.

Remember that it is up to the service providers to support various capabilities of the


JNDI. Make sure you carefully read the documentation that comes with the service
provider package to see what features are enabled and what security considerations are
handled. For example, the file system provider from Sun supports only naming services.
The LDAP provider allows you to serialize objects, but others may not. The RMI provider
supports only a flat directory. It also supports the notion of referenceable objects being
bound into the RMI registry.

At this point, we have looked at how you can use Java to access enterprise data from a
variety of services. The next chapter begins to explore how you can create services of your
own, beginning with Web server services in the form of a new application structure called a

- 124 -
servlet.

Chapter 6: What Are Servlets?


Overview
Servlets, Sun’s latest contribution for enhancing the capabilities of a Web server, are
small Java programs that run on a Web server. The name servlets is play on words
because servlets are to a Web server as applets are to a Web browser. Servlets replace
the need for other server-side programming paradigms with a Java-specific solution. Web
servers began as a mechanism for sending static Web documents to a Web browser
using a simple protocol, HTTP. Before long, people began looking for ways to extend the
capabilities of the Web browser so that more complex forms of elements and objects
such as date validation fields and spreadsheet objects could be displayed. At the same
time, several technologies emerged for adding program logic on the server.

This chapter shows how typical server-side programming works and compares it to the
process of servlet creation and use. This comparison provides you with a basis for
understanding how other server-side technologies provide Web client services and at the
same time provides an overview of how to use servlets to perform the same tasks in
Java. Chapter 7, “Programming Servlets,” covers the features and use of servlets in
greater detail.

Server-Side Programming Technologies


Many technologies for programming on the server have emerged. The most common
technologies include the following:

Common Gateway Interface (CGI). CGI enables developers to create separate


applications that communicate with the Web server. The applications (or scripts)
generate Web pages or other files dynamically by processing form data and returning
documents based on the form values and other criteria.

Plug-ins. Server vendors such as Microsoft and Netscape came out with their own
proprietary APIs for extending the application logic of their particular servers. These
“plug-in” technologies (ISAPI and NSAPI, respectively) allow the programmer to take
advantage of server-specific features for managing the relationship between the Web
server and other resources. Plug-ins are custom code extensions that are added directly
to the server and run inside the server’s memory address space. Plug-ins have the
performance advantage of running in the Web server process, but if the plug-in crashes,
it also has the potential of crashing the server.

Server-side includes. Many servers support extensions to HTML called server-side


includes. Some Web servers have the ability to preprocess HTML before sending it to the
Web browser. The Web servers support additional HTML tags that can be dynamically
processed by the Web server so that the resulting HTML viewed in the Web browser can
be customized by this server-side include. An extension to server-side includes is the
concept of server-side scripting. Many servers are now supporting this type of scripting.
For example, Microsoft’s Web server supports Active Server Pages; Netscape supports
Server-Side JavaScript, previously known as LiveWire. These extensions are interpreted
by the server.

All of these technologies enable developers to create complete, distributed applications in


which the user interface logic is displayed in the Web browser and the business logic is
managed in server programs. Enhanced HTML capabilities for developing complete (but
rudimentary) user interfaces for the Web browser and the ability to build complex
business logic via CGI and other technologies on the Web server clearly changed the
way enterprise applications are written for a large number of systems.

- 125 -
Rather than creating a single monolithic application, we define smaller applications that
provide a finite number of functions such as validating a credit card or retrieving a product
description from a database. These smaller applications work together to perform the
functions of a larger enterprise system. Each of these applications can reside on a single
server, or they can be distributed throughout the network. This flexibility allows better
load balancing, performance tuning, and maintenance of each individual component. For
instance, you can upgrade one component such as an application that provides the user
with a menu of options without having to change the rest of the application components.

The next section describes how a CGI script can be used to process Web client requests.
This discussion forms a basis for comparing CGI and servlets.

Processing Forms with CGI


While each of the server technologies is slightly different in how it performs its tasks, the
CGI process illustrates the basic purpose and function of server-side logic. CGI programs
can be used to dynamically generate Web pages, as in this simple example using the
Perl language:

#!/bin/perl

# Send the output mime type to the server to know what output to
handle
print "Content-type: text/html\ n\ n";

print "<HTML>\ n";


print "<HEAD><TITLE>Hello World</TITLE></HEAD>\ n";
print "<BODY>\ n";

print "<H1>Hello World</H1>\ n";

print "</BODY></HTML>\ n";


exit;

Depending on how your Web server is configured, from a Web browser you could type:

HTTP://www.myserver.com/cgi-scripts/helloworld.pl

to access this program. Typically, CGI programs are used to process HTML forms and
then return dynamic HTML; however, CGI can be used to input and output any type of
data. Figure 6.1 shows a simple HTML form that is processed with a CGI script. After the
user enters values in the form, he presses the Submit button. The values are sent to the
CGI script as the FORM action. The CGI script processes the values sent to it and then
dynamically returns an HTML page thanking the user for the entry.

- 126 -
Figure 6.1: HTML form.

Here is the HTML for the page:

<html>
<head>
<title>Book Review</title>
</head>

<body>
<H1>Book Review</H1>
<form action=HTTP://servername/cgi-bin/bookreview.pl
method=POST>
What is your first name?
<input type=text name=fname>

<BR><BR>Check if you are planning to write a servlet


<input type=checkbox name=writeservlet>

<BR><BR>What was your favorite chapter so far?<BR>


<BR>Chp. 1<input type=radio name=favchap value=1>
<BR>Chp. 2<input type=radio name=favchap value=2>
<BR>Chp. 3<input type=radio name=favchap value=3>
<BR>Chp. 4<input type=radio name=favchap value=4>
<BR>Chp. 5<input type=radio name=favchap value=5>
<BR>Chp. 6<input type=radio name=favchap value=6>
<BR><BR><input type=submit><input type=reset>
</form>
</body>
</html>

Notice that each form element has a unique name and value. When the user presses the
Submit button, the form data is packaged up and sent to the CGI program as a request.
The CGI program parses the request and processes each of the form element’s values.
Then the CGI program typically returns another HTML document to the Web browser.
Figure 6.2 shows what a response might look like after the preceding form is submitted.

- 127 -
Figure 6.2: Resulting Web page after form is processed.

Here is what a CGI program written in Perl might look like:

#!/usr/bin/perl

require "cgilib.pl";

print "Content-type: text/html\ n\ n";

%dataDict = ();

&readData(*data);
&parseData(*data,*dataDict);
$fName = $dataDict{ "fname"} ;
$chp = $dataDict{ "favchap"} ;
$writeServlet = $dataDict{ "writeservlet"} ;

print "<HTML>";
print "<TITLE>Book Review Response</TITLE>";
print $fName;
print ", thank you for your feedback.<BR>";
print "Your favorite chapter so far is chapter ";
print $chp;
print ".<BR>";
print "<HR>";
print "You are ";

($writeServlet ne "") || print "not ";


print "planning to write a servlet.";
print "</HTML>";

Although the code for these programs may look complex, it is actually quite simple. A
Web client sends a set of key-value pairs to the Web server, which forwards the data to
the script named in the <FORM> tag. The CGI program processes these values and
writes HTML or other information back to the Web server. In the middle of this process,
the CGI program may write to a log file or access a database or other server resource.
Figure 6.3 illustrates what this three-tier architecture looks like.

- 128 -
Figure 6.3: Three-tier Web application architecture.

Processing Forms with Servlets


When Java came on the scene, applets allowed Web browsers to display even more
sophisticated user interfaces in a platform-independent way. Just as applets extend the
capabilities of a Web browser, servlets extend the capabilities of Web servers. Servlets
can be used as replacements for CGI programs or as plug-ins within JavaSoft’s JavaWeb
Server and other Web servers supporting the Servlet API.

Note The JavaWeb Server packages also come with plug-in support for popular
Web servers such as Netscape, Microsoft, and Apache. This means you can
write a servlet and have it treated as a vendor-specific plug-in by these Web
servers.

Like most Java technologies, servlets are portable and should work on any “servlet-
enabled” server. Because the servlet API is a standard, any server that supports servlets
should support your servlet. This is a huge advantage over CGI and other server-side
technologies. As more and more servers support servlets, the decision of which server
you use will not affect the design and implementation decisions you make for building
your enterprise application’s server logic.

In CGI programming, a server sends a request to a CGI program, which starts up,
processes the request, and shuts down. Because the CGI program doesn’t remain “open”
between requests, resources it uses, such as database connections, must be
reestablished between calls. This is a performance bottleneck and often requires more
complex programming logic to work around. Unlike CGI programs, servlets are
persistent. A database connection created in a servlet can remain open between server
requests, greatly enhancing performance.

Both servlets and CGI can be used to write small applications that perform specific tasks.
One advantage of CGI is that CGI scripts can be written in almost any language. The
disadvantage of CGI is that each time the server needs the services of one of your CGI
programs, it must start up a new CGI process. This process has no persistent state
unless you write the logic for storing it. For example, if you wanted to keep track of the
last credit card processed, you would need to write it to a file or a database of some sort.
This means every time a request is made of your CGI program, the Web server must
start an instance of your program, then the program must load its state from a file,
database, and so on. Also, CGI does not manage the communication between multiple
instances of the CGI program, so if multiple clients are accessing your Web server and
requesting the CGI, multiple instances of the CGI program must be loaded, taking up
memory. These multiple instances will not share state, so you will have to write specific
code if you want them to communicate. In other words, CGI programs are represented by
multiple single-threaded applications by default. This is one reason that servlets are a

- 129 -
good choice for Web server application programming. They do not have these limitations.
Servlets are loaded into the server’s address space at load time or at the time of the
initial request. After the initial request, they respond very quickly. Also, because the
servlet can remain resident, it can maintain state between client requests. Figure 6.4
illustrates typical servlet use.

Figure 6.4: Web application architecture with servlets.

Creating a Servlet
In order to create servlets, use the Java Servlet Development Kit, or JSDK, from Sun.
The JSDK consists of two packages: javax.servlet and javax.servlet.HTTP. These
packages contain the classes and interfaces needed to create your own servlets. Your
Web server might also provide a copy of these packages; if so, you don’t have to
download this kit. JSDK 2.0 is found on your CD-ROM or it can be downloaded from
http://java.sun.com/products/java-server/servlets/index.html. After installing the JSDK,
you can begin creating servlets.

The following discussion provides a brief overview of the major concepts in creating
servlets. Chapter 7, “Programming Servlets,” covers these concepts in detail and
provides a number of demonstrations.

GenericServlet

Remember, when you create an applet, you typically subclass java.applet.Applet. With
servlets that aren’t specifically used for HTTP processing, you subclass
javax.servlet.GenericServlet and override several methods. Typically, you will override
init(), which performs any initialization your servlet requires, such as initializing logs,
opening database connections, and so on. You will also usually override service(), which
is called each time your servlet is invoked. When this method is called, it is passed a
javax.servlet.ServletRequest and javax.servlet.ServletResponse object, used to process
the client request.

HTTPServlet

If your servlet will be used primarily for processing HTTP requests, subclass
javax.servlet.HTTP.HTTPServlet. This abstract subclass of GenericServlet implements a
default service() method for processing requests. It calls several other methods that you
typically override to handle the request. The most common methods to override include
doGet() and doPost(). For example, the method doGet() is called when the form requests
are sent in GET, HEAD, or POST format. Each of these request types corresponds to a
standard HTTP request mechanism and is passed objects that represent the request and
the response.

- 130 -
The two most common request types are GET and POST. A GET request sends
information to a servlet using a URL. The results of the GET request should be the
contents of the URL. The URL can contain extra data in the form of a query string
appended to it with a question mark. For example, the URL
www.pri.com/servlets/go?name=stephen passes the key-value pair name=stephen to a
servlet. This key-value string is optional, and most of the requests you make with a
browser when clicking on links are actually handled with GET requests to the server that
do not contain this query string. POST requests send an HTTP body that contains all of
the data being sent to the servlet. Normally, POST requests are used as the action for a
form, especially when a lot of data is being sent to the servlet or CGI script.

Processing Responses

When one of the processing methods of HTTPServlet is called, the typical steps are to
read the values from the request, do any additional processing such as logging or
database access, and then write a response to the client.

To write a response to the client, HttpServletResponse has a method getWriter that


returns a PrintWriter instance. Use this object to write data to the client. All HTTP
responses are expected to include a content type. Because the servlet is handling a
specific client request, the servlet should define this type as shown above. The value for
this content type is included in the HTTP header returned to the client. Possible content
types include text/html, text/plain, image/gif, and any other valid MIME type. Notice that
the type is separated into a major and minor type. All text uses the text major type but
can either be HTML or plain. Images use the image major type and can use a number of
minor types, including gif, jpg, and tiff. The list of acceptable types is constantly growing.
New types are often tested using the x- prefix to indicate that they are experimental. You
can find more information about the available types by browsing to www.ietf.org/ and
looking for a request for comments (RFC) search engine. Then go to RFC 2046. These
RFCs can be hard to read, so the other option is to use your browser’s preferences
dialog to find a list of the types it supports.

In the example that follows, if you don’t specify that the data you are sending is HTML,
the browser parses HTML tags as regular text and just displays the text. For example,
rather than seeing a horizontal rule, you would see <HR> in the browser. To specify the
content type for a servlet’s response, call the HttpServletResponse method
setContentType and pass in an appropriate type. For example, the following line:

response.setContentType("text/html");

tells the browser we are sending HTML.

Example Servlet
The code that follows defines a basic servlet BookReviewServlet that can be used in
place of the CGI program used to display the book review in the section “Processing
Forms with CGI.” The only change to the HTML file is the <FORM> tag. The new tag
looks like this:

<form action=http://localhost:8080/servlet/BookReviewServlet
method=POST>

In the BookReviewServlet code, the servlet performs the same function as the Perl script
by processing the form request and sending back an HTML response page. For
simplicity, only POST is supported.

import java.io.*;
import javax.servlet.*;

- 131 -
import javax.servlet.http.*;

public class BookReviewServlet extends HttpServlet


{
// Write a response to the client.
public void doPost(HttpServletRequest req,
HttpServletResponse res)
throws ServletException, IOException
{
/*
Set the "content type" header of the response so that
the browser knows we are sending HTML, not just raw
text. If you don't do this, the HTML tags will not
be interpreted.
Try commenting out this line to see what happens.
*/
res.setContentType("text/html");
//Get the response's PrintWriter to return text to
client.
PrintWriter toClient = res.getWriter();

// Read form values.


String fName = req.getParameter("fname");
String chp = req.getParameter("favchap");
String writeServlet = req.getParameter("writeservlet");

// Respond to client with a thank you.


toClient.println("<HTML>");
toClient.println("<TITLE>Book Review Response</TITLE>");
toClient.print(fName);
toClient.println(", thank you for your feedback.<BR>");
toClient.print("Your favorite chapter so far is chapter
");
toClient.println(chp + ".<BR>");
toClient.println("<HR>");
toClient.println("You are ");
if(writeServlet == null)
toClient.print("not ");
toClient.println("planning to write a servlet.");

toClient.println("</HTML>");

// Close the writer when response is done.


toClient.close();
}
}

Also notice that doService() is a synchronized method. This is because servlets typically
run in multithreaded environments. Because there is only one instance of the servlet
object created by the Web server, the servlet must be able to service simultaneous
requests. Servlets, therefore, need to ensure that they synchronize access to such
shared resources as their instance variables, database connections, and file streams.

Running the Servlet


- 132 -
JSDK includes a testing application called servletrunner in the JSDK/bin directory. Put
your compiled servlet in the examples directory of the JSDK and then execute
servletrunner. If you make any changes to your servlet, restart the servletrunner. To load
the servlet from the Web browser, specify the location
http://localhost:8080/servlet/ServletName. If servletrunner is on another machine, replace
localhost with the server name. Port 8080 is the default port that servletrunner is on.

Once you are comfortable that your servlet works, you can move it to your production Web
server. You can also use a Web server such as the Java Web Server for testing purposes.
Read the documentation on your server to determine where to put the servlet. For more
information concerning adding servlet support to your Web server, read the documentation
that comes with the JSDK.

Other Server-Side Features


The Java Web Server provides several mechanisms for creating dynamic content,
including server-side includes and servlet chaining. This section provides an overview of
these features. If you are going to use the Java Web Server, you may want to check out
http://jserv.javasoft.com:80/products/java-
server/documentation/webserver1.0.2/index_developer.html for more information on
these two features.

Server-Side Includes
Servlets may be invoked by Web servers to preprocess Web pages as server-side
includes in Web servers that provide complete servlet support. Server-specific
enhancements to the HTML syntax send an indication to the Web server to preprocess.
The following code is an example of HTML syntax that sends this message to the server:

<SERVLET NAME=ServletName>
<PARAM NAME=param1 VALUE=val1>
<PARAM NAME=param2 VALUE=val2>
This text only displays if the server doesnÌt support Server side
includes for servlets
</SERVLET>

The <SERVLET> tag indicates that a servlet ServletName should be loaded and then
called with a particular set of parameters. The output of the servlet is included at this
point in the HTML file returned to the client. You can think of the <SERVLET> tag as a
way of telling the server to “fill in the blank” with values generated by the servlet. You
could use this technique to include a table that is dynamically generated by values in a
database. Figure 6.5 illustrates the output of the following HTML:

- 133 -
Figure 6.5: Output of server-side include.

<HTML>
Here is your order so far:
<HR>
<SERVLET NAME=ProcessOrderServlet>
<PARAM NAME=orderid VALUE=123>
</SERVLET>
<HR>
</HTML>

The HTML indicates to the Web server that a servlet called ProcessOrderServlet should
be called to fill in the HTML that goes between the <HR> tags. The servlet is passed one
parameter, the order id, which is used to access the order database, then outputs the
result shown in Figure 6.5.

Servlet Chaining
The Java Web Server supports chaining local and remote servlets. The input from the
browser is sent to the first servlet in the chain; the output from the last servlet in the chain
is sent back as the response to the browser. Each servlet in the chain has the inputs and
outputs piped to the servlet before and after it, respectively. You could use this feature to
create small servlets that act as data filters or business rules. For instance, a Web page
containing an order to process could submit form data to the first servlet, which validates
the credit card entered on the form. If it is a valid card, the information is passed on to the
order-processing servlet, which checks the inventory database for stock and then
forwards the information to the result servlet, which presents the client with the success
or failure result of its submission. These chains are configured by using the
Administration tool that is part of the Java Web Server or by configuring an association
between a specific MIME type and a list of servlets. This is done in the
mimeservlets.properties file residing in the directory
<server_root>/properties/server/javawebserver/webpageservice/. For more information
on this feature, see the Java Web Server tool kit documentation at
http://jserv.javasoft.com:80/products/java-server/toolkit/index.html.

Note Because servlets must be able to support requests from multiple clients, the
servlet API supports both single-threaded and multithreaded servlets. The
developer still needs to handle synchronizing other resources such as
database connections. Chapter 7, “Programming Servlets,” covers this in
more detail.

- 134 -
Using servlets as server-side includes allows you to create dynamic Web content. Using
servlets in a chain allows you to create small filter services that can parse input from a form
and run it through multiple business rules before sending results back to the client. These
features are specific to the Java Web Server, but other vendors will be adopting them or
something similar as the standards are flushed out.

Summary
Servlets are a natural extension to Java and greatly simplify the task of writing enterprise
solutions in Java. The main benefits of servlets are as follows:

• Java servlets enable you to write server-side logic with the same object-oriented
power of client-side Java.

• Servlet persistence solves much of the overhead and state-management issues


typically encountered with CGI programs.

• Because servlets are written in Java, they can seamlessly take advantage of other
Java technologies such as JDBC, JNDI, and others. Chapter 25, “A Four-Tier Online
Store,” includes an example servlet that accesses an Enterprise JavaBean via JNDI.

• As servlet support is added to more Web servers, servlets will alleviate the need to
make design-level decisions about which server to use—an implementation detail that
does not affect your server-side logic.

Chapter 7, “Programming Servlets,” provides a detailed definition of the process and API
required for creating servlets. Chapter 8, “A Servlet-Based Search Engine,” walks you
through the development of a complete servlet-based search engine that you could use on
your own Web site.

Chapter 7: Programming Servlets


Overview
Servlets represent the extension of applet-style programming onto the Web server.
Servlets are usually written by extending an existing class and implementing one or two
key methods. However, as servlets have become a common method for implementing
server code, they have also become a standard interface between client and server code.
This chapter discusses the basic syntax for creating servlets as well as techniques used
to design, test, and tune your servlets. As you read through the chapter, you may want to
run the examples using the source code on the CD-ROM. In this case, skip to the section
“Running and Hosting Servlets” at the end of the chapter for specific information about
running the examples. It is important to understand the basics of servlet programming at
this point because there are a number of large examples that rely on servlets in later
chapters.

This chapter focuses on the mechanics and concepts of programming servlets and
contains small examples. Later chapters provide examples of full-fledged servlets. In
particular, Chapter 8, “A Servlet-Based Search Engine,” provides a large example of a
servlet that can be used for searching a Web site.

A Basic Servlet
The first step in defining a servlet is to create an object that implements the
javax.servlet.Servlet interface. Once this class is defined, you can implement methods
that handle client requests. There are two standard ways to implement the Servlet
interface. The first is to subclass javax.servlet.GenericServlet. GenericServlet essentially
implements all of the methods of the Servlet interface, as listed in Table 7.1, and provides

- 135 -
a few convenience methods. The second way to implement Servlet is to subclass
javax.servlet.http.HttpServlet. This implementation of Servlet provides more methods for
dealing with servlets interacting with a Web server. Objects can also implement Servlet
directly, although this technique is usually used only in cases in which it is necessary for
the object to belong to a different inheritance hierarchy.

The Web server associates servlets with URLs. The server may also allow a servlet to be
associated with a complete type of URL. For example, Chapter 9, “What Is Server-Side
Scripting?” introduces JavaServer Pages. These pages are compiled by a servlet that
handles all requests to “.jhtml” files. Essentially, the servlet handles a file request type
rather than a particular URL.

When the user accesses a URL represented by a servlet, the Web server first checks to
see if it has loaded that servlet. Some Web servers provide an administrative tool for
preloading servlets; others wait until the first user request before loading the servlet.
Loading the servlet is a two-step process. First, an instance of the servlet’s class is
created using the default constructor, based on the class name provided when the servlet
was configured to a particular URL. Next, the servlet is notified of its loading.

Like applets, servlets are notified of their instance creation and destruction by the servlet
host, usually a Web server. In particular, the servlet is sent the message “init” when it is
first loaded and “destroy” when it is unloaded from the server. These specific messages
are used in place of relying on the constructor to finalize the method, to give the Web
server control over when and how it notifies the servlet. In fact, a server could send init
and destroy to the same servlet object several times, assuming that it is unloaded before
being initialized again.

In the servlet model, only one object is created to service each URL. This object receives
the init, service, and destroy messages for that URL. In other words, the init method is
called once, regardless of the number of requests, and destroy is called once when the
servlet is unloaded. The service method is called on the same servlet object every time
any client accesses that URL on the server. See Table 7.1.

Table 7.1: Servlet Interface Methods

Method Description

void destroy() Cleans up whatever resources are being held.

ServletConfig Returns a servlet config object—in particular,


getServletConfig() the configuration object passed to init.

String getServletInfo() Returns a string containing information about


the servlet, such as its author, version, and
copyright.

void init(ServletConfig) Initializes the servlet.

void service(ServletRequest, Called each time a request is made to the


ServletResponse) servlet.

The init method takes a ServletConfig object as its argument. For servlets that extend

- 136 -
GenericServlet, the configuration object is stored by the servlet for later use in the
GenericServlet’s implementation of init. When subclassing GenericServlet and overriding
the init method, you should call super.init() in the init method to inherit this behavior. The
following code shows the declaration for the init method.

public void init(ServletConfig config) throws ServletException

Using the provided configuration, init prepares the servlet for work. This initialization
might involve creating a network connection, loading a file into memory, or any other
potentially lengthy operation. The server guarantees that it will send init once on loading,
and it will wait to send any service messages to the servlet until init has returned. This
means that init is basically thread safe and will be called only once per servlet load.

If an error occurs during initialization, the servlet throws an exception. The package that
contains Servlet also defines the ServletException and UnavailableException classes.
UnavailableExceptions are thrown during init if initialization cannot complete. There are
two types of unavailable servlets: A servlet can be permanently unavailable, and a servlet
can be temporarily unavailable. A permanently unavailable servlet cannot be recovered.
A temporarily unavailable servlet can recover after some condition is met. A good
example of a permanently unavailable servlet is a servlet configured to use a file that
doesn’t exist. A temporarily unavailable servlet is one that uses a database that is
currently unavailable. The key differentiator is that permanently unavailable servlets
require administrative action. Temporarily unavailable servlets may become available
later without administrative action, or they may be self-correcting.

Table 7.2 shows the UnavailableException methods that provide information to the
servlet host or Web server. The most important method for truly self-correcting servlets is
getUnavailableSeconds. Servlets use this method to tell the server how long the servlet
needs to correct the current problem. All of these values are set by arguments in the
constructor for UnavailableException.

Table 7.2: UnavailableException Methods

Method Description

public boolean isPermanent() Returns true if the servlet requires


administrative intervention before becoming
available.

public Servlet getServlet() The servlet in question.

public int The number of seconds that the servlet


GetUnavailableSeconds() expects to be unavailable.

Servlets implement the destroy method to clean up any outstanding resource. For
example, a servlet might close a network connection or file that it is holding open. The
servlet’s host tries to wait on all service requests to be fulfilled before destroy is sent to
the servlet.

If the servlet is executing long-running operations, it may be told to destroy before all of
the operations are complete. This means that as the developer, if you are using
multithreaded servlets, you should plan for destroy to be called during a long-running
operation. One possible mechanism for dealing with this situation is to have destroy set a

- 137 -
flag and have the servlet check whether the flag is on at the end of each request. If the
flag is true and the request is the last one, the servlet will clean up its resources. Put
simply, the servlet might not clean up in the destroy method, but instead in its service
method, after checking whether destroy was called.

Keep in mind that most servlets should perform reasonably short operations, in order to
provide user responsiveness. In those cases, it is not necessary to plan too extensively
around destroy getting called too early. Servlets that do not require open resources,
created in init and closed in destroy, may not even need to implement the method.

Handling a Request
Once a servlet is initialized, the key method used to interact with it is:

public void service(ServletRequest request,


ServletResponse response) throws ServletException

This service method is made up of a simple interface that takes information about the
request and an object that encapsulates the response. Whenever a request arrives at the
server for a particular servlet, the server wraps up information about the request, creates
a response object to contain information relevant to the response, and calls service. The
service method uses a ServletException, a special type of Exception object, to indicate
when an exception has occurred.

A simple example of the service method follows in the form of the classic HelloWorld
program. The results of accessing this servlet are pictured in Figure 7.1. The bold code
shows that the service method is sent a request object containing the details of the client
request and a response object used to return values to the client. The method writes
“Hello World” to a Web page on the client browser.

Figure 7.1: Hello World.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class HelloWorldServlet extends GenericServlet


{
public void service(ServletRequest request,

- 138 -
ServletResponse response)
throws ServletException,
IOException
{
PrintWriter out;

response.setContentType("text/html");

out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println("Hello World");
out.println("</TITLE></HEAD><BODY>");
out.println("<H1>Hello World</H1>");
out.println("</BODY></HTML>");
out.close();
}
}

Other servlets may have similar versions of service or implementations that are more
complex. The important point is that service is the Web server’s interface to the servlet.
You, as the servlet writer, can do anything you want, as long as Java supports it, inside
the service method. This could include using JNDI to look up the e-mail address in an
LDAP directory or finding inventory items in a database through JDBC.

SingleThreadModel
The next step in implementing a servlet is to decide if it will support multithreading. This is
an important decision for the programmer. If you decide not to support multithreaded
access to the servlet, the servlet should implement the SingleThreadModel interface. This
interface defines no methods and is instead a typing mechanism. Servlets marked with
SingleThreadModel are guaranteed by the server to be accessed only by a single thread.
When a servlet implements SingleThreadModel, it is really saying that it doesn’t want the
service method called from more than one thread.

Several issues go into the decision of servlet multithreading:

1. Does it make sense for the action performed by the servlet to occur twice
simultaneously? For example, if the servlet returns data from a cache, it is reasonable
that the servlet could respond to multiple queries at the same time. However, to
ensure file integrity, you may want a servlet that deletes files to delete only one file at
a time.

2. Does the servlet alter information that may not be thread safe? Does it access a file
or another object that is not thread safe? Does it use a socket to interact with another
application? If so, is access to the socket synchronized? If a resource being accessed
by the servlet is not itself thread safe, the servlet should be single threaded to protect
that resource.

3. Will there be performance implications if the servlet is marked single threaded? If the
requests performed by the servlet in a single-thread model are time consuming, other
requests may be delayed too long. In this case, if possible, the servlet should be
written to support multiple threads.

Keep in mind that supporting multiple threads is more flexible than marking the servlet as
single threaded. Marking it single threaded is certainly easier on the program design, but
it is more limiting in your program design. We will discuss multithreaded servlets in detail
in the upcoming section, “Multithreaded Servlets.”

- 139 -
ServletRequest
Each call to service includes an object that implements the ServletRequest interface. This
object stores information about the request made by the client to the servlet. Request
information comes in three forms. First, there is information about the client/server
environment. Second, there is raw access to the client request. Third, there are
processed versions of the client request. The difference between the last two types of
information is very easy to describe in the context of a servlet that hosts an HTML form.
When the form is submitted, the client sends an encoded string containing the name and
value of each form element. The servlet can access this string directly (the second form),
or it can access the values of each form element by their names (the third form).

All of the methods for ServletRequest are listed in Table 7.3.

Table 7.3: ServletRequest Methods

Method Description

Object Returns the value of the named attribute of the


getAttribute(String) request or null if the attribute does not exist.
Attributes can provide arbitrary, server-specific
information, like access to SSL configurations.

String getCharacter Returns the character set encoding for the input of
Encoding() this request. This information is used by the method
getReader to provide the appropriate reader for the
requests input.

int Returns the size of the request entity data or -1 if not


getContentLength() known. This method is particularly used when a form
POST request is made. Content length will indicate
the size, in bytes, of the input sent by the form. For
CGI programmers, this is equivalent to the
CONTENT_LENGTH environmental variable.

String Returns the Internet Media Type of the request entity


getContentType() data or null if not known. For CGI programmers, this
is equivalent to the CONTENT_TYPE environmental
variable.

ServletInputStream Returns an input stream for reading binary data in the


getInputStream() request body. If the servlet is expecting textual data, it
should use getReader instead. The reader will
account for character encodings; the stream will not.

String getParameter After parsing the input, this method returns a string
(String) containing the lone value of the specified parameter
or null if the parameter does not exist. If the
parameter specified contains multiple values, this
method will return the first one.

Enumeration After parsing the input, this method returns the


getParameterNames() parameter names for this request as an enumeration
of strings or, if there are no parameters or the input

- 140 -
stream is empty, as an empty enumeration.

String[]GetParameterValues(String) After parsing the input, this method returns


the values of the specified parameter for
the request as an array of strings or null if
the named parameter does not exist. This
method is used to access parameters with
multiple values. For example, an HTML list
might send back multiple selections.

String getProtocol() Returns the protocol and version of the request as a


string of the form <protocol>/<major version>.<minor
version>.

BufferedReader Returns a buffered reader for reading text in the


getReader() request body. This reader will be initialized with the
appropriate character encoding to ensure that the
data read is correct.

String getRealPath Applies alias rules to the specified virtual path and
(String) returns the corresponding real path or null if the
translation cannot be performed for any reason.
Passing in the value of / will return the document root
for this servlet.

String Returns the IP address of the agent that sent the


getRemoteAddr() request. For CGI programmers, this is the
REMOTE_ADDR environmental variable.

String Returns the fully qualified host name of the agent that
getRemoteHost() sent the request. For CGI programmers, this is the
REMOTE_HOST environmental variable.

String getScheme() Returns the scheme of the URL used in this


request—for example, “http,” “https,” or “ftp.”

String Returns the host name of the server that received the
getServerName() request. For CGI programmers, this is the
SEVER_NAME environmental variable.

int getServerPort() Returns the port number on which this request was
received. For CGI programmers, this is the
SEVER_PORT environmental variable.

Servlets that are designed to respond to HTML form requests will normally use the
getParameterValue and getParameterValues methods to obtain information about the
form request. Servlets that expect a string of text to be supplied should use getReader to
obtain an appropriate reader; servlets that expect binary input should use getInputStream
to access data from the request.

Later in this chapter, in the HttpServletRequest discussion, an example demonstrates


how to access form information using the request object. Because HttpServlet Request is
a special type of ServletRequest, the majority of the methods used in that example are
from Table 7.3.

Although the remaining methods are self-explanatory, the getAttribute method deserves
further discussion. The getAttribute method is provided by the ServletRequest interface to
provide server-specific information to the servlet. Not all servers provide the same

- 141 -
attributes. As a result, Sun has documented several attributes that its server may provide
and named them in a way that ensures the Sun-specific meaning. These attributes are
listed in Table 7.4.

Table 7.4: Sun-Defined Attributes

Attribute Name Attribute Type Description

javax.net.ssl.cipher_suite string The string name of the


SSL cipher suite in use
if the request was made
using SSL.

javax.net.ssl.peer_certificates array of The chain of X.509


javax.security.cert. certificates that
X509Certificate authenticates the client.
objects This is available only
when SSL is used with
client authentication.

javax.net.ssl javax.net.ssl.SSLSession An SSL session


object, if the request
was made using SSL.

Refer to your server’s documentation for a complete list of the attributes that are provided
to your servlets.

ServletResponse
The server provides the servlet with an object that implements the ServletResponse
interface in each call to service. This object provides methods for sending a response to
the user in the form of a MIME-typed message. The message can be either text or binary,
and like the request, the text provided can be encoded in a number of ways.

The methods provided by ServletResponse are listed in Table 7.5. You will notice
immediately that these objects are set up in a way such that more information is provided
about a generic request than a generic response. This is because it is up to the servlet to
define the response, and in most cases, the response will require only a type, a size, and
a body. HttpServlets will have access to more response parameters that correspond to
the values in an HTTP header. However, because the servlet’s service method is passed
both objects, it can use the information from the request to compose its response.

Table 7.5: ServletResponse Methods

Method Description

String Returns the character set encoding used for this

- 142 -
GetCharacterEncoding() MIME body. Use the setContentType method to
change this encoding. This method is used to create
the appropriate writer for sending text data to the
client.

ServletOutputStream Returns an output stream for writing binary response


getOutputStream() data.

PrintWriter getWriter() Returns a print writer for writing formatted text


responses.

void Sets the content length for this response in bytes. If


setContentLength(int) the servlet knows how much data is going to be
written, it should provide this information here.

void Sets the content type for this response as a standard


setContentType(String) MIME type. The default is text/plain.

Because the content type is used to determine the character encoding for the output
writer, set the content type before accessing the writer. Also, in the case of HTTP style
servlets, the content type is returned to the client in the header of the HTTP reply, while
the output stream and writer return data in the body. This means that you should set the
content type first, before using either method to return data to the client. Bottom line: Set
the content type before sending any output. Also, use the content length, if possible.

ServletConfig
When a servlet is initialized, it is passed an object that implements the ServletConfig
interface. Table 7.6 lists the methods defined in this interface. This object is designed to
provide information to the servlet about the server and how the servlet is currently
configured. Essentially, the configuration object provides two pieces of information. First,
the configuration provides access to configuration parameters defined when the servlet
was installed on the server. Second, the configuration object provides access to the
servlet’s context.

Table 7.6: ServletConfig Methods

Method Description

String Returns a string containing the value of the named


getInitParameter(String) initialization parameter of the servlet or null if the
parameter does not exist.

Enumeration Returns the names of the servlet’s initialization


getInitParameterNames() parameters as an enumeration of strings or an empty
enumeration if there are no initialization parameters.
Use the getInitParameter method to get the value for
each named parameter.

ServletContext Returns the servlet’s context, as defined by the


getServletContext() server.

- 143 -
Depending on the server you install, a servlet on the initialization parameters may be
defined differently. For example, the Java Web Server provided by Sun uses a
configuration applet to set initialization parameters; the JSDK uses a properties file.

ServletContext
The ServletConfig interface provides specific information about the servlet’s
configuration, but the ServletContext interface defines methods that provide information
about the environment in which the servlet itself is running. These methods are listed in
Table 7.7. The context is accessed via the configuration object using the method
getServletContext.

Table 7.7: ServletContext Methods

Method Description

Object getAttribute(String) Like the request method of the same


name, this method provides access to
server-specific information.

String getMimeType(String) Returns the MIME type of the specified file


or null if not known. This method may or
may not be implemented by the server,
and servlets should plan for the possible
null return value.

String getRealPath(String) Applies alias rules to the specified virtual


path and returns the corresponding real
path.

String getServerInfo() Returns the name and version of the


network service under which the servlet is
running.

Servlet getServlet(String) Returns the servlet of the specified name


or null if not found.

Enumeration getServletNames() Returns an enumeration of the Servlet


object names in this server.

void log(Exception, String) Writes the stacktrace and the given


message string to the servlet log file.

void log(String) Writes the given message string to the


servlet log file.

The context provides access to both the servlet log file and the other servlets on the
server. Like the similar feature in AppletContext, access to other servlets may be
considered a security risk and therefore not to be implemented on all servers. Refer to
your server’s documentation for information on what these methods do.

- 144 -
GenericServlet
Perhaps the easiest way to create a servlet is to subclass GenericServlet. GenericServlet
implements the Servlet interface and provides a number of convenient methods for
accessing information from the servlet configuration. These methods are listed in Table
7.8.

Table 7.8: GenericServlet Methods

Method Description

public void destroy() Logs the destruction in the servlet log file.

public String Returns a string containing the value of the


GetInitParameter(String) named initialization parameter or null if the
requested parameter does exist.

public Enumeration Returns the names of the servlet’s initialization


getInitParameterNames() parameters as an enumeration of strings or an
empty enumeration if there are no initialization
parameters.

public ServletConfig Returns a servletConfig object containing any


getServletConfig() startup configuration information for this servlet.
This object was set in the init method.

public ServletContext Returns a ServletContext object, which contains


getServletContext() information about the network service in which
the servlet is running. The context is acquired
from the configuration.

public String Returns a string that contains information about


getServletInfo() the servlet, such as its author, version, and
copyright. This method should be overridden
appropriately by subclasses. For example, you
might return the string “Search Servlet, Stephen
& Scott, version 1.0 1998.”

public void Initializes the servlet by storing the config object


init(ServletConfig) in an instance variable and logs the initialization.

public void log(String) Writes the class name of the servlet and the
given message to the servlet log file.

public void Implements the service method by doing nothing.


service(ServletRequest, This method is intended to be overridden.
ServletResponse) throws
ServletException,
IOException

Many of these methods are covers for ServletConfig or ServletContext methods. These

- 145 -
methods simply forward messages to the appropriate object.

HTTP Servlets
Although GenericServlet provides a good base for writing a servlet, it does not provide
convenient access to HTTP-specific information. In particular, generic servlets do not
distinguish among the varieties of request types provided by the HTTP specification.
HTTP also defines specific headers for requests and has been extended to support
concepts like cookies.

Rather than require all servlets to work via the Web protocol HTTP, the servlet libraries
allow programmers to choose their servlet implementation. In the previous examples, we
used GenericServlet as the parent class. This allowed access to HTTP style requests,
but it also allows other forms of requests such as RMI or CORBA. For servlets intended
to handle only HTTP requests, the javax.servlet.http.HttpServlet class specializes
GenericServlet.

Perhaps the most telling of the methods added by HttpServlet, as listed in Table 7.9, are
the ones used to deal with specific request types. For example, doGet is provided to
handle HTTP GET requests; doPost is provided for HTTP POST requests. The second
important specialization provided by HttpServlet is the creation of special request and
response objects that provide access to HTTP-specific information.

Table 7.9: HttpServlet Methods

Method Description

void Performs the HTTP DELETE operation; the


doDelete(HttpServletRequest, default implementation reports an HTTP
HttpServletResponse) BAD_REQUEST error to the client, indicating
that DELETE operations are not supported.

void Performs the HTTP GET operation; the default


doGet(HttpServletRequest, implementation reports an HTTP
HttpServletResponse) BAD_REQUEST error to the client, indicating
that GET operations are not supported.

void Performs the HTTP OPTIONS operation; the


doOptions(HttpServletRequest, default implementation of this method
HttpServletResponse) automatically determines the HTTP Options
that are supported by checking the Java run
time for the methods defined in the servlet’s
class. If a subclass overrides one of the
doXXX methods, that request option will
automatically show up in doOptions.

void Performs the HTTP POST operation; the


doPost(HttpServletRequest, default implementation reports an HTTP
HttpServletResponse) BAD_REQUEST error to the client, indicating
that POST operations are not supported.

void Performs the HTTP PUT operation; the default


doPut(HttpServletRequest, implementation reports an HTTP
HttpServletResponse) BAD_REQUEST error to the client, indicating
that PUT operations are not supported.

- 146 -
void Performs the HTTP TRACE operation; the
doTrace(HttpServletRequest, default implementation of this method causes
HttpServletResponse) a response with a message containing all of
the headers sent in the trace request. Servlets
will rarely override this method.

long getLastModified Gets the time the requested entity was last
(HttpServletRequest) modified. This time is returned in a long
integer indicating milliseconds since midnight,
January 1, 1970, UTC. The default
implementation returns a negative number,
indicating that the modification time is
unknown. In this case, the modification time
should not be used for GET or other cache
operations.

void Checks the request method and calls the


service(HttpServletRequest, appropriate request handler. For example,
HttpServletResponse) POST requests result in a call to doPost. If the
method is unknown, an error message is sent
to the client.

void service(ServletRequest, Checks whether this is an HTTP request. If


ServletResponse) not, an exception is thrown. If this is an HTTP
request, the request and response objects are
cast appropriately, and the previously defined
service method is called.

Notice that many of these methods report errors to the client if a particular request type is
not supported. These errors are defined and managed in the HttpServletResponse
object, as described in the upcoming section, “HttpServletResponse.” To support HTTP
style servlets, the javax.servlet.http library also provides the class HttpUtils. This class
provides three methods, as listed in Table 7.10, for managing HTTP-specific data.

Table 7.10: HttpUtils Methods

Method Description

StringBuffer getRequestURL( Using information from the request, this


HttpServletRequest) method builds the URL that the client uses to
access the servlet.

Hashtable parsePostData Parses HTML form data as passed using a


(int, ServletInputStream) POST request, stores it in a hash table, and
returns the table. Multivalued element names
will have arrays for their value in the hash
table.

Hashtable parseQueryString Parses a query string, as provided in a GET


(String) request, and builds a hash table of key-value
pairs, where the values are arrays of strings. If
there is only one

- 147 -
For the most part, these methods are used by the library and will probably not be
accessed directly by servlets.

HttpServletRequest
HTTP requests are represented by HttpServletRequest objects. These objects implement
the ServletRequest interface and are created by the server before it calls the servlet’s
service method. The primary function of a request is to provide data to the servlet. This
data comes in several forms, including connection information, request information, and
the data sent with the request. For example, a form submitted by the user will be
represented to a servlet as an HttpServletRequest object. The request can tell the servlet
if this was a POST or GET request; the authentication mechanism, if any, used to identify
the client; and the data contained in the form.

For accessing the data sent with the request, use the methods provided by the
ServletRequest interface, such as getParameter and getReader. The getReader methods
return the raw client input; getParameter processes the input into key-value pairs for easy
access. You should not mix these two mechanisms, because getParameter has to
process all of the data before it can respond and may make the reader unavailable.
Servlet Request also defines some of the standard methods for accessing client
information such as the client’s IP address. The HttpServletRequest class adds methods
to the interface that provide specific HTTP information. These methods are listed in Table
7.11.

Table 7.11: HttpServletRequest Methods

Method Description

String getAuthType() Gets the authentication scheme of this


request. For standard Web server
authentication, this may be “basic.” In this
case, a simple username/password scheme is
used.

Cookie[] getCookies() Gets the array of cookies found in this


request.

long getDateHeader(String) Gets the value of the requested date HTTP


header field of this request. The return value is
a long integer indicating milliseconds since
midnight, January 1, 1970, UTC. Use this long
integer to create a Date object.

String getHeader(String) Gets the value of the requested HTTP header


field of this request.

Enumeration getHeaderNames() Gets the HTTP header names for this request.

int getIntHeader(String) Gets the value of the specified integer HTTP


header field of this request.

- 148 -
String getMethod() Gets the HTTP method (for example, GET,
POST, PUT) with which this request was
made.

String getPathInfo() Gets any optional extra path information


following the servlet path of this request’s URI
but immediately preceding its query string. For
example, if the servlet called go is accessed
by the URL http://server/servlet/ go/run/fast,
this value will be “/run/fast,” the path
information after the servlet request.

String getPathTranslated() Gets any optional extra path information


following the servlet path of this request’s URI
but immediately preceding its query string and
translates it to a real path.

String getQueryString() Gets any query string that is part of the HTTP
request URI. This will be a string that is
included in the URL of the request after a
question mark. For example, the query string
http://server/servlet/go?one has the query
string “one.”

String getRemoteUser() Gets the name of the user making this


request. This value will be empty if no
authentication scheme is in place.

String Gets the session id specified with this request.


getRequestedSessionId()

String getRequestURI() Gets, from the first line of the HTTP request,
the part of this request’s URI that is to the left
of any query string.

String getServletPath() Gets the part of this request’s URI that refers
to the servlet being invoked.

HttpSession Gets the current valid session associated with


getSession(boolean) this request if create is false or, if necessary,
creates a new session for the request if create
is true.

boolean isRequestedSessionId Checks whether the session id specified by


FromCookie() this request came in as a cookie.

boolean Checks whether the session id specified by


isRequestedSessionIdFromUrl() this request came in as part of the URL.

boolean Checks whether this request is associated


isRequestedSessionIdValid() with a session that is valid in the current
session context.

HttpServletRequest also adds new functionality to the request object for managing
session information. Sessions are discussed in detail under the heading “HttpSessions,”
but for now notice the methods for managing the session id and session object.

As an example of the information provided by the request object, we have created a

- 149 -
servlet that displays the headers, parameters, and information from the request back to
the client. Notice that depending on your environment, some of these methods may not
work successfully. See the section on “Debugging Servlets” for more information on how
to find these errors. The results of running this servlet are pictured in Figure 7.2. The
input from a form was included, along with the environmental information. All of the code
for this example is included in the single-class PrintEnvServlet that extends HttpServlet.
The service method is implemented to print information from the request in an HTML
page and return the HTML to the client.

Figure 7.2: PrintEnvServlet results.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class PrintEnvServlet extends HttpServlet
{
public void service(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter out;
Enumeration headers;
String curHeader;

The content type for a servlet's response is set using the response object. In this case,
we will return HTML, which has the MIME type text/html. Once the content type is set, the
servlet prints the beginning of the HTML page.

response.setContentType("text/html");

out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println("Print Environment");
out.println("</TITLE></HEAD><BODY>");

Next, the servlet uses the HttpUtils class and the request object to recreate the exact
URL used by the client to access the servlet. For example, this code constructs a string
such as http://zero:8080/servlet/printer.

- 150 -
out.println("<H1>Requested URL</H1>");
out.println(HttpUtils.getRequestURL(request).toString());
out.println("<BR>");

After printing the request URL, the servlet prints all of the header fields in the request and
their associated values.

out.println("<H1>Headers</H1>");
headers = request.getHeaderNames();

while(headers.hasMoreElements())
{
curHeader = (String) headers.nextElement();

out.println(curHeader

+"="+request.getHeader(curHeader)+"<BR>");
}

HttpServletRequest provides a number of direct methods for accessing information about


a request. The following code displays all of these values with their corresponding
names. Table 7.11 describes the meaning of each value. Some of these values overlay
the headers printed above.

out.println("<BR>");
out.println("<H1>Request Information</H1>");

try
{

out.println("AuthType="+request.getAuthType()+"<BR>");
out.println("Scheme="+request.getScheme()+"<BR>");
out.println("Method="+request.getMethod()+"<BR>");

//Crashes on some servers.


// out.println("Char Encoding="
+request.getCharacterEncoding()+"<BR>");

out.println("Request URI="
+request.getRequestURI()+"<BR>");
out.println("Request protocol="
+request.getProtocol()+"<BR>");
out.println("Servlet path="
+request.getServletPath()+"<BR>");
out.println("Path
Info="+request.getPathInfo()+"<BR>");
out.println("Path Translated="
+request.getPathTranslated()+"<BR>");
out.println("Query String="
+request.getQueryString()+"<BR>");
out.println("Content length="
+request.getContentLength()+"<BR>");

- 151 -
out.println("Content type="
+request.getContentType()+"<BR>");
out.println("Server name="
+request.getServerName()+"<BR>");
out.println("Server port="
+request.getServerPort()+"<BR>");
out.println("Remote user="
+request.getRemoteUser()+"<BR>");
out.println("Remote address="
+request.getRemoteAddr()+"<BR>");

//Times out if no DNS.


// out.println("Remote host="
+request.getRemoteHost()+"<BR>");
}
catch(Exception exp)
{
out.println("Exception: "+exp+"<BR>");
}

Next, the servlet checks any parameters and prints them. These will be defined by forms
or manually by the HTML and browser. Parameters can be single valued or multi valued.
This code checks for both and, if the parameter is multivalued, prints a list. Otherwise, a
single string is printed.

out.println("<BR><H1>Parameter Information</H1>");

Enumeration parameters;
String[] values;
String curParam;
String value;
int i,max;

parameters = request.getParameterNames();

while(parameters.hasMoreElements())
{
curParam = (String) parameters.nextElement();

values = request.getParameterValues(curParam);
value = request.getParameter(curParam);

out.println(curParam+"=<BR><UL>");

if((values != null)&&(values.length>1))
{
max = values.length;

for(i=0;i<max;i++)
{
out.println("<LI>"+values[i]);
}

- 152 -
}
else if(value != null)
{
out.println("<LI>"+value);
}

out.println("</UL>");
}

Finally, the HTML page is concluded, and the output stream to the client is closed.

out.println("</BODY></HTML>");

out.close();
}
}

The HTML page shown activates the servlet. Notice that the form action URL has extra
path information passed in order to show, in the servlet and in the output, how this
information is passed to the servlet and processed as PathInfo data. A list of options is
also provided in order to show how multivalued input is handled.

<HTML>
<HEAD>
<TITLE>
Print Env Form
</TITLE>
</HEAD>
<BODY>
<FORM METHOD=POST
ACTION="/servlet/printenvservlet/xtrapathinfo">

<INPUT NAME="Field" VALUE="Field Value"><BR>


<SELECT SIZE=2 NAME="List" MULTIPLE>
<OPTION NAME="ListItem1" VALUE="Item One" SELECT>Item One
<OPTION NAME="ListItem2" VALUE="Item Two" SELECT>Item Two
<OPTION NAME="ListItem3" VALUE="Item Three">Item Three
</SELECT><BR>
<INPUT TYPE="Submit" NAME="Submit" VALUE="Go">

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

On the CD-ROM, this file is called PrintEnvTester.html. When used, it results in a page
like the one pictured in Figure 7.2.

This section demonstrated how the request object can be used to process client input.
The next section demonstrates how the response object can be used to output results to
the client.

HttpServletResponse
Like the HttpServletRequest object, HttpServletResponse provides HTTP-specific access

- 153 -
to the servlet’s response. This information includes the ability to set headers, add
cookies, encode URLs, send error codes, and redirect the client to another URL. These
methods are listed in Table 7.12.

Table 7.12: HttpServletResponse Methods

Method Description

void addCookie(Cookie) Adds the specified cookie to the HTTP


header of the response.

boolean Checks whether the response message


containsHeader(String) header has a field with the specified name.

String encodeRedirectUrl Encodes the specified URL for use in the


(String) sendRedirect method or, if encoding is not
needed, returns the URL unchanged. This
method is used when the session
management system requires URL encoding
in place of cookies.

String encodeUrl(String) Encodes the specified URL by including the


session ID in it or, if encoding is not needed,
returns the URL unchanged. This method is
used when the session management system
requires URL encoding in place of cookies.

void sendError(int) Sends an error response to the client using


the specified status code and a default
message.

void sendError(int, String) Sends an error response to the client using


the specified status code and a descriptive
message.

void sendRedirect(String) Sends a temporary redirect response to the


client using the specified redirect location
URL.

void setDateHeader(String, Adds a field to the response header with the


long) given name and date-valued field.

void setHeader(String, Adds a field to the response header with the


String) given name and value.

void setIntHeader(String,int) Adds a field to the response header with the


given name and integer value.

void setStatus(int) Sets the status code for this response.

void setStatus(int, String) Sets the status code and message for this
response.

- 154 -
Four of these methods are used to notify the client using status codes. These methods
include both sets of the setStatus and sendError methods. The two methods called
setStatus are used if no error occurs, but the servlet will not return a document if, for
example, a put was successful or a file is temporarily unavailable. The sendError
methods also cause the servlet to not return a document, but in that case, the client is
told that an error has occurred. In both cases, a status code and optional messages can
be sent to the client in place of any other data.

The available status codes are provided as static variables of HttpServletResponse.


Some of the more common ones are listed in Table 7.13. The complete list is available in
the javax.servlet documentation.

Table 7.13: Common HTTP Status Codes

HttpServletResponse
Static Variable Description of Status Code

SC_ACCEPTED Indicates that a request was accepted for processing


but was not completed.

SC_FORBIDDEN Indicates that the server understood the request but


refused to fulfill it.

SC_METHOD_NOT_ALLOWED Indicates that the method specified is not allowed for


that URL.

SC_NOT_FOUND Indicates that the requested resource is not available.

SC_NOT_MODIFIED Indicates that a conditional GET operation found that


the resource was available and not modified.

SC_OK Indicates that the request succeeded normally.

SC_REQUEST_TIMEOUT Indicates that the client did not produce a request


within the time that the server was prepared to wait.

SC_SERVICE_UNAVAILABLE Indicates that the HTTP server is temporarily


overloaded and unable to handle the request.

SC_UNAUTHORIZED Indicates that the request requires HTTP


authentication.

When a servlet encounters a problem from which it cannot recover, it should use the
status codes and error codes in Table 7.13 to indicate the error to the client.

The following example demonstrates how a servlet can return non-HTML data. In this
case, the servlet returns the data for an image. The motivation of this example was to
have a servlet that rotated through a set of banner ads. The ads are stored in a directory,
and when the servlet is initialized, it reads the directory for available images. When a
request is made, the servlet returns the next image in the list. The directory of images is
indicated to the servlet via an initialization/configuration parameter to maximize flexibility.

- 155 -
We implemented the getLastModified method to indicate that the resource is always new,
always just modified. Because this servlet returns an image, it would normally be used as
the SRC value for an IMG tag, but it could be accessed directly. Figure 7.3 shows what it
looks like to directly access the ad rotator.

Figure 7.3: AdRotator.

AdRotatorServlet extends HttpServlet and, for simplicity, marks itself single threaded by
implementing SingleThreadModel. A Vector instance variable called imageFiles is used
to keep a list of images; an integer called curIndex is used to keep track of the current
image to display. These two variables are combined so that the servlet returns a different
image from the list on each request.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class AdRotatorServlet extends HttpServlet


implements SingleThreadModel
{
Vector imageFiles;
int curIndex;

The init method loads the available images into the imageFiles Vector based on the
imageDir configuration parameter. Only GIF and JPG files are supported for simplicity.

public void init(ServletConfig config) throws


ServletException
{
String imageDirName;
File imageDir=null;
String[] files;
int i,max;
File curFile;

super.init(config);

imageFiles = new Vector();

imageDirName = getInitParameter("imagedir");

- 156 -
if(imageDirName != null) imageDir = new
File(imageDirName);

if((imageDir!=null) && imageDir.exists()


&& imageDir.isDirectory())
{
files = imageDir.list();

max = files.length;

for(i=0;i<max;i++)
{
if(files[i].endsWith("jpg")||
files[i].endsWith("gif"))
{
curFile = new File(imageDir,files[i]);
imageFiles.addElement(curFile);
}
}
}
else
{
log("Cannot find image dir: "+imageDirName);
}
}

When the servlet receives a GET request, it gets the next image file and outputs the
correct content type. Then, using standard Java I/O, the content of the image is sent to
the client. If no images are available, the SERVICE_UNAVAILABLE error is sent to the
client. The NOT_FOUND error is used if the image file is, for some reason, in the list but
not on the disk.

public void doGet(HttpServletRequest request,


HttpServletResponse response)
throws ServletException, IOException
{
File curFile;
int len = imageFiles.size();
String ctype;
String fileName;
FileInputStream fileIn;
BufferedInputStream bufIn;
OutputStream out;
ServletContext ctxt;
int cur;

if(len>0)
{
curFile = (File)
imageFiles.elementAt(curIndex);
fileName = curFile.getName();

ctxt = getServletConfig().getServletContext();

- 157 -
ctype = ctxt.getMimeType(fileName);

if(ctype == null)
{
if(fileName.endsWith(".jpg"))
{
ctype = "image/jpeg";
}
else
{
ctype = "image/gif";
}
}

response.setContentType(ctype);

try
{
fileIn = new FileInputStream(curFile);
bufIn = new BufferedInputStream(fileIn);

out = response.getOutputStream();

while((cur=bufIn.read())!=-1)
{
out.write(cur);
}

out.close();
bufIn.close();
}
catch(FileNotFoundException exp)
{
response.sendError(
HttpServletResponse.SC_NOT_FOUND);
}
catch(Exception exp)
{
response.sendError(
HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}

curIndex= (curIndex+1)%len;
}
else
{
response.sendError(
HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
}

public long getLastModified()

- 158 -
{
return System.currentTimeMillis();
}
}

This example also demonstrates how to use the error codes in the try/catch blocks to
indicate a nonrecoverable problem in the servlet. In this case, different status codes are
used to indicate that a file is no longer available or an unknown exception occurred.

You could also implement this servlet using redirection. In this case, the servlet would
use the method sendRedirect, along with the appropriate URL back to the client. Then
the client would make a new request to the server for the specified resource. The servlet
could also be extended to associate links with ads.

The opposite version of this ad rotator that sends images is a servlet that supports file
uploads. An example of this type of servlet is provided on the CD-ROM in the chapter_07
directory under the fileupload package. This example is a larger exercise in parsing the
input to the servlet to determine the start and end for each file, so we have not included it
in the text. However, we strongly suggest that you look at this example if you are thinking
about supporting file uploading on your server. Many servers provide their own support
for uploads, but it can’t hurt to know the basics of what they are doing.

HttpSession
One of the more powerful features provided by the HTTP servlet library is the ability to
track session information. A session is a single continuous interaction with a particular
user. The session-tracking mechanism allows an arbitrary collection of objects to be
associated with a session. The actual implementation of session tracking is server
dependent. One common mechanism is to use an id to track the session and associate a
hash table of some sort with this id. The hash table is stored in memory throughout the
session.

In some servers with a large number of sessions open, the data may be stored to disk
using serialization. When a session begins, the id is created. This id is passed between
the client and server to identify the session. Remember that HTTP is a stateless protocol,
so the client and server are not always in contact. By sending the id back and forth, the
client and server are able to maintain an ongoing session. You as a programmer get
access to the session via the HttpSession object and can manipulate or query it using the
methods in Table 7.14.

Table 7.14: HttpSession Methods

Method Description

long getCreationTime() Returns the time the session was created, in


milliseconds, since midnight, January 1, 1970,
UTC.

String getId() Returns the session’s id.

long getLastAccessedTime() Returns the last time the client sent a request
using this session id, in milliseconds, since
midnight, January 1, 1970, UTC.

- 159 -
HttpSessionContext Returns the session context.
getSessionContext()

Object getValue(String) Returns the object value based on the string


name.

String[] getValueNames() Returns an array of the names of all the objects


in the session.

void invalidate() Causes this representation of the session to be


invalidated and removed from its context.

boolean isNew() Returns true if the session is new, false


otherwise.

void putValue(String,Object) Adds an object to the session with the given


name.

void removeValue(String) Removes the value with the specified name


from the session.

The current HttpSession is acquired from the HttpServletRequest using the method
getSession. This method takes a single Boolean argument that indicates whether the
session exists and whether it should be created if it does not. By using this flag, you can
wait to create a session and simply check if one currently exists. On large Web sites, this
is an important technique. To see why, imagine an online store. Thousands of people are
shopping, but only those with a shopping cart need a session. Limiting sessions to those
people can reduce server resource usage dramatically.

Once you get the session object, you can use it to access the session data as a set of
named values. There are also methods for getting the session’s id and information about
its creation. All of these are listed in Table 7.14.

When a session is first created, it is considered new. The session remains new until the
client “knows” about the session. There are two ways the client learns about the session.
First, the session-tracking mechanism tries to use cookies to tell the client about the
session id. If the browser supports and accepts the cookie, this mechanism is transparent
to the programmer. The other way to tell the client about the session is to send the
session id as part of a URL. In this case, the programmer must encode all URLs sent to
the client so that they include the session id. Obviously, cookies are easier to program
with, but URL encoding is more portable between browsers. However, URL encoding
works only if all the servlets and pages that contain URLs on the site encode them
appropriately. This is often too much to ask of a large site, and many simply do not
support sessions on browsers that do not support cookies. To encode a URL, use the
methods encodeUrl and encodeRedirectUrl in the HttpResponse object.

The values in a session are all objects. On the Java Web Server, it is helpful to make
these objects serializable so that they can be saved to disk, if necessary. However, any
object will do. If you want to store custom objects, you can also implement the
HttpSessionBindingListener interface. In this case, the object is notified when it is added
or bound to a session and again when it is unbound. HttpSessionBindingListener defines
the methods:

public void valueBound(HttpSessionBindingEvent event)


public void valueUnbound(HttpSessionBindingEvent event)

The server calls these methods when the object is bound and unbound from the session.
You might use this notification to update cached information or free resources being held

- 160 -
by the object. For example, you might associate objects in a session with Enterprise
JavaBeans. When the object is unbound, the relationship can be discontinued.

Normally, it is up to the server configuration to determine when a session is no longer


valid. Often this is a question of how much time has passed since the last access, but
many servers are configurable. You can also invalidate a session directly by using the
invalidate method of HTTPSession.

The following example code shows a simple servlet that keeps a counter for each time
the servlet is accessed in the given session. It also prints some of the information
provided by the session to the servlet.

The first time this servlet is accessed, it sets the counter to 0 and displays a page like the
one in Figure 7.4. Accessing the servlet again displays a page like the one in Figure 7.5.

Figure 7.4: First time to servlet.

Figure 7.5: Subsequent access to servlet.

All the code for this servlet is included in the doGet method. The bold code creates the
session and checks whether it is new before constructing the appropriate output. In all
cases, a string containing a link back to the servlet is included for the user to press.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class SessionInfoServlet extends HttpServlet

- 161 -
{
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
HttpSession session = request.getSession(true);

response.setContentType("text/html");
PrintWriter out = response.getWriter();

out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>");
out.println("Session Info Servlet");
out.println("</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");

if(session.isNew())
{
out.println("<H1>New Session</H1>");
out.println("The count is set to 0.");

session.putValue("sessioninfo.count",new Integer(1));
}
else
{
Integer count;
int intCount=0;

count = (Integer)
session.getValue("sessioninfo.count");
if(count != null) intCount = count.intValue()+1;

out.println("<H1>Session Information</H1>");
out.println("The count is set to "+intCount+".");
out.println("<BR>");

session.putValue("sessioninfo.count"
,new Integer(intCount));

out.println("Session ID: " + session.getId());


out.println("<BR>");
out.println("Creation Time: "
+ (new Date(session.getCreationTime())));
out.println("<BR>");
out.println("Last Accessed Time: "
+ (new
Date(session.getLastAccessedTime())));
out.println("<BR>");
}

out.println("<A HREF=\ "");

- 162 -
out.println(response.encodeUrl(request.getServletPath()));
out.println("\ ">");
out.println("Press Here");
out.println("</A>");
out.println("To reload the page, with url encoding.");
out.println("</BODY>");
out.println("</HTML>");
out.close();
}
}

If you try this servlet, you will find that reloading the Web page increments the counter
and updates the last accessed time. Also, if you leave the servlet alone long enough—
about 10 to 30 minutes—the session expires, and the next time you access it, you will get
a new counter. This expiration time is usually configurable by the server administrator.

Cookies
Sessions provide a method for storing data about a current user interaction, but they are
not intended for storing information permanently. Cookies, on the other hand, are key-
value pairs that a servlet can associate with a client and that can have an arbitrary
expiration time. The drawbacks for cookies are threefold. First, they can only be strings.
Second, cookies are stored on the client, so they take up client disk space; as a result
most browsers limit their number and size. As a guideline, each server can assign 20 or
so cookies to the client, with a total size of 4K. Finally, cookies have to be sent over the
Internet with each request. In HTTP, the cookies are part of the header for a request and
reply.

The good thing about cookies is that they are easy to program, are flexible, and can be
used to store small pieces of information about a particular program on the client’s
machine, thus freeing up server resources. Cookies are also associated with a specific
server and path, preventing other servers from reading the information they contain.
Cookies can be assigned expiration dates, making them persist between sessions. The
servlet library uses the class Cookie to represent the cookies provided with an
HttpRequest or returned with a response. Table 7.15 shows the methods provided by
Cookie.

Table 7.15: Cookie Methods

Method Description

String getComment() Returns the comment describing the purpose of this cookie
or null.

String getDomain() Returns the domain of this cookie. The cookie’s domain
indicates the set of computers that the cookie will be sent
to. For example, if the domain is sun.com, the cookie will
be sent only to sun.com computers.

int getMaxAge() Returns the maximum specified age of the cookie in


seconds.

- 163 -
String getName() Returns the name of the cookie.

String getPath() Returns the prefix of all URLs for which this cookie is
targeted. For example, if the cookie is for only
http://sun.com/servlet/cookiecounter, the path is
/servlet/cookiecounter. Subdirectories will also see the
cookie, but parent and sibling directories will not.

boolean getSecure() Returns the value of the secure flag. If the cookie is
secure, it will be sent over only secure channels such as
https, not over standard http.

String getValue() Returns the value of the cookie.

void Used by the Web browser to indicate its purpose to the


setComment(String) user.

void Sets the cookie’s domain.


setDomain(String)

void setMaxAge(int) Sets the cookie’s maximum age in seconds. A negative


value tells the cookie that it should persist for only the
current session. Zero means that the cookie should be
deleted. A positive number indicates the time, in seconds,
for which the cookie should persist.

void Sets the cookie’s path as described above.


setPath(String)

void Turns the secure flag on and off.


setSecure(boolean)

void Sets cookie’s value.


setValue(String)

To determine the cookies for a request, use the HttpServletRequest object’s getCookies
method. This returns an array of Cookie objects. To associate cookies with the response,
use the HttpServletResponse method addCookie. This call should always happen before
you send HTML to the client, because the HTML body may force the HTTP header to be
sent, and the cookie must be part of the header. If you have cookies from the response
that you want to return to the client, add them using addCookie as well.

The following code example implements a basic counter using a cookie. Another cookie
is assigned to track the initial time that the first cookie was assigned. This second cookie
doesn’t change, but it needs to be reassigned to the response to ensure its continued
existence. The code for creating and adding the cookies is highlighted to draw attention
to it. The surrounding code is used to increment over the list of available cookies and
display a page to the user, indicating the results as pictured in Figure 7.6.

- 164 -
Figure 7.6: Cookie servlet results page.

import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
public class CookieCounterServlet extends HttpServlet
{
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
response.setContentType("text/html");
PrintWriter out = response.getWriter();

//Get list of current cookie values from request object.


Cookie cookies[] = request.getCookies();
int i,max=0;
String countStr=null;
String createStr = null;
Cookie curCookie=null;
Cookie countCookie=null;

if(cookies != null) max = cookies.length;

for(i=0;i<max;i++)
{
curCookie = cookies[i];

if("count".equals(curCookie.getName()))
{
countStr = curCookie.getValue();
countCookie = curCookie;
}
else if("create".equals(curCookie.getName()))
{
createStr =
URLDecoder.decode(curCookie.getValue());

- 165 -
//Don't change just re-add
response.addCookie(curCookie);
}
else
{
response.addCookie(curCookie);
}
}

//Set the cookie first, since it goes in the header.


if((countStr == null)||(countCookie == null))
{
countStr = "0";
curCookie = new Cookie("count",countStr);
response.addCookie(curCookie);

createStr = (new Date()).toString();


createStr = URLEncoder.encode(createStr);

curCookie = new Cookie("create"


,createStr);

createStr = URLDecoder.decode(createStr);
response.addCookie(curCookie);
}
else
{
int intCount=0;

intCount = Integer.parseInt(countStr) + 1;
countStr = String.valueOf(intCount);
countCookie.setValue(countStr);

response.addCookie(countCookie);
}
out.println("<HTML>");
out.println("<HEAD>");
out.println("<TITLE>");
out.println("Cookie Counter Servlet");
out.println("</TITLE>");
out.println("</HEAD>");
out.println("<BODY>");

out.println("The count is set to "+countStr+".");


out.println("<BR>");
out.println("The creation time was "+createStr+".");
out.println("</BODY>");
out.println("</HTML>");
out.close();
}
}

- 166 -
To ensure that the data sent in the cookie referenced in this code is valid HTTP style
data, it is encoded with the URLEncoder class provided in java.net. However, we must
write our own URLDecoder to recover the original data string. This class is provided on
the CD-ROM.

Because cookies are limited by the client, keep in mind the total use of cookies by your
Web server, including session ids, when adding new cookies to the list. One interesting use
of cookies—and a way around the size limitations—is to create your own sessions by
creating ids and storing them in cookies. Associate the ids with data in a file or in the
database. This allows the session to be persistent with the session id on the client and the
session information on the server.

Multithreaded Servlets
The section “SingleThreadModel” discussed the decision of whether or not to make a
servlet multithreaded. Given the environment that servlets live in, the question remains:
Should a servlet be multithreaded, and if it is, how do we write it? Put simply, a
multithreaded servlet is one that allows its service method to be called by more than one
thread at a time. This means that any data accessed in service must be protected and
thread safe. This section covers some of the techniques specific to servlets that support
multiple threads.

There are two basic ways to handle thread issues in servlets. One way is to make sure
that each request works independently of the others, without sharing data. The other
mechanism is to use synchronization to protect shared resources. For servlets that can
work autonomously, handling each request with resources that are independent of other
requests, write them that way. This is especially easy for servlets that perform an
algorithmic task and don’t actually return data from another source. These servlets can
usually store all of the data they need in local variables, preventing overlap between one
thread’s execution of the service method and another thread’s.

In the case of a servlet that shares resources between requests, the shared resources
are often files or database connections. Sometimes you might also share objects in
memory. Protecting files and connections can be a complex task because the object
representing that resource may not be able to provide true thread-safe access. For
example, a file can’t protect itself between the time you ask if it exists and the time you
test its length. There is always a chance that in this small amount of time, another thread
will execute code to delete the file.

As an example of one way to deal with this situation within a servlet, we have created a
FileLock object listed in the code below. This object uses standard lock/unlock semantics
to protect a file object. Servlets can use these lock objects to protect their access to
shared files. Unfortunately, this object is limited to working within a single virtual machine
and requires the program to be written with locking in mind. Although several servlets
may share the FileLock class and thus the locks for that server, another Web server
instance or another program can still create an unsafe situation. The solution to these
situations is to minimize the chance of their occurrence by copying files, caching, and so
on.

import java.io.*;
import java.util.*;

public class FileLock extends Object


{
private boolean locked;
private File file;

private static Hashtable locks;

- 167 -
The getLockFor method gets a file lock for a file. It is synchronized so that multiple
requests block until the first one is finished.

public static synchronized


FileLock getLockFor(String path)
{
FileLock retVal = null;
File tmp = new File(path);
String absPath = tmp.getAbsolutePath();

try
{
if(locks == null)
{
locks = new Hashtable();
}
else
{
retVal = (FileLock) locks.get(absPath);
}

if(retVal == null)
{
retVal = new FileLock(tmp);
locks.put(absPath,retVal);
}
}
catch(Exception exp)
{
retVal = null;
}

return retVal;
}

Each FileLock object knows which File it is associated with and stores this information in
the file instance variable.

protected FileLock(File f)
{
file = f;
}
public File getFile()
{
return file;
}

When a thread attempts to lock the file, it waits until the locked flag is not true or it can
assign a time-out and only wait until the time-out occurs. Time-outs result in exceptions.

//Waits to acquire a lock.


public synchronized void lock()
{

- 168 -
while(locked)
{
try
{
wait();
}
catch(Exception exp)
{
}
}

locked = true;
}

//Waits to acquire a lock.


//timeout in millis
public synchronized void lock(int timeout)
throws InterruptedException
{
while(locked)
{
wait(timeout);
}

//If wait throws and exception


//we don't get here.
locked = true;
}

Unlocking the FileLock sets the locked flag to false and notifies any waiting threads that
the lock is not available.

//Notifies threads waiting for lock.


public synchronized void unlock()
{
locked = false;
notifyAll();
}
}

The following servlet called FileLockingServlet was written to test the file lock and
appends messages to a specific file. By using the lock, the servlet is guaranteeing that
each message will be written fully before the next message begins. Locks are obtained
from the FileLock class, by file path. Each lock should be unique for a specific path. Once
the lock is acquired, it can be locked. There are two versions of the lock method: one
takes a time-out and the other doesn’t. If you use the time-out and the time-out occurs,
you will get an exception. Handle this exception appropriately, keeping in mind that the
file lock was not acquired if the time-out occurred and the file is not safe to access. When
the servlet is done with the file, it unlocks it.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

- 169 -
public class FileLockingServlet extends GenericServlet
{
public void service(ServletRequest request,
ServletResponse response)
throws ServletException, IOException
{
PrintWriter out;
String message;
FileOutputStream fileOut;
PrintWriter log;
FileLock lock;
File file;

response.setContentType("text/html");

out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println("Log Tester");
out.println("</TITLE></HEAD><BODY>");

out.println("<H1>Logged</H1>");

message = request.getParameter("Message");

out.println(Thread.currentThread()+" "+message);

out.println("</BODY></HTML>");

/*
The servlet gets a file lock for the locktest.txt file.
This may block if other clients are trying to access
the
same file.
*/
lock = FileLock.getLockFor("c:\ \ temp\ \ locktest.txt");
lock.lock();

// Once the file is actually locked, read in the file.


file = lock.getFile();
fileOut = new

FileOutputStream(file.getAbsolutePath(),true);
log = new PrintWriter(fileOut,true);
log.println(Thread.currentThread().hashCode()+" "
+message);
log.close();

// Done with the file, release it for other clients.


lock.unlock();
out.close();
}
}

- 170 -
Another way to deal with the problem of shared resources is to create a cache. If the
resources are used only for reading and aren’t changed, they can be cached in memory
and accessed freely. Reading resources is not a problem with multiple threads; writing is
the problem. We have even heard of one example in which a company created a servlet
that cached an entire directory of files and handled all requests from this read-only cache.
Although this is extreme, the authors of this servlet report no thread issues, and they say
that the servlet’s performance is great.

Servlets and Applets


Servlets are a powerful mechanism for serving dynamic Web pages. They have begun to
fill an important role in Java client/server programming. Many developers are creating
applet/servlet pairs that cooperate to form a complete application. Applications are often
split for reasons that include improving performance, minimizing network traffic, and
centralizing business logic. The decision to use servlets and applets is often one of
portability and accessibility. Applets run on most browsers, making them a portable client
choice. Servlets run as part of the Web server, making them accessible via HTTP
through most corporate firewall configurations. By splitting a program into this type of
pair, the programmer is basically guaranteed that anyone can run an application.

Connecting an applet to a servlet is really more of an applet programming problem than a


servlet one. The applet can use standard HTTP to communicate with the servlet, so the
servlet doesn’t need to do anything special. However, for servlets that are designed to
talk to an applet, the servlet can optimize its communication by return text or binary data
instead of HTML. The following example implements the classic Eliza computer
psychologist as an applet/servlet pair. The applet displays a user interface, and the
servlet performs all of Eliza’s processing. The engine for Eliza is provided in a special
package, augmented slightly from the public domain package written by Charles Hayden.
(Thanks, Charles!)

As you can see from the servlet code that follows, both GET and POST requests are
handled, and the Eliza engine is used to handle the incoming message. Plain text is
returned to minimize the applet’s job in interpreting it. The DebugLog object, discussed in
the section “Debugging Servlets,” is also used. This servlet has two init param eters for
debugging and one for defining the script file Eliza uses. This script file is provided on the
CD-ROM with the other files in the Eliza package and can be edited to respond
differently.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import Eliza.*;

public class ElizaServlet extends HttpServlet


implements SingleThreadModel
{
ElizaMain eliza;
DebugLog logger;

The initialization for this servlet prepares the DebugLog (discussed later) and loads the
Eliza engine based on the configured script file.

public void init(ServletConfig conf) throws ServletException


{
super.init(conf);

- 171 -
String logFile,logServer,scriptFile;
int res;

logFile = getInitParameter("logfile");
logServer = getInitParameter("logserver");
scriptFile = getInitParameter("scriptfile");

eliza = new ElizaMain();

if((logFile != null)||(logServer != null))


{
logger = DebugLog.getSharedLog();

synchronized(logger)
{
if(!logger.initialized())
{
if(logServer != null)
{
logger.logTo(logServer);
}
else
{
logger.logTo(new File(logFile));
}
}
}
}

try
{
res = eliza.readScript(scriptFile);
eliza.setLog(logger);
}
catch(Exception exp)
{
res = -1;
logger.log(exp);
}

if(res != 0) logger.log("Couldn't create eliza main.");


}

When a GET or POST request is received, the servlet calls its internal
respondToMessage method to handle the request.

public void doGet(HttpServletRequest request,


HttpServletResponse response)
{
String message;

message = request.getParameter("message");

- 172 -
respondToMessage(message,response);
}

public void doPost(HttpServletRequest request,


HttpServletResponse response)
{
String message;

message = request.getParameter("message");

respondToMessage(message,response);
}

The respondToMessage method checks if a message was entered. If no message was


sent then a welcome reply is returned. If a message was sent, Eliza is queried. If Eliza is
unavailable, an apology is returned to the user. All responses are simply text messages.

protected void respondToMessage(String message


,HttpServletResponse response)
{
PrintWriter out=null;
String reply;

try
{
if(message==null)
{
reply = "Please enter a message to Eliza.";
}
else if(eliza != null)
{
reply = eliza.processInput(message);
}
else
{
reply = "Sorry, Eliza is not available.";
}
}
catch(Exception exp)
{
reply = "I am having trouble hearing, "
+"please repeat.";
logger.log(exp);
}

try
{
response.setContentType("text/plain");

out = response.getWriter();

out.println(reply);
}

- 173 -
catch(Exception exp)
{
}
finally
{
if(out != null) out.close();
}
}

public void destroy()


{
logger.closeLog();
}
}

The applet for Eliza creates a simple user interface and responds to action events from
the text field. This applet is pictured in Figure 7.7. It is certainly not pretty, but it provides
the necessary code to demonstrate applet servlet messaging. Standard JDK 1.0 event
handling is used to maximize portability, although the applet has been tested only on
Netscape Navigator 3.0 and 4.0.

Figure 7.7: The Eliza applet.

The majority of the applet/servlet code is in the action method. In the following code, the
applet checks whether the parameters say to use GET or POST to talk to the servlet. If
POST is used, the applet creates a URLConnection; otherwise, a URL with a query string
is used to connect to the servlet. POST messages require the connection to support
output, in this case a URL-encoded key-value pair containing the user’s message to
Eliza. Notice the one piece of code changing the content type. This is required to get the
applet to work in Netscape. Once the message is posted to the servlet, the applet reads
the response as plain text and displays it in a label.

import java.awt.*;
import java.applet.*;
import java.net.*;
import java.io.*;

public class ElizaApplet extends Applet


{
Label response;
TextField request;

- 174 -
String server;

public void init()


{
Font f = new Font("Times-Roman",Font.PLAIN,16);
Label message;

response = new Label("Eliza will see you know.");


response.setFont(f);

request = new TextField(24);


request.setFont(f);

setLayout(new GridLayout(2,1,5,5));

add(response);
add(request);

try
{
server = getParameter("server");
}
catch(Exception exp)
{
server = null;
}
}

public boolean action(Event evt,


Object what)
{
if(evt.target == request)
{
String reply;
String message;
DataInputStream reader;
InputStream in=null;
URL url;

try
{
message = "message=";
message += URLEncoder.encode(request.getText());

if("POST".equals(getParameter("method")))
{
URLConnection connection;
PrintStream printOut;

url = new URL(server);


connection = url.openConnection();
connection.setDoOutput(true);

- 175 -
connection.setDoInput(true);
connection.setUseCaches(false);
//Work around for netscape settings for
//post requests.
connection.setRequestProperty("Content-Type"
, "application/x-www-form-
urlencoded");

printOut=new

PrintStream(connection.getOutputStream());

printOut.print(message);
printOut.flush();
printOut.close();

in = connection.getInputStream();
}
else
{
url = new URL(server+"?"+message);

in = url.openStream();
}

reader = new DataInputStream(in);

reply = reader.readLine();

reader.close();
in.close();
}
catch(Exception exp)
{
reply = "Error, network may be down.";
}

response.setText(reply);
request.selectAll();
}

return true;
}
}

Notice that the applet uses regular HTTP requests to talk to the server and simply reads
the results in the expected format. In this case, the results are a text string, but the servlet
could return an image, file, or other resource—even a serialized object. In other words,
although the applet/servlet relationship is affected by the performance of HTTP and
intervening firewalls, it is a very flexible mechanism for distributed computing. Even this
technique’s reliance on HTTP makes it portable across most installations.

Designing Servlets
- 176 -
Perhaps one of the hardest decisions to make when using servlets, or any other Web
server application, is how to break the program into logical pieces. In the case of a Web-
based program, these logical pieces are often associated with Web pages. When
creating a Web application with several Web pages, the first step is to determine what
types of pages will be displayed. For example, in an online catalog, there might be pages
that list available items and pages that display item details. There may be numerous
versions of each type; there are certainly many different items that have details, and
those items may be categorized into separate lists.

Given the types of pages you need, ask yourself: Is the data on this type of page
dynamic? If it is, the next question is, how dynamic? Does the data change for every
request, or just once in a while? For example, the pages at amazon.com are
personalized for each registered visitor. This means that the Amazon site has to update
the page for each request. On the other hand, an online magazine might add stories
daily, but once added, the stories aren’t changed for each request. In between these two
examples is a Web page that shows data from a database changing hourly. In this case,
all users within a given hour see the same page.

For truly dynamic pages, assign a servlet to them. If a page is simply added on a regular
basis, just add HTML pages to the site. For pages that change regularly but for which all
users see the same version, you have two choices. First, you could use a servlet. This
requires the data to be created for each request, or possibly cached in memory. Second,
you could have a servlet or another program update the HTML files on disk. This method
maximizes the performance of each request and still allows pages to change
dynamically.

The bottom line is that you want to use servlets only for pages that change with each
request. A great example of this is the search servlet described in Chapter 8, “A Servlet-
Based Search Engine.” Each search is unique and needs to be handled individually.

Servlets should be assigned by page type and not by individual page. Unless your site
has only one page type, you may use numerous servlets to build the site. By specializing
the servlets, you minimize the amount or work each one has to do to fulfill a request.
Figure 7.8 shows an example of a servlet-based Web application that has been broken
up using this philosophy.

Figure 7.8: Example of servlet application.

The other kind of servlet you might need to write is one that handles the server-side
portion of an applet/servlet pair, or a servlet that provides server code to another type of
program, such as a Java application. Again, try to minimize the work each servlet does to
respond to a request, or at least organize the code to handle the request in a way that
makes it easy to maintain. For example, have the service method call other methods
based on the client request in the same way that an HttpServlet calls doGet for GET
requests and doPost for POST requests. You might even put the code for handling each
request type in another class to minimize the size of the servlet code, making it more
readable. Testing the servlet and finding errors or performance bottlenecks will also be
easier.

There are a number of simple rules you can follow to improve the design and security of

- 177 -
your servlets beyond what Java already provides.

1. Double-check any assumptions you make about the user input. Many of the security
risks that can occur are the result of faulty assumptions about input. These include
the content or the amount of input.

2. Be careful and check that you are not allocating too much memory. In particular,
watch out when you are reading data, and don’t read an arbitrary amount. Use limits.
A hacker could make a POST request with a CONTENT_LENGTH that is
outrageously large. This might cause you script problems.

3. Don’t assume that the data sent to your script is valid. Check the data first to make
sure that it can be used the way you plan to use it. For example, make sure that an
int is really an int or use try-catch blocks to handle any exceptions thrown when trying
to parse data.

4. Don’t assume that all the form elements were filled in. The user may not fill in any or
all of the form elements. You might want to check that required elements exist; if they
do not, send the user a page that explains the missing fields and provides him or her
with a link back to the form.

5. Don’t assume that the key-value pairs sent to your script necessarily correspond to
actual form elements. A cracker could generate a false request with other fields.

6. Don’t return an arbitrary file to the client.

7. Don’t assume that path information sent to your script describes a real file. The path
sent to a script may not describe a valid file path.

8. Don’t assume that path information sent to a script is safe. A cracker could send the
path to a file that you don’t want him or her to see, such as /etc/passwd. One of the
authors once mailed a programmer his password file to prove this point. Always limit
access to the file system.

9. Don’t assume that a selection is made in a selection list.

10. Make sure your Web server is not running as root. This is a huge security risk and
could allow an attacker to bring your machine to a grinding halt. Most servers are run
as nobody. You might create a user called www and an accompanying group. This
allows you to control file permissions more specifically. For instance, all scripts can
have group execution and reading permissions without being readable by everyone.
You should also test the script as the user who will run it to ensure that that user has
the needed file access.

11. Double-check any uses of e-mail from inside a script. Make sure users cannot mail
themselves an arbitrary file. They might try to get the password file this way. Also
make sure it is okay for users to change e-mail addresses that are hard-coded into
your Web pages as arguments to a script.

12. Make sure you don’t give a client too much information. Don’t return unneeded
information about the server.

13. Don’t assume that hidden fields are really hidden. Users won’t see these in the
browser, but they will see them if they view the source. This means that the user can
also change them.

14. Don’t try to invent your own encryption algorithms. It is common in large scripts to
make data persistent by using hidden fields or cookies. This is a useful technique, but
these cookies and fields are visible to the user. You might decide that you want to
encrypt the fields to hide them from prying eyes. This too is a reasonable solution.

- 178 -
However, encryption is a difficult business. Use a proven encryption scheme rather
than inventing one yourself. If you do invent your own, make it public and ask for help
testing it. We highly suggest the book Applied Cryptography, Second Edition:
Protocols, Algorithms and Source Code in C by Bruce Schneier (John Wiley & Sons
Inc., 1995) as a resource for finding an encryption scheme and learning why writing
your own is usually a bad idea. This is a lesson you don’t want to learn the hard way.

15. Be careful using native code in servlets. This may be necessary to access some
resources, but it also introduces into the Web server the possibility of memory leaks
and server-crashing code. When possible, use 100 percent Pure Java servlets to
protect both the servlet itself as well as the server from leaks and errors.

16. Double-check all of your service methods that make network connections or perform
any operation that could take a long time. Make sure that you use time-outs where
possible, and minimize the client’s waiting as much as possible.

Note In order to simplify the code, not all of our examples use all of these rules. We
often assume that a value is correct before using it. In your production code,
do not do this. Instead, follow this book’s larger program examples, where we
have checked all input before use.

Hopefully, these guidelines will help you with your servlet designs. This is not a complete
list and probably never could be, but it does represent a good starting point for thinking
about servlet issues. For other guidelines and tips, keep an eye on www.javaworld.com for
articles and other tips that may solve some of your problems before they happen.

Running and Hosting Servlets


There are a growing number of products that support servlets. In writing this book, we
have used several providers. First, the Java Servlet Development Kit, or JSDK, provides
a command-line program called servletrunner. This program loads servlets and handles
servlet requests, but it does not support normal HTML requests, so it doesn’t provide the
same complete environment as a Web server. Second, the Java Web Server, a product
provided by Sun, supports both servlets and JavaServer Pages, as discussed in later
chapters. BEA WebLogic Application Server also supports servlets. All of these hosts
support initialization arguments that the servlet acquires via the ServletConfig object.

Regardless of the host you pick, you need to know several things before running servlets:

Where do the servlet’s class files go? Often the host provides a directory called
servlets for these files. The Java Web Server has a directory called servlets;
servletrunner uses command-line arguments to indicate the directory all the servlet
classes are contained in.

Where do supporting classes go? Most likely, supporting files will go with the servlet,
in the CLASSPATH, or in another special directory defined by the host. The Java Web
Server provides a directory called classes that is a sibling to the servlets directory.
Supporting classes should go in this directory. For servletrunner, the supporting classes
should be in either the same directory as the servlet class or in the CLASSPATH used to
run servletrunner.

How is the server told about a servlet? In most cases, either a special file or tool is
used to tell the server about servlets. The Java Web Server provides an administrative
tool that can be used to add servlets to the server, set their names, and assign a class to
each name. This name is used in the URL to indicate the servlet using the form
http://server/servlet/servletname. The servletrunner uses a properties file and expects
lines of the form:

servlet.adrotator.code=AdRotatorServlet

- 179 -
This indicates a servlet’s name and class. This properties file can be assigned as a
command-line argument to servlet runner.

How are initialization parameters set? As with the previous question, either a file or
tool is often used to set these parameters. The Java Web Server provides an
administrative tool that can be used to add servlets to the server, set their names, and
assign a class to each name. This name is used in the URL to indicate the servlet using
the form http://server/servlet/servletname. The servletrunner uses a properties file and
expects lines of the form:

servlet.adrotator.initArgs=\
imagedir=c:\ \ temp\ \ chapter_07\ \ images

This code indicates a servlet’s arguments. Multiple arguments should be comma


delimited, and the \ character can be used to break the arguments across lines. A \ in
the argument must be escaped, as shown in the preceding code. This properties file can
be assigned as a command-line argument to servlet runner.

The Java Web Server provides excellent support for servlet programming. Even better is
the fact that there is every indication that other major server vendors are adopting this
technology as well. Expect support and additional functionality to appear in most server
products. This means that if you write servlets now, you should see enhanced features
and performance in the years to come at very little cost to you.

The servlet development kit is included on the CD-ROM that accompanies this book. Visit
java.sun.com for information on downloading a trial version of the Java Web Server or
check with your server provider to see if it supports servlets. You might also want to browse
to www.livesoftware.com, a company that provides tools for adding servlet support to your
existing Web server.

Debugging Servlets
Perhaps one of the hardest aspects of programming servlets is testing them—not testing
the features as much as the reliability. Consider that the servlet runs on the Web server
and therefore is probably not accessible to a debugger. Certainly, some servlet hosts
might provide debug capabilities, but it is not the norm. When an uncaught exception
occurs in a servlet, it simply fails to return data. The programmer won’t get any
information from the client about the error beyond the nagging message, “document
contains no data.” Also, many developers may write and test their servlets on one Web
server and deploy them to another one, making it hard to expect normal debugging
facilities. Finally, although servlets do have access to the log file, there are a number of
issues with using log statements to debug the program.

The destination for the log is server dependent. Strings sent to the log may be altered
and may have other log messages interspersed between them. Log-style debugging is
also time consuming in the sense that you will often add logging comments to the code
and then remove them for deployment. More important, the log is intended for
administratively significant messages, not debugging. Despite these drawbacks, log-style
debugging is perhaps the most portable and stable debugging mechanism for servlets.

To make log-style, or printf-style, debugging easier with servlets and other distributed
programs, we have created a class listed in the code that follows; this code defines a
class named DebugLog. The goal of this class is to provide multiple logging destinations,
rather than just supporting the servlet log. In particular, the DebugLog object can be told
to send log messages to a file, stream, or even to a log server that has been provided.
This server prints messages to System.out or a file. In the case of the log server, you can
watch log messages in real time while testing the servlet. Two methods are provided: one
to log string messages and the other to log an exception’s stack trace. This can be
especially useful when an exception occurs.

- 180 -
We also want to minimize the code changes required to move from development to
production. The technique we use to accomplish this is a simple one. First, the Debug
Log object is not initialized with a specific log destination. If no destination is provided, the
logging code will simply ignore all logging messages. This means that you can leave
debugging code in your servlets at the cost of a message send and an if statement. In the
context of a server, this is a minimal requirement for reducing the maintenance required
to remove debugging code.

The code for DebugLog follows. Notice that the main issues are keeping track of the
server in a way that allows the server to go down and the logger to reconnect
appropriately. In fact, if either the servlet goes away or the server goes down, the other
will self-correct.

import java.io.*;
import java.net.*;
import LogServer;

public class DebugLog extends Object


{
private Socket server;
private String serverName;
private BufferedReader serverReader;

private PrintWriter log;

private static DebugLog sharedLog;

public static synchronized DebugLog getSharedLog()


{
if(sharedLog == null) sharedLog = new DebugLog();
return sharedLog;
}

public DebugLog()
{
log = null;
}

public synchronized boolean initialized()


{
return ((log != null)||(serverName!=null));
}

public synchronized void log(String str)


{
//exit quick if no log
if((log != null)||(serverName!=null))
{
log(str,true);//retry
}
}

public synchronized void log(Exception exp)


{

- 181 -
if((log != null)||(serverName!=null))
{
StringWriter out;
PrintWriter printOut;
String logTrace;
StringReader in;
BufferedReader bufIn;
String curLine;

try
{
out = new StringWriter();
printOut = new PrintWriter(out);

exp.printStackTrace(printOut);
printOut.close();

logTrace = out.toString();
in = new StringReader(logTrace);
bufIn = new BufferedReader(in);

while((curLine = bufIn.readLine()) != null)


{
log(curLine,true);
}

bufIn.close();
}
catch(Exception ex)
{
}
}
}

//protected method that allows the logger to reconnect


//to a server
protected synchronized void log(String str,boolean retry)
{
boolean error=false;

if(log != null)//exit quick if no log


{
try
{
log.println(str);

if(serverReader != null)
{
//Read the response, but ignore
//This should force an exception
// if the socket is closed
serverReader.readLine();

- 182 -
}
}
catch(Exception ex)
{
error = true;
closeLog();
}
}
else
{
error = true;
}

if((serverName != null) && error)


{
if(retry)
{
logTo(serverName);
log(str,false);//only retry one time
}
else
{
closeLog();
}
}
}

public synchronized void logTo(File f)


{
if(f!=null)
{
closeLog();

try
{
FileWriter fileIn = new
FileWriter(f.getAbsolutePath(),true);
log = new PrintWriter(fileIn,true);
}
catch(Exception exp)
{
log = null;
}
}
}

public synchronized void logTo(OutputStream stream)


{
if(stream!=null)
{
closeLog();

- 183 -
try
{
log = new PrintWriter(stream,true);
}
catch(Exception exp)
{
log = null;
}
}
}

public synchronized void logTo(String logServer)


{
if(logServer!=null)
{
closeLog();
serverName = logServer;

try
{
server = new
Socket(logServer,LogServer.DEFAULT_PORT);

InputStreamReader readIn;
readIn = new
InputStreamReader(server.getInputStream());
serverReader = new BufferedReader(readIn);

log = new
PrintWriter(server.getOutputStream(),true);
server.setSoTimeout(2000);//two seconds
}
catch(Exception exp)
{
log = null;
server = null;
}
}
}

public synchronized void closeLog()


{
if((log!=null)&&(server!=null))
{
try
{
log.println(LogServer.DISCONNECT_MSG);
}
catch(Exception exp)
{
}
}

- 184 -
if(log != null)
{
log.flush();
log.close();
log = null;
}

if(server != null)
{
try
{
if(serverReader != null) serverReader.close();
server.close();
server = null;
serverReader = null;
}
catch(Exception ex)
{
}
}
}

public synchronized void finalize()


{
closeLog();
}
}
class DebugLogTester
{
public static int MSG_COUNT=100;

public static void main(String[] args)


{
DebugLog logger = DebugLog.getSharedLog();
int i;

System.out.println("Created log.");
logger.logTo("192.168.0.172");
System.out.println("Set log dest.");

System.out.println("Logging messages.");

for(i=0;i<MSG_COUNT;i++)
{
try
{
logger.log("Test "+i);

Thread.sleep(100);
}
catch(Exception exp)

- 185 -
{
System.out.println("Exception: "+exp);
}
}

System.out.println("Closing log.");
logger.closeLog();
}
}

To use the DebugLog class, you can either make an instance or use the shared instance
provided by the class method getSharedLog. Once you have a DebugLog, initialize log to
a specific destination using one of the logTo methods. Finally, send log messages to
forward strings to the log. In the case of a servlet, the log will probably stay alive for the
life of the servlet. However, you can close the log using closeLog. This closes
connections and files that are open. Throughout the code, auto-flushing is used with our
PrintWriters to ensure that the log messages are sent to the underlying streams
immediately.

The log server is implemented in a class called LogServer. The code for this class is
available on the CD-ROM but, to save space, has not been included here. The LogServer
is a single Java class that uses a separate thread to handle connections from DebugLog
clients. Each log message sent by the client is either printed to System.out or a file,
depending on the command-line arguments. To make it easier to track messages, they
are prepended with the IP address of the client that sends them. The CD-ROM also
contains the DebugLog.java file. A class called DebugLogTester is included as part of
this file for testing purposes.

For servlets that want to share a single DebugLog object, the method initialized is
provided to check if another servlet has initialized the log. This method should be used
inside a synchronized block with the call to logTo in order to ensure that one servlet
doesn’t initialize the log between the time that another calls initialized and logTo. The
following servlet finds and initializes the log in its init method using configuration
parameters for the destination. This servlet, called LoggingServlet, can be used to test
the log features. It takes a parameter called message and logs the message. All of the
code for interacting with the debug log is in bold in the code that follows. The surrounding
code is simply support for the servlet to return Web pages and determine configuration
parameters.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class LoggingServlet extends GenericServlet


{
DebugLog logger;

public void init(ServletConfig conf) throws ServletException


{
super.init(conf);

String logFile,logServer;

logFile = getInitParameter("logfile");
logServer = getInitParameter("logserver");

- 186 -
logger = DebugLog.getSharedLog();

if((logFile != null)||(logServer != null))


{
//Need to protect against access by multiple clients.
synchronized(logger)
{
if(!logger.initialized())
{
if(logServer != null)
{
logger.logTo(logServer);
}
else
{
logger.logTo(new File(logFile));
}
}
}
}
}

public void service(ServletRequest request,


ServletResponse response)
throws ServletException, IOException
{
PrintWriter out;
String message;

response.setContentType("text/html");

out = response.getWriter();
out.println("<HTML><HEAD><TITLE>");
out.println("Log Tester");
out.println("</TITLE></HEAD><BODY>");

out.println("<H1>Logged</H1>");

message = request.getParameter("Message");

logger.log(message);

out.println(message);

out.println("</BODY></HTML>");
out.close();
}

public void destroy()


{
logger.closeLog();
}

- 187 -
}

An HTML page called LoggingTester.html follows. This Web page activates the Logging
Servlet. The HTML contains a field for the message and a Submit button.

<HTML>
<HEAD>
<TITLE>
Logging Tester
</TITLE>
</HEAD>
<BODY>
<FORM METHOD=POST ACTION="/servlet/logservlet">
Message to log:
<INPUT NAME="Message" VALUE=""><BR><BR>
<INPUT TYPE="Submit" NAME="Submit" VALUE="Log">

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

As tools improve, the Web servers that host servlets may begin to provide better
debugging tools. Until that time, log-style debugging is a powerful technique.

Warning We have found that servlets often crash if you try to print an invalid or null
string to the output writer. Be careful when printing dynamic strings, and
take into account the possibility that they may be null.

Using the DebugLog object allows you to create servlets with the necessary log
messages, without having to change a lot of code when you deploy the servlet. In fact,
you can even use a configuration argument to specify the logging destination, thus
changing no code when deploying. More important, not changing code means that you
won’t introduce new bugs while deploying.

You may also want to use www.javaworld.com as a jumping-off point to find tools that help
servlet debugging. Live Software, mentioned previously, provides one solution, and other
companies provide some tools as well. As enterprise Java programming becomes more
mainstream, more debugging and testing tools will become available, so keep an eye out
for the “latest and greatest.”

Performance Tuning
As you deploy your servlet-based applications, you will probably be concerned about
performance. As with all performance-tuning activities, the first step in improving servlet
performance is to measure it. To aid in testing the performance of servlets, a simple class
follows; its instances use multiple threads to make requests to an arbitrary URL.
Currently this class, called LoadTester, supports only GET requests, but it could be
updated to support a wider range of request types. The purpose of load tester is to create
a load on the server and measure the average response time.

Keep in mind that the implementation of LoadTester is a simple one, mainly to serve as
an example for the book. But it does provide a good starting point for testing the general
response time of your servlets. Given an optional data file, a load tester even sends data
to the servlet for processing. In return, the load tester prints timings for the request and
average request times to System.out. Given these times, you can begin to tune
performance and check your progress. You can even assign a pause between requests
to improve realism.

- 188 -
The LoadTester class that follows exercises a servlet or Web server by making
consecutive requests. If no data is provided to the tester, it will get the contents of the
specified URL. If data is provided, that data is sent to the URL and the reply read. Both
methods use GET requests (the default).

import java.net.*;
import java.io.*;

/* *
* Timing for each request is logged to standard out.
*
* Usage: LoadTester numTests numThreads pause url
optionalDataFile
*
* The data file should be in the form:
* key value
* key2 value2
*
* using spaces for delimeters.
*
* If numTests==0 then the tester will continute
* indefinately. Each thread will make the specified
* request numTests times.
*
* pause is the time between tests in milliseconds.
*
* The results of each test are read and thrown away.
* Timing is performed from the request to the first byte read,
* and to the last byte.
*/
public class LoadTester extends Object
{
private URL url;
private String data;
private int numTests;
private int numThreads;
private int pause;

static public void main(String[] argv)


{
LoadTester tester=null;
int threads, tests,pause;

if(argv.length < 4)
{
System.out.println("Usage: LoadTester numTests "
+numThreads pause url optionalDataFile");
System.exit(0);
}

try
{

- 189 -
tests = Integer.parseInt(argv[0]);
threads = Integer.parseInt(argv[1]);
pause = Integer.parseInt(argv[2]);

tester = new LoadTester(threads,tests,pause,argv[3]);

if(argv.length == 5)
{
tester.setData(argv[4]);
}

tester.start();
}
catch(Exception exp)
{
System.out.println(exp);
System.exit(0);
}
}

public LoadTester(int nthds,int ntsts,int p,String u)


throws MalformedURLException
{
numTests = ntsts;
numThreads = nthds;
url = new URL(u);
pause = p;
}

The setData method reads a file of key-value pairs to create the input for a servlet. This
file can be used to define the parameters that a user would normally type into a form.

public void setData(String fileName)


throws Exception
{
FileReader fileIn;
BufferedReader bufIn;
String curLine;
String key,value;
StringBuffer dataBuf = new StringBuffer();
boolean firstLine = true;
int index;

fileIn = new FileReader(fileName);


bufIn = new BufferedReader(fileIn);

while((curLine=bufIn.readLine()) != null)
{
index = curLine.indexOf(" ");

if(index<0) break;

key = curLine.substring(0,index);

- 190 -
value = curLine.substring(index+1).trim();

if(!firstLine) dataBuf.append('&');

dataBuf.append(URLEncoder.encode(key));
dataBuf.append('=');
dataBuf.append(URLEncoder.encode(value));

firstLine = false;
}

data = dataBuf.toString();
bufIn.close();
}

When started, the load tester creates a special thread subclass to do the actual work and
starts the thread running.

public void start()


{
LoadTesterThread thread;
URL tmpURL;
int i;

try
{
for(i=0;i<numThreads;i++)
{
tmpURL = new URL(url.toExternalForm());
thread = new

LoadTesterThread(numTests,tmpURL,pause,data);
thread.start();
}
}
catch(Exception ex)
{
System.out.println("Tester failed...");
}
}
}

A LoadTesterThread stores information about each test. The run method runs the
specified number of tests and prints the results to the console.

class LoadTesterThread extends Thread


{
private URL url;
private String data;
private int pause;
private int numTests;
private int curTests;
private int curTotal;

- 191 -
private int curTotalFinal;

public LoadTesterThread(int ntsts,URL u,int p,String s)


{
numTests = ntsts;
url = u;
pause = p;
data = s;
}

public void run()


{
if(numTests > 0)
{
int i;
long testStart,testEnd;
double totalTime;
double average;

testStart = System.currentTimeMillis();

for(i=0;i<numTests;i++)
{
runTest();
try
{
if(pause != 0)
{
sleep(pause);
}
}
catch(Exception ex)
{
}
}

testEnd = System.currentTimeMillis();

totalTime = ((double)((testEnd-testStart)-
(numTests*pause))/1000);//in seconds

synchronized(System.out)
{
System.out.print(numTests+" performed in ");
System.out.print(totalTime);
System.out.print(" seconds ");
System.out.print(totalTime/numTests);
System.out.println(" avg.");
}
}
else
{

- 192 -
while(true)
{
runTest();
}
}
}

Each test is executed by connecting to the specified URL, making the request with the
provided data, and reading the response. Two times are tracked for reading—the time to
start reading and the time to actually read the data—because long responses obviously
take longer than short ones.

public void runTest()


{
long start,end,realEnd;
URLConnection connection;
PrintWriter writer;
InputStream in;
int cur;

try
{
connection = url.openConnection();
connection.setDoOutput(true);

if(data != null)
{
connection.setDoInput(true);
writer = new
PrintWriter(connection.getOutputStream(),true);

start = System.currentTimeMillis();

writer.print(data);

writer.close();
}
else
{
start = System.currentTimeMillis();
}

in = connection.getInputStream();
cur=in.read();
end = System.currentTimeMillis();

while(cur != -1)
{
cur=in.read();
}

in.close();

- 193 -
realEnd = System.currentTimeMillis();

curTotal += end-start;
curTotalFinal += realEnd-start;
curTests++;

synchronized(System.out)
{
System.out.print("Initial ");
System.out.print(end-start);
System.out.print(" (");
System.out.print((double)(curTotal/curTests));
System.out.print(") ms : Final ");
System.out.print(realEnd-start);
System.out.print(" (");

System.out.print((double)(curTotalFinal/curTests));
System.out.println(") ms.");
}
}
catch(Exception exp)
{
System.out.println("Test Failed.");
}
}
}

Of course, the other method for testing performance is just to use the servlet. In this
case, you are testing perceived performance. In reality, perceived performance is more
important than actual performance in servlets that respond to user requests. The user will
not notice a five-millisecond difference that the computer might. However, when multiple
users access the servlet, it will have a different perceived performance than when one
programmer tests it. As a result, you may want to combine the load tester with personal
testing. Have the load tester imitate a reasonable number of users, then test the servlet
by hand, to check perceived performance.

Note Often you can improve perceived performance via user feedback. It is easier
to wait 30 seconds watching a moving progress bar than waiting 20 seconds
with no feedback. For users, the most important thing to perceive is that work
is being done on their behalf.

In case the total response time of a servlet is considered too long, the first step is to
figure out why. Unfortunately, there is no easy way to profile a running servlet, unless the
server provides a profiling tool. Live Software also provides a tool for load-testing
servlets. You might also find a tools provider for this type of profiling in journals,
magazines, or on the Web. Without a tool to discover specific choke points, the next best
thing is to evaluate standard performance bottlenecks. In general, this includes items
such as:

• Opening network connections—for example, database connections

• Opening and closing files, general I/O

• Allocating memory

• Poorly written algorithms

- 194 -
Of these, the first two can often be solved by pooling resources, or caching. For example,
a servlet can share database connections between requests, assuming that there is no
security reason not to. In this case, only the init method suffers the performance hit of
making a connection. However, sharing connections and keeping them open uses
resources. On large Web servers, it is important to consider the effects of holding
resources, as much as it is to consider the time it takes to keep reconnecting. It may also
be possible to cache the results of database or other queries. In this case, only the first
query requires network access, while the others just access data in memory.

Along the same lines, files can be cached in memory. Although this seems like overkill at
first, it can increase performance dramatically. However, it does require memory, and the
total memory usage of the servlet should be considered before caching lots of files.
Regardless of caching, always use buffered readers or streams to read and write files.
This can improve performance 5 to 10 times, in some cases.

One of the advantages of servlets over other server plug-in technologies like NSAPI or
ISAPI is that they run inside the virtual machine and are subject to garbage collection.
This means, in one sense, that you don’t have to worry about memory. However, the
reality is that creating objects takes time, and a servlet that creates a lot of objects will
reflect that time usage in its performance. There are several ways to deal with the
allocation issue. First, keep in mind that you should write simple code first. Don’t try to
avoid memory allocation issues before you even test the program the first time. Second,
avoid simple allocations issues by:

1. Using StringBuffers to create large strings rather than appending strings together.
This will ensure that only one buffer is used to create the result.

2. Reusing objects when possible rather than creating new ones.

3. Avoid using Sessions unless they are necessary.

4. Avoiding the creation of immutable objects, like Strings, for data that has to change.
This is especially true in your own libraries.

Third, set object references to null when you are done with the object to ensure its
availability to the garbage collector. Finally, the next generations of virtual machines,
including Sun’s HotSpot, are designed to reduce the overhead of creating a lot of
temporary objects. If you have good code that needs to make a bunch of objects, look
into trying a different VM and see if that improves performance sufficiently.

Algorithms represent the foundation of your servlet or any program. In many cases, you
will not be using the classic algorithms like sorting or searching, but your servlet will rely
on some form of recipe for performing its job. The hard part of tuning your algorithms is
the desire to tune them too early. Always use the simplest algorithm first. If testing shows
a problem and you can associate the performance problem with the algorithm, it is worth
improving.

Extending this discussion to code in general, write simple, solid, maintainable code first.
Experience has shown that your performance bottleneck will probably be in less than 10
percent of that code, so don’t try to optimize the whole thing. Instead, try to find the key
performance issues, then rewrite that code, if necessary, to use fancier and faster
techniques.

As always, the fastest code is the code you don’t write. Small, targeted servlets can be
much faster, once you deal with any network/file issues, than an equivalent large,
multipurpose servlet. In the same vein, the fastest servlet is the one you don’t have to
write. Basically, this means that you should let the Web server do its job whenever
possible. In particular, Web servers are great at sending files to the user. Don’t write a
servlet to do the same thing unless you are adding value to the process. A good example

- 195 -
of this would be a site with an online catalog. Let’s say the catalog changes once a day.
Instead of using servlets to dynamically display the data from the database for each
request, you could rebuild the HTML pages once a day, and have the Web server serve
the HTML directly from disk. Basically, if the data for a Web page is not customized on
every request, try to cache it to disk and let the Web server do its job, rather than
programmatically creating HTML each time.

To conclude the discussion on performance, we should at least mention the hardware


vendor’s motto for performance: more memory, faster CPU, bigger computers, spend
money. Basically, you may have good code, but the demand has outstripped the platform
you run it on. For Java, this platform extends beyond the hardware into the virtual
machine. From an objective point of view, there are a number of things to try, not all of
which cost money:

• Compiler options; use optimization when possible

• New compiler

• New virtual machine

• New servlet host (Web server/application server)

• More memory

• Different network cards

• New OS or new network drivers

• New hardware

Certainly you may be constrained to maintain the existing version of any of these items,
but keep them in mind when tuning performance.

As always, performance tuning is a hard process and very application specific. The most
important rule is always to measure before you tune. If you don’t measure, you can spend
a lot of time on parts of the code that don’t really affect performance one way or the other.

Summary
Servlets represent a powerful mechanism for extending a Web server’s functionality and
implementing the server portion of a servlet/applet pair or a Web application. As with all
client/server programming, there are important performance and design issues to
consider:

• Minimize the time required to perform each service request. This often means
providing several servlets, one for each request type in an application.

• If possible, support multiple threads. This will allow the most flexibility and, on a
multiprocessor computer, may allow you to take advantage of multiple processors.
However, you will need to protect shared resources with locks and/or isolation.

• Keep security in mind. Servlets are run on your computer and should not be
programmed to execute the client’s whims, but your own well-planned actions.

• Measure performance before tuning in a situation that is as realistic as possible. Use


tools allowing you to test the servlet under a real-world request load.

- 196 -
• Use the DebugLog or some other tool to implement debugging code early on. Even in
writing this book, we relied on the DebugLog to find errors. In most cases, we removed
the debugging code to make the example easier to read, but in the larger examples in
later chapters, this code was left in to show how it could be used to find errors.

• Rely on the Web server to do its job. Don’t write servlets because you can, write them
because you need the dynamic output that they provide.

• Use HTTP servlets when you can. This will provide an easier implementation
framework, and the request and response objects will provide more information than
the generic equivalents.

The next chapter focuses on a larger servlet-based application. Following chapters cover
JavaServer pages. These server pages actually create servlets and rely on all of the
information discussed so far. At the end of the book are a number of large examples, some
of which use servlets to provide HTTP tunneling. Servlets are a flexible, powerful
mechanism for creating server code and will probably fill a key role in your enterprise
applications over the next few years.

Chapter 8: A Servlet-Based Search Engine


Overview
This chapter is the first in the set of larger example programs that are included in this
book. This example is a servlet that implements a simple Web page search engine. It
demonstrates a number of the concepts we discussed in Chapter 7, “Programming
Servlets,” such as initialization parameters, path information and parameters from the
request, thread synchronization, error codes, and redirection. Searches can be
constrained to a maximum number of hits, and navigation links are provided to view the
next block of hits if the query results in more than the maximum. Queries can either be
plain words or Boolean statements consisting of ands, ors, and nots. The performance for
this servlet is adequate for a Web site and can be configured to use custom help pages
and support multiple, separately indexed directories. All search results are displayed in a
Web page like the one pictured in Figure 8.1. A navigation bar is dynamically generated
for moving through large result sets.

Figure 8.1: Search results.

The core search engine for this servlet is provided in a package called index, which
provides the code needed to index and search HTML files. Indices are represented by

- 197 -
HTMLIndex objects in memory and text files on disk. The index objects use hash tables
as their internal representation. This makes searches very fast, at the cost of some
memory overhead. However, although the size of an index file depends on the number of
unique words and files, experience shows that it is approximately one tenth of the size of
the pages indexed. Part of this size reduction comes via the use of a skip table that
contains words that aren’t indexed. This table can be edited, although it requires
recompilation of one of the classes in the index package. Although this example servlet
does not take advantage of it, the indices also include the number of occurrences for
each word. (This book does not go into the details of the index package.) In a later
section, this chapter looks at the index manager that handles loading and unloading of
indexes. For details on the index itself, refer to the source code on the CD-ROM that
accompanies this book.

Note In order to run this example, you will need to install it on a Web server that
supports servlets. You will also need to configure the initialization parameters
described below.

HTMLSearchServlet
The Web interface to the search engine is the class HTMLSearchServlet. This servlet
extends HttpServlet and defines an instance variable, logger, to store a DebugLog object
(described in Chapter 7, “Programming Servlets”), the helpPage instance variable stores
a string containing the URL for a help page, and the instance variable noIndexPage
stores a string containing the URL for a page to display when the requested search
cannot be performed. The static variable DEFAULT_MAX_HITS is defined to indicate the
default maximum hits to display at one time. QUERY_FIELD_NAME,
MAX_FIELD_NAME, CURRENT_FIELD_NAME SUBMIT_FIELD_NAME, and
HELP_NAME are defined to indicate the names of the parameters that the servlet
expects from the client. Static variables for these names are used to improve
documentation and reduce the number of magic strings in our code.

The following code listing is the beginning of the HTMLSearchServlet class file. It
contains the required import statements and the defintion of the static variables used in
the class.

import java.io.*;
import java.util.*;
import java.net.*;
import javax.servlet.*;
import javax.servlet.http.*;

import index.*;

public class HTMLSearchServlet extends HttpServlet


{
protected DebugLog logger;
protected String noIndexPage;
protected String helpPage;

protected static final int DEFAULT_MAX_HITS=25;

public static final String QUERY_FIELD_NAME="query";


public static final String MAX_FIELD_NAME="maxhits";
public static final String CURRENT_FIELD_NAME="hitstart";
public static final String SUBMIT_FIELD_NAME="submit";
public static final String HELP_NAME="help";

- 198 -
The HTMLSearchServlet relies on four parameters: the query parameter, the maxhits
parameter, the hit-start parameter, and the submit parameter. The query parameter
indicates the user’s query. The maxhits parameter is used to change the number of hits
to display. If one is not provided, the default is assumed. The hitstart parameter is used
internally when a query exceeds the maximum number of hits and the user is scrolling
through the blocks of results. If it is not part of a request, the servlet displays the first set
of results, without exceeding the maxhits value. The submit parameter is used to
determine if the user wants the help page displayed. If the submit parameter is equal to
the help name, the help page is displayed; otherwise, the request is interpreted as a
query.

The following code segment defines the init method of the HTMLSearchServlet. All of the
instance variables are initialized in the init method from configuration parameters listed in
Table 8.1.

Table 8.1: HTMLSearchServlet Configuration Parameters

Parameter Description Default

logfile To file to log messages to. not set

logserver The IP address of the not set


DebugLogServer.

helppage The URL for the help page to display /SearchHelp.html


if the user requests it.

noindexpage The URL for the page to display if no /NoIndex.html


index is available.

updateinterval The number of seconds to wait not set


between checking whether files have
changed on the disk and the index
should be rebuilt.

The debug log is created from a file or server, depending on the available parameters. If
no file or server is provided, the log will ignore debug messages. (For more discussion on
the debug log, see Chapter 7, “Programming Servlets.”) Because the class IndexBuilder
that creates the indexes for the servlet also uses the shared debug log, if the servlet
initializes it, the index builder will print messages to the log as well. An updateInterval can
be specified as one of the configuration parameters. If this interval is non-zero, it
represents the number of seconds between times that the index manager checks
whether the index is up to date with the directory it indexes. If it is not up to date, the
index is rebuilt.

public void init(ServletConfig config)


throws ServletException
{
super.init(config);

String logFile,logServer;

- 199 -
logger = DebugLog.getSharedLog();

logFile = getInitParameter("logfile");
logServer = getInitParameter("logserver");
helpPage = getInitParameter("helppage");
noIndexPage = getInitParameter("noindexpage");
updateIntervalString = getInitParameter("updateinterval");

if(updateIntervalString != null)
{
try
{
updateInterval =
Long.parseLong(updateIntervalString);
IndexManager.updateInterval = updateInterval;
}
catch(Exception exp)
{
}
}

if((logFile != null)||(logServer != null))


{

//Index builder uses the same log.

synchronized(logger)
{
if(!logger.initialized())
{
if(logServer != null)
{
logger.logTo(logServer);
}
else
{
logger.logTo(new File(logFile));
}
}
}
}
}

The help and no index files are defined relative to the document root for the Web server
and are complete URLs for that server. A sample of each file is provided on the CD-
ROM. The example help file is called SearchHelp.html. If this file is placed in the Web
server’s document root, the helppage configuration parameter should be set to
/SearchHelp.html. If you type SearchHelp.html without the slash, the server will try to
redirect the client to this file as though it were inside the servlet itself, resulting in an error.

The HTMLSearchServlet processes client requests through the doGet method. The code
for this method it listed below. The doGet method does two things: First, it checks
whether the client wanted to see the help page or wanted to perform a query. Second,
the servlet displays the help page or performs the query and returns the results. This

- 200 -
decision relies on the way that the search request is submitted. The form submitting the
request is expected to name its submit button SUBMIT_FIELD_NAME. If the value of this
name is HELP_NAME, the help page is displayed, using redirect. If the help page was
not defined in our configuration, the client is sent an error code indicating that help is
unavailable.

public void doGet(HttpServletRequest request,


HttpServletResponse response)
throws IOException
{
String requestType;

requestType = request.getParameter(SUBMIT_FIELD_NAME);

/*
If the submit field contains the name of the help page,
then send the client to the help html page.
*/
if((requestType != null)
&&(requestType.equalsIgnoreCase(HELP_NAME)))
{
logger.log("Got help request");

if(helpPage != null)
response.sendRedirect(helpPage);
else

response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
else//null is the same as do query
{
handleQuery(request,response);
}
}

If the submit field retrieved in the doGet method does not contain a request for help, the
handleQuery method defined to handle the specifics of the query request is called.

The handleQuery method contains the majority of the HTMLSearchServlet code. The
method handleQuery is defined with the same parameters as the doGet method, to make
it easy to call. Local variables in the handleQuery method are defined to contain any
temporary information that might be necessary. The following code defines the
handleQuery method signature and its local variables.

protected void handleQuery(HttpServletRequest request,


HttpServletResponse response)
throws IOException
{
HTMLIndex index; // The index
String query; // The query to execute
Vector results; // A list of search
results
int i; // A local counter
intmax; // Maximum return results
resultCount=0; // Number of query

- 201 -
results
String maxString; // Temporary string
int maxHits = DEFAULT_MAX_HITS; // Max results displayed
String curHitStartString; // Temporary string
int curHitStart=0; // Where to start
displaying
PrintWriter writer; // Output writer
String dir; // Directory path
String fullDir; // Full directory path
String curFileName=null; // Current file to
display
String myURL; // The servlet's URL

These local variables are used throughout the handleQuery method.

PathInfo and Query Initialization


Instead of using configuration parameters to indicate the directory to search, the concept
of HTTP path information is used. Path information, provided as a file path, appears after
the servlet’s name in the URL that the client uses to access the servlet. For example, if
the search servlet is installed as htmlsearch and you want to search a directory called
doc2, you would use the URL:

http://servername/servlet/htmlsearch/doc2

This tells the servlet to look in /doc2 for the index and documents. Using path information
rather than another mechanism such as a configuration parameter is a good choice
because the HttpServletRequest already knows how to convert path information into a
real file path on the server. The path information itself is assumed relative to the
document root by the HttpServletRequest. This means that both the correct URL and file
path for the search directory can be acquired easily. The limitation is that the
implementation does not handle spaces in file names.

The two values for the search directory are stored in local variables and follow the code
listed above:

dir = request.getPathInfo();
fullDir = request.getPathTranslated();

All other values that we may need are then initialized. The response type is set to tell the
client that an HTML document is being returned. The writer for the output to the client is
stored along with the various client request parameter values. The variable myURL
stores the URL from the request for processing later. Then the maxString and
curHitStartString variables are converted to integers and stored in maxHits and
curHitStart, respectively. The code for this is listed below:

response.setContentType("text/html");

writer = response.getWriter();
query = request.getParameter(QUERY_FIELD_NAME);
maxString = request.getParameter(MAX_FIELD_NAME);
curHitStartString = request.getParameter(CURRENT_FIELD_NAME);

myURL = HttpUtils.getRequestURL(request).toString();

if(maxString != null)

- 202 -
{
try
{
maxHits = Integer.parseInt(maxString);
}
catch(Exception exp)
{
maxHits = DEFAULT_MAX_HITS;
}
}

if(curHitStartString != null)
{
try
{
curHitStart = Integer.parseInt(curHitStartString);
}
catch(Exception exp)
{
curHitStart = 0;
}
}

After the necessary parameters have been retrieved from the client request, the method
tries to load the index. All of the indices are managed by the IndexManager class
discussed later in this section. An index is requested based on the directory it searches,
and IndexManager returns either the index object or null if the directory is not indexed.
Because indexing takes a while, don’t make the user wait for it. However, do make the
first user to search a directory wait for the index to be loaded. In other words, the first
user to search a directory that is not indexed receives an error message telling them that
the index is not available. But that user also triggers the creation of the index. The first
person to access a directory with an index will wait for the index to load, before his or her
query is processed. Once loaded, IndexManager keeps track of the index and returns it
when requested. The indexes are thread safe once loaded, because IndexManager is
used to protect them during loading and to allow index sharing, which improves
performance. If you try this example from the CD-ROM, notice that the first search can be
slow due to the load, but subsequent searches are nearly immediate.

The following code in the handleQuery method logs a status message to the log and then
attempts to load the index for the current directory.

logger.log("Requesting index for: "+dir);


index = IndexManager.indexForDirectory(fullDir,dir);

In this code, IndexManager is given both the relative and absolute paths to the directory
so that the index can be found on disk and so that it returns file paths relative to the
servers document root, instead of real file paths.

The next section discusses how to check whether the index was returned or whether the
manager returned null.

Handling Non-Query Requests


The preceding code attempted to load the index. The following code shows how
handleQuery works in case the index is unavailable. If the index is unavailable, an
attempt is made to redirect the client to the no-index page indicated by the configuration

- 203 -
parameters. If this page is unavailable, an error code is sent to the client, indicating that
the service is unavailable.

if(index == null)
{
logger.log("Index not available for: "+fullDir);

if(noIndexPage != null)
response.sendRedirect(noIndexPage);
else

response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
}
else
{
...

These error codes are a poor choice for communicating the situation to the user. The
error codes should be used as a last resort. Another consideration is that the HTMLIndex
class has a main method that will build an index for a directory. This means that you can
build the initial directory early instead of waiting for a user request. The builder will also
rebuild the index if any files in the directory or subdirectories are newer than the existing
index. You might want to update the indices regularly, although the servlet will do this
automatically. If the update interval configuration parameter is set, the servlet will check
the index after the specified number of seconds pass and update it if necessary.

If the index described is not null, a query is performed. The next section describes the
code executed in this case (under the else condition) in the handleQuery method.

Handling Queries
When a query request arrives, the servlet’s first step is to ask the index for the files
associated with that query. The index parses the query for “and,” “or,” and “not,” searches
its hash table, and returns the matching files in a vector called results. The servlet then
uses this vector to determine the number of hits.

results = index.filesForQuery(query);

if(results != null) resultCount = results.size();

logger.log("Got "+resultCount+" results for query:


"+query);

Next, write the header for the results page using the writer of the response object. The
following code shows how the handleQuery method does this by first writing the header,
which wraps the results in a centered table to make them more readable. The results
themselves will be displayed as links.

writer.println("<HTML>");
writer.println("<HEAD>");
writer.println("<TITLE>");
writer.println("Search Results");
writer.println("</TITLE>");
writer.println("</HEAD>");
writer.println("<BODY TEXT=\ "#000000\ " BGCOLOR=\
"#FFFFFF\ ""
+" LINK=\ "#0000EE\ " VLINK=\ "#551A8B\ ""

- 204 -
+" ALINK=\ "#FF0000\ ">");

writer.println("<CENTER>");
writer.println("<TABLE>");
writer.println("<TR>");
writer.println("<TD>");

Once the header is written, figure out whether there were any result hits. If there were no
hits, an appropriate message is displayed. If there are hits, determine whether the
number of results exceeds the maximum hits to display by comparing the maxHits to the
resultCount minus the numeric index of the curHitStart. The variable curHitStart contains
the counter for the first element to display. In other words, you could have counted 100
items indexed from 0 to 99, but if you start by displaying the element at index 95, there
are only five elements to display. The actual number of hits to display on this page is
stored in the local variable max. Before displaying the results, a message is displayed
that tells the user how many hits occurred and which ones are displayed on this page.

if(resultCount > 0)
{
writer.println("<H1>Your search results are:</H1>");
max = Math.min(resultCount-curHitStart
,maxHits);

//inc printout by one to make it 1-25 not 0-25


writer.println("<CENTER>"+(curHitStart+1)+"-"
+(curHitStart+max)
+" of "+resultCount
+" matches</CENTER>");

outputHitsNavigator(maxHits,curHitStart,resultCount
,query,myURL,writer);

writer.println("<BLOCKQUOTE>");

try
{

for(i=0;i<max;i++)
{
curFileName = (String)
results.elementAt(curHitStart+i);
outputFile(curFileName,dir,writer);
}
}
catch(Exception exp)
{
logger.log("Bad file name: "+curFileName);
}

writer.println("</BLOCKQUOTE>");

outputHitsNavigator(maxHits,curHitStart
,resultCount
,query,myURL,writer);

- 205 -
}
else
{
writer.println("<H1>No files matched your "
+"query.</H1>");
}

After all of the files are displayed, the result page’s footer is printed to the client, and the
handleQuery method concludes as defined in the code that follows.

writer.println("</TD>");
writer.println("</TR>");
writer.println("</TABLE>");
writer.println("</CENTER>");
writer.println("</BODY>");
writer.println("</HTML>");
}
}

Each of the results in the preceding code is displayed using a method called outputFile.
This method displays a link for each file, with the link’s text equal to the file’s name. The
code for outputFile is as follows.

protected void outputFile(String file,String dir,PrintWriter w)


{
String fileName = file.substring(dir.length());
w.print("<A HREF=\ "");
w.print(file.replace('\ \ ','/'));//make sure the URL is
valid
w.print("\ ">");
w.print(fileName.replace('\ \ ','/'));
w.println("</A><BR>");
}

Managing a Large Number of Hits

The navigation bar for moving around in large result sets is displayed by the method
outputHitsNavigator. This method first determines if a navigation bar is needed by
comparing the number of results to be displayed to the maximum number of hits allowed
to be displayed. For example, if there are 100 results to a query and maxhits is 20, the
first 20 result hits are displayed and a navigation bar is used to allow access to the last
80 hits. If the bar is not required, the method returns without sending any HTML to the
client. If a bar is required, the method determines if the Previous and Next buttons are
needed. If the display is the first block of the result set, the Previous button is not needed;
if it is the last block, the Next button is not needed. The navigation buttons are displayed
as gray text when deactivated or links when active. The bar is always centered. The
following code defines the outputHitsNavigator method.

protected void outputHitsNavigator(int maxHits,int current


,int curMax,String query
,String url,PrintWriter w)
{
boolean needPrev,needNext;
int prev,next;

- 206 -
if(curMax>maxHits)
{
/*
Only need a previous button if we are not displaying
the first set of results
*/
needPrev = (current != 0);
/*
Only need a next button if we are not displaying
the last set of results
*/
needNext = ((current+maxHits)<curMax);

w.println("<CENTER>");
if(needPrev)
{
w.print("<A HREF=\ "");
w.print(url);
w.print("?");
w.print(QUERY_FIELD_NAME);
w.print("=");
w.print(URLEncoder.encode(query));
w.print("&");
w.print(MAX_FIELD_NAME);
w.print("=");
w.print(maxHits);
w.print("&");
w.print(CURRENT_FIELD_NAME);
w.print("=");
prev = (current-maxHits);
w.print(prev);
w.println("\ "><prev</A> ");
}
else
{
w.print("<FONT COLOR=\ "#777777\ ">");
w.print("<prev ");
w.println("</FONT>");
}

if(needNext)
{
w.print("<A HREF=\ "");
w.print(url);
w.print("?");
w.print(QUERY_FIELD_NAME);
w.print("=");
w.print(URLEncoder.encode(query));
w.print("&");
w.print(MAX_FIELD_NAME);
w.print("=");
w.print(maxHits);

- 207 -
w.print("&");
w.print(CURRENT_FIELD_NAME);
w.print("=");
next = (current+maxHits);
w.print(next);
w.println("\ ">next></A>");
}
else
{
w.print("<FONT COLOR=\ "#777777\ ">");
w.print("next>");
w.println("</FONT>");
}

w.println("</CENTER>");
}
}

The navigation links use query strings to tell the servlet the query, the maximum number
of hits to display as defined in the initial request, and the start for the result block to
display, if the link is selected. The Next button moves up maxHits items, and the Previous
button moves back maxHits. The URL for the servlet itself was determined in
handleQuery, and the query is encoded to make this a proper URL. Using the example
HTML file SearchTester.html listed at the end of the chapter, this navigation bar will
contain HTML such as the following:

<CENTER>

<A HREF="http://192.168.0.173:8080/servlet/htmlsearch/
?query=java&maxhits=25&hitstart=0"><prev</A>

<A HREF="http://192.168.0.173:8080/servlet/htmlsearch/
?query=java&maxhits=25&hitstart=50">next></A>

</CENTER>

That completes the HTMLSearchServlet class. The results from a search with this servlet
look like Figure 8.2.

- 208 -
Figure 8.2: HTMLSearchServlet results.

Next let’s look at the IndexManager to see how to use synchronization to protect the
indices.

IndexManager
IndexManager uses a hash table to associate directories and indices. When the servlet or
any other object requests an index, IndexManager checks whether the index is already
loaded. If the index is available, it is returned. The index is thread safe and uses minimal
synchronization because it is basically read-only once it is loaded. The trick is to protect
the index during loading and to load only one index per directory.

To manage the loading process for each index, IndexManager uses subclasses of thread
called IndexLoader. These loaders and the manager itself avoid loading indices multiple
times by synchronizing on the manager’s hash table whenever a decision is made to load
an index. The loader locks the hash table to add a new index to the available pool. When
a request comes in for an unloaded index, the manager puts the indexloader in the hash
table with the requested directory. If another thread requests that index, the manager
knows to return null, without creating a new loader.

An updateInterval is defined to tell the manager when to recheck a directory for changes.
The default is to check only the first time the index is loaded. You can set this to any time,
in seconds, using the updateinterval configuration parameter on the HTMLSearchServlet.
To help manage updates, the manager uses a hash table containing the last index load
time. The following code defines IndexManager.java.

package index;

import index.*;
import java.util.*;
import java.io.*;

import DebugLog;

public class IndexManager


{
protected static Hashtable indices;
protected static Hashtable loadtimes;

public static long updateInterval = 0;//seconds


// Cache once and then reuse.
static
{
indices = new Hashtable();
loadtimes = new Hashtable();
}

public static HTMLIndex indexForDirectory(String dir


,String rel)
{
HTMLIndex retVal = null;
Object test = null;
IndexLoader loader=null;

- 209 -
if(dir == null) return null;

// Support multithreaded access.


synchronized(indices)
{
// Does the directory exist in the hash table?
test = indices.get(dir);

if(test == null)
{
// No, then load it.
loader = new IndexLoader(dir,rel,indices);

indices.put(dir,loader);
// Keep track of how long it took to load.
loadtimes.put(dir,new Date());
}
else if(test instanceof HTMLIndex)
{
retVal = (HTMLIndex) test;

if(updateInterval>0)
{
try
{
Date now = new Date();
Dateload;
long nw,ld;

load = (Date) loadtimes.get(dir);

nw = now.getTime();
ld = load.getTime();

if(nw > (ld+(updateInterval*1000)))


{
if(retVal.indexNeedsRebuilding())
{
//reload
loader =
new
IndexLoader(dir,rel,indices);

indices.put(dir,loader);
loadtimes.put(dir,new Date());

retVal = null;
}
else
{
//Update load time.
loadtimes.put(dir,new Date());

- 210 -
}
}
}
catch(Exception exp)
{
retVal = (HTMLIndex) test;
indices.put(dir,retVal);//just in case
loadtimes.put(dir,new Date());
}
}
}
//else it is the index loader working...
}

if(loader != null)
{
if(loader.needsBuild())
{
loader.start();
}
else
{
loader.load();
retVal = (HTMLIndex) indices.get(dir);
}
}

return retVal;
}
}

In this code, IndexManager is not as concerned about loading indices as building them. If
an index file is available for a directory, it is loaded in the same thread that requested the
index. Only directories that do not yet contain an index file cause the manager to spawn a
thread to build the index and return null. Notice that the use of synchronization around
the loading thread is minimized so that when one thread loads an index, the others can’t
get it, but they also don’t have to wait for it to load before they can access another index
or get the null return value.

IndexLoader is defined in the same file as IndexManager. Its job is to load the index and
add it to the manager’s hash table. The loader tells the index to rebuild, if necessary, if it
is run in a background thread, but it will simply load the existing file if the index is up to
date. This prevents the index from checking whether it needs to update twice: once when
asked by the manager and once when told to load. Here is the code for IndexLoader as
defined in IndexManager.java:

class IndexLoader extends Thread


{
protected String dir;
protected String rel;
protected Hashtable indices;
protected HTMLIndex newIndex;
protected boolean buildIfNec;

- 211 -
public IndexLoader(String d,String r,Hashtable holder)
{
dir = d;
rel = r;
indices = holder;
buildIfNec = false;

newIndex = new HTMLIndex(new File(dir));


}

public boolean needsBuild()


{
return newIndex.indexNeedsRebuilding();
}

public void run()


{
buildIfNec = true;
load();
}

public void load()


{
if(dir == null) return;

if(rel!=null) newIndex.setRelativePath(rel);

newIndex.loadIndex(buildIfNec);

synchronized(indices)
{
indices.put(dir,newIndex);
}
}
}

Both the manager and loader use the provided relative path to tell the index how to return
files. If this relative path is not provided, a file’s absolute path is returned from queries.
Although this absolute path is not useful to a servlet, you might use it in an application that
relies on the index package.

SearchTester.html
To test the servlet, we created an HTML file called SearchTester.html. This file provides a
form with two input elements and two submit buttons, as pictured in Figure 8.3.

- 212 -
Figure 8.3: SearchTester.html.

One input element is a text field for entering the query. The second input element is a
pull-down list for choosing the maximum number of hits to display. One submit button
initiates a search; the other requests that the help file be displayed. The following code
listing is the SearchTester.html file.

<HTML>
<HEAD>
<TITLE>
Search Tester
</TITLE>
</HEAD>
<BODY>
<FORM METHOD=GET
ACTION="/servlet/htmlsearch/">
Query:
<INPUT NAME="query" VALUE="" SIZE=25>
<SELECT NAME="maxhits" SIZE=1>
<OPTION VALUE="25">25
<OPTION VALUE="50">50
<OPTION VALUE="100">100
</SELECT>
<BR><BR>
<INPUT TYPE="Submit" NAME="submit" VALUE="Search">
<INPUT TYPE="Submit" NAME="submit" VALUE="Help">

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

Notice in the HTML that the form ACTION provides a / for the path information. This causes
the search servlet to search the document root of the Web server.

Summary
The Web site searching servlet discussed in this chapter demonstrates a number of
important guidelines. First, don’t make the user wait too long for a response. In this case,
a special page is used to prevent the user from waiting for an index to be built, because
this can be a leng