OTN's Servlet-based Surveys
Part 4 - How it Works
August 1999 |
 |
This part of the case study describes how the Survey Application was
designed and implemented. It presents the following topics:
Why Servlets?
A servlet is a server-side counterpart to an applet: an applet adds features
to a browser, a servlet adds features to a web server. The Survey Application
uses servlets to generate HTML forms and present them to a client, making
web pages interactive. CGI scripts can do this too, but servlets provide
many advantages, including:
-
better performance
-
better resource management
-
better error handling
Oracle JDeveloper, Oracle's component-based Java programming tool, includes
everything you need to build, test, and debug Java servlets. You can even use
wizards to build HTML database forms without coding: JDeveloper generates Java
code based on your specifications. These servlets
-
Process HTTP requests from a client.
-
Query the database.
-
Generate HTML forms that display retrieved data to the client.
-
Post changes to the database based on client interactions with the HTML
forms.
JDeveloper can generate code skeletons for the servlet's methods, including
service,
doGet, doPost, doPut, and
doDelete.
Of course, you can customize the code as needed, with complete access to
features including:
-
Validation
-
Custom forms (Query, Browse, Insert, etc.)
-
UI hints
-
Cascading Style Sheets
-
JavaScript
You can also test and debug servlets from within JDeveloper. The wizard provides
a simple interface to specify runtime parameters, and you can run servlets directly
from the IDE�you don't need a web server.
How Do Servlets Work?
Basically, servlets process client requests and respond to them. The Survey
Application uses HTTP servlets: they extend javax.servlet.http.HttpServlet
which in turn extends javax.servlet.GenericServlet. Request data comes
to these servlets in an HttpServletRequest object. The servlet calls HttpServletRequest
methods to extract:
-
Servlet parameter names and values.
-
Remote host name that made the request.
-
Server name that received the request.
-
The client's protocol and version information.
-
Protocol-specific data, such as methods for accessing HTTP-specific header
information for the HTTP protocol.
-
Input stream data from clients that are using HTTP methods such as POST,
GET, and PUT.
To respond to requests, the servlets call methods from the HttpServletResponse
class to:
-
Set the content length of the reply.
-
Set the MIME type of the reply.
-
Provide an output stream and writer capability so the servlet can send
the reply data.
-
Perform protocol-specific capabilities such as manipulating HTTP-specific
header.
During its lifetime a servlet is loaded, initialized, executed, and destroyed
(removed from service). All of these actions are performed by the web server.
The following figure shows the general flow of execution.
In the first phase, the web server loads the servlet and executes the servlet's
init method. The web server does not call this method again until after
the servlet is destroyed. Here is the code from SDSessionServlet.init.
public void init(ServletConfig config) throws ServletException {
super.init(config);
m_context = config.getServletContext(); // Get the Servlet Context.
// Initialize the Session Cache. The Session Cache keeps track of
// all the sessions started by the user.
m_sessionCache = new Hashtable(10);
m_myauth = new Authorization(); // Instantiate the authorization class.
}
|
The execution phase begins when the servlet receives a request from a client.
Each request invokes the servlet in a separate thread. The servlet processes each
request in its service method. The following snippet is from SDSessionServlet.service.
public void service (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Initialize the servlet path setting if (this.s_servletPath == null) { String l_servletPath = request.getRequestURI(); StringTokenizer st = new StringTokenizer(l_servletPath,"?"); s_servletPath = st.nextToken(); } // Get the session associated with the request.
// If none exists, session is set to null. SDSession session = findValidSession (request);
// Determine the REQUEST_TYPE i.e what is being asked for by looking // at the REQUEST_TYPE field first. If REQUEST_TYPE parameter is set, // then proper method is invoked. String requestType = request.getParameter ("REQUEST_TYPE"); if (session != null && requestType == null) { endSession (session); // Display Log Out Page response.setContentType("text/html"); PrintWriter out = new PrintWriter (response.getOutputStream()); // Setting the database connection Connection l_connection; try { InitialContext ic = new InitialContext(); m_dataSource = (DataSource)ic.lookup(m_SURVEY_DATASOURCE); l_connection = m_dataSource.getConnection(); // Sets the auto-commit property for the connection to be false. // By default, the connections always auto-commit. l_connection.setAutoCommit(false); out.println(m_myauth.getLoginPage(l_connection, true)); l_connection.close(); } catch (SQLException se) { System.out.println("SQL Error while connecting to the database : "+ se.toString()); } catch (NamingException ne) { System.out.println("Naming Exception Error while connecting to the database : "+ ne.toString()); } catch (Exception ne) { System.out.println("Other Error while connecting to the database : "+ ne.toString()); } out.close(); }
... } else DoAction (requestType, session, request, response); }
|
Servlets execute until they are removed from service, usually by the web server
administrator. To remove the servlet, the web server calls the servlet's destroy
method. The Survey Application servlets do not use expensive resources, so they
do not override destroy. The superclass implementation handles cleanup
chores.
JavaScript Benefits
The Survey Application servlets use JavaScript to enable client-side processing.
This technique increases application performance by avoiding network round-trips.
See ListSelect.getJS and JSMultiList.genJavaScript in
the SDServlet package for examples. Following is a snippet from ListSelect.getJS.
It shows how a Java method can return the text of a JavaScript function.
public String getJS() {
return
"<script language=\"JavaScript1.1\">\n"+
"var idx = 0;\n"+
"function swapandHighlight( offset, idx, newIdx )\n"+
"{\n"+
" if ( (idx == -1) || ( idx == 0 && newIdx == -1 ) ||\n"+
" ( idx == document.forms[0].elements[offset].length-1 && newIdx
== document.forms[0].elements[offset].length ) )\n"+
" {\n"+
" return;\n"+
"}\n"+
" swap( offset, idx, newIdx );\n"+
" document.forms[0].elements[offset].selectedIndex = newIdx;\n"+
"}\n"+
...
|
The Survey Response servlet also uses JavaScript to validate user responses
on the client, rather than taking a network round-trip for each response. The
validation JavaScript is attached to each survey when it is generated, as shown
in the following snippet from SRServlet.putSurveyElement.
...
// For a response Datatype number, check response vaildity
String l_valid = "";
if (l_qsn.m_responseDatatype.equalsIgnoreCase("number")) {
if (l_qsn.m_formatMask == null)
l_valid = "checkFloat(document.forms[0]."+l_name+")";
else if (l_qsn.m_formatMask.equalsIgnoreCase("INTEGER"))
l_valid = "checkInt(document.forms[0]."+l_name+")";
else if (l_qsn.m_formatMask.equalsIgnoreCase("POSINTEGER"))
l_valid = "checkPosInt(document.forms[0]."+l_name+") ";
else if (l_qsn.m_formatMask.equalsIgnoreCase("NEGINTEGER"))
l_valid = "checkNegInt(document.forms[0]."+l_name+") ";
...
} else if (l_qsn.m_responseDatatype.equalsIgnoreCase("date")) {
l_valid = "checkDate('"+l_qsn.m_formatMask+"',
document.forms[0]."+l_name+") ";
}
if (!l_valid.equals("")) {
l_buffer.append(" onChange=\"javascript:"+l_valid+"\" ");
p_submitVal.addElement(l_valid);
}
...
|
Reusing Java Classes
The application reuses Java classes to keep the code footprint small and
to make the code itself easier to write and maintain. For example, the
Survey Repsonse package includes classes that generate an HTML form based
on survey specification data. The Survey Definition servlet reuses these
classes to provide a Preview feature without actually invoking the Survey
Response servlet. Here is some example code from SDSessionServlet.DoAction.
It calls SRSessionServlet.generateSurveyForm to display a preview
of survey to the client.
...
if (action.equals("PREVIEW_SURVEY")) {
Survey s = (Survey) session.popElement();
try {
session.getConnection().setAutoCommit(false);
s.wizardFinish(session, request, response,true);
SRSessionServlet.generateSurveyForm(session.getConnection(),
Long.parseLong(s.m_surveyID),response);
session.getConnection().rollback();
session.getConnection().setAutoCommit(true);
} catch (SQLException ex) {
response.setContentType("text/ascii");
response.getOutputStream().println("Error: "+ex.toString());
}
}
...
|
Following is code from SRSessionServlet.generateSurveyForm:
public static void generateSurveyForm(Connection p_conn, long p_surveyID,
HttpServletResponse response)
throws ServletException, IOException {
PrintWriter out = new PrintWriter (response.getOutputStream());
Survey l_survey = null;
try {
// Create a Survey object to hold the survey definition
l_survey = new Survey(p_surveyID);
l_survey.retrieveFromDB(p_conn); // Retrieve Survey contents from the db
printSurveyForm(out, l_survey, true, false); // Print out HTML form
} catch (SQLException ex) {
out.println(" <P><B>Error generating Survey Form</B><p>\n");
}
l_survey = null;
System.gc();
out.close();
}
|
Threads and Sessions
The Survey Application servlets are multi-threaded. They handle multiple
survey definition and response interactions in parallel by maintaining
state information in session objects (defined in the SDSession and SRSession
classes). Each session maintains information about the following items:
-
User ID - Distinguishes different users.
-
Database connection - Each session needs its own connection to track user
transactions.
-
Wizard stack - Tracks wizard status as survey elements are created.
-
Edited object - Tracks status of survey elements as they are edited.
The servlets identify sessions by setting cookies, as shown in this snippet
from SDSessionServlet.startNewSession:
...
// Create a new session.
session = new SDSession (userId, dbConnection);
// Create a client Cookie. Cookies are a mechanism that a servlet can use
// to have clients hold a small amount of state-information associated with
// the user.
Cookie c = new Cookie("SDSessionServlet", String.valueOf (session.getUserId()));
// Put the Session into the Session Cache
m_sessionCache.put (c.getValue(), session);
c.setPath ("/");
response.addCookie(c); // Add the cookie to the response
...
|
The Survey Wizard also uses information that persists across servlet interactions.
The following example snippet comes from SDSessionServlet.service:
public void service (HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (this.s_servletPath == null)
s_servletPath = request.getRequestURI();
// Get the session associated with the request. If none exists, set session to null.
SDSession session = findValidSession (request);
...
|
The following code from SDSessionServlet.findValidSession searches
through the cookies associated with an HTTP request to find a session and
a user ID.
private SDSession findValidSession (HttpServletRequest request) {
// Get the Cookies associated with the request
Cookie c[] = request.getCookies();
SDSession session = null;
for (int i=0;(c!= null) && (i<c.length);i++) {
if (c[i].getName().equals("SDSessionServlet")) {
String userID = String.valueOf (c[i].getValue());
session = (SDSession) m_sessionCache.get(userID);
break;
}
}
return session;
}
|
When the Survey Response servlet receives a request to generate a survey,
the method SRSessionServlet.findSession queries the database and
stores the survey definition in the session object. It uses this data to
display an HTML form to the user. Later, the servlet accesses the stored
definition to validate a user's responses. The following snippet comes
from SRSessionServlet.processResponse:
public boolean processResponse(SRSession p_session,
HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException
{
...
// Retrieve contents of the survey.
Survey l_survey = p_session.getSurvey();
Vector l_contents = l_survey.m_surveyContents;
// Loop through all the questions in the survey and retrieve
// the responses to the question from the httpRequest object.
boolean l_error = false;
for (int i=0; i<l_contents.size();i++) {
SurveyElement l_elem = (SurveyElement)l_contents.elementAt(i);
...
|
Java and HTML
The following code snippet from the Survey Definition servlet shows how
a servlet can send HTML to a client. This code comes from Question.generateHTML.
In the first few lines, it calls HttpServletResponse.getOutputStream
and stores the results in the local ServletOutputStream variable named
l_out, which represents a pipeline to the client. Then it uses println
calls to transmit several lines of hard-coded HTML.
public void generateHTML(SDSession p_session, HttpServletRequest p_request,
HttpServletResponse p_response) throws Exception {
String l_chgrpJS = "";
// Create a ServletOutputStream to write the output
ServletOutputStream l_out = p_response.getOutputStream();
// Sets the content type of the response
p_response.setContentType("text/html");
if(m_questionId == -1) { // To create a Question
if (!this.m_qsnForSurvey)
// Print the HTML header of the page
l_out.println(SurveyHTMLUtil.getPageHeader(p_session.getUserId(),
"Create a new Question","QUESTION",false,p_session.getUserFlag()));
else
l_out.println("<HTML><HEAD><TITLE>Create Question for Survey</TITLE>" +
"</HEAD><BODY bgcolor=\"#c0c0c0\">");
String l_errors = new String(this.m_error);
if (!l_errors.equals(""))
l_out.println("<FONT SIZE=+2>ERRORS: </FONT><BR>"+l_errors);
// Display the guide lines to create a Question in HTML page
l_out.println("<BR><CENTER><TABLE bgcolor=#c0c0c0 border=1 WIDTH=475>");
l_out.println("<TR><TD><FONT FACE=\"Arial,Helvetica\" size=2>");
l_out.println(" Using this form you can create a new Question. ");
l_out.print(" To create a new Question: <BR>");
l_out.println(" . Enter the Question Name, Text and choose a ");
l_out.print("Question Format<BR>");
l_out.println(" . Choose the type of question and enter the ");
l_out.print("properties<BR>");
l_out.println(" . Save the Question by pressing the Create button");
l_out.println("</TR></TD></FONT></TABLE></CENTER><BR>");
...
|
Performance Tips
Because complex survey forms can take a long time to generate, the Survey
Application
-
Stores survey definitions in the database.
-
Caches HTML forms for fast access.
Surveys in the Database
The Survey Definition servlet can generate the HTML code and store it in
the database. This can improve performance for end-users, because reading
HTML from the database is faster than generating it, especially for surveys
with many questions. The method SDSessionServlet.generateCLOB
implements this feature, as shown in the following snippet.
...
// Select the CLOB column to write the HTML data
l_pstmt = l_connection.prepareStatement(
"select survey_generated from survey_pregen p where survey_id = ? for update");
l_pstmt.setInt(1,l_surID);
l_rst = l_pstmt.executeQuery();
if (l_rst.next()) { // CLOB column found
CLOB l_clob = ((OracleResultSet)l_rst).getCLOB(1); // Retrieve CLOB column
// Open stream to write character data
Writer l_surveyStream = l_clob.getCharacterOutputStream();
// Generate the HTML for the survey
StringBuffer l_buffer = SRSessionServlet.getSurveyHTML(l_connection, l_surID);
...
|
Caching Forms for Fast Access
The Survey Response servlet caches the HTML code for a survey when a user
reponds to it. When a request comes again for the same survey, the servlet
reads the HTML from the cache. The following snippet from SRSessionServlet.generateSurveyForm
looks for a survey in the cache, then queries the database. If the survey
does not exist in either place, it must be generated.
public void generateSurveyForm(SRSession p_session, HttpServletResponse response)
throws ServletException, IOException {
// Obtain survey response output stream
PrintWriter out = new PrintWriter (response.getOutputStream());
response.setContentType("text/html");
Connection l_conn = p_session.getConnection(); // Obtain the database connection
try {
long l_surID = p_session.getSurveyID(); // Survey ID
Survey l_survey = null; // Object holds the survey definition
StringBuffer l_buffer = null; // Object holds survey HTML
// Check if the survey is present in the cache
boolean found = false;
for (int i=0;i<3;i++) {
if (m_surveyCache[i] != null && m_surveyCache[i].m_surveyID == l_surID
&& !m_surveyCache[i].isExpired()) { // Valid survey found
m_surveyCache[i].setUsed(); // Set the last used time for the survey
l_survey = m_surveyCache[i].m_survey; // Obtain the survey definition from cache
l_buffer = m_surveyCache[i].m_htmlBuffer; // Obtain the survey HTML from cache
found = true; // Set the found flag
break;
}
}
if (!found) { // Survey not found in cache
// Retrieve Survey definition from the database
l_survey = new Survey(p_session.getSurveyID());
l_survey.retrieveFromDB(l_conn);
// Query to retrieve pregenerated survey from the CLOB column,
// survey_generated from the table survey_pregen
PreparedStatement l_pstmt = l_conn.prepareStatement(
"select use_pregen_ind , survey_generated from survey_pregen where survey_id = ?");
l_pstmt.setLong(1, p_session.getSurveyID());
ResultSet l_rst = l_pstmt.executeQuery();
// If pregenerated survey found, and the use_pregen_ind flag is set to true,
// Read survey HTML from the CLOB
if (l_rst.next() && l_rst.getString(1).equals("Y")) {
// Retrieve CLOB column to read survey HTML
oracle.sql.CLOB l_clob = ((oracle.jdbc.driver.OracleResultSet)l_rst).getCLOB(2);
// Open a character stream
Reader l_surveyStream = l_clob.getCharacterStream();
l_buffer = new StringBuffer(); // Holds the survey HTML
char[] l_buff = new char[1000]; // Temporary buffer
int l_nread = 0;
// Read from the clob read stream, and read the survey HTML into the
// StringBuffer, l_buffer.
while ((l_nread=l_surveyStream.read(l_buff)) != -1) // Until done
l_buffer.append(l_buff,0,l_nread);
l_surveyStream.close(); // Close stream.
} else { // Survey needs to be generated dynamically
...
|
Database Schema
The Survey Application database schema provides tables to hold Survey Definitions
and Survey Responses.
Survey Definition Tables
| Table |
Description |
| SURVEYS |
Stores survey properties. Columns include TITLE,
HEADER, and FOOTER. The primary key column is ID, which is system-generated. |
| SURVEY_CONTENTS |
Stores questions and display elements used by
surveys. It has two foreign key columns: SUR_ID and QSN_ID. The sequence
of the Survey Contents is maintained using a SEQUENCE_NO column. It also
has columns for attributes such as font and color. |
| QUESTIONS |
Stores question properties. Columns include
TEXT, NAME, RESPONSE_TYPE, and RESP_DATATYPE. It has one foreign key column,
CHGRP_ID, which holds the Choice Group of the question. The primary key
column is ID, which is system-generated. |
| CHOICE_GROUP |
Stores Choice Group properties such as NAME.
The primary key column is ID, which is system-generated. |
| CHOICES |
Stores all available choices. The primary key
column is ID, which is system-generated. |
| GROUP_CHOICES |
Relates Choices to Choice Groups. It has two
foreign key columns: CHO_ID and CHGRP_ID. |
| SURVEY_PREGEN |
Stores HTML for a survey form. Improves application performance by
providing pre-generated forms instead of generating code on the fly for
each user. The CLOB column SURVEY_GENERATED stores the HTML code. |
Survey Response Tables
The following tables store Survey Responses:
| Table |
Description |
| RESPONSE_MASTER |
Master table for a survey response. Columns
include RESPONSE_DATE and MEMB_ID. It has one foreign key column: SUR_ID.
The primary key column is ID, which is system-generated. |
| RESPONSE_DETAILS |
Stores responses for every question in a survey.
It has one foreign key column: qsn_ID. The column ANSWER holds the user's
response. The column stores text, one Choice ID, or multiple Choice IDs,
depending on the question type. |
Java Classes
This topic describes the Java classes that implement the Survey
Definition and Survey Response components
of the Survey Application.
Survey Definition Component
The Survey Definition component consists of one servlet, SDServlet, implemented
by the following kinds of classes:
Servlet Implementation Classes
| Class |
Description |
| SDSessionServlet |
The main class of the Survey Definition component. Services
HTTP requests and maintains session information across invocations. Displays
the login screen to the user. |
| Authorization |
Authorizes users logging in to the application. Provides
the method getLoginPage, which displays a login form and passes
user input to SDSessionServlet.service. |
| SDSession |
Maintains Survey Definition session information and also
state-related information. |
| ConnectionParams |
Defines database connection parameters for the application.
Resides in the SRServlet package. |
HTML Generation Classes
| Class |
Description |
| SDUtil |
Provides static methods that return HTML pages such as
the Main Page, Surveys Main Menu, and the Question Main Menu. Information
in these pages does not persist across invocations. |
| SurveyHTMLUtil |
Provides methods that generate HTML for elements such as
page header and page footer, which are common across all survey pages. |
| CHListSelect |
Outputs JavaScript code and HTML tags for a form. When
a user submits the form, this class processes the HTTP response, and returns
a list of selected items, in the order in which they are selected. |
| ListSelect |
Similar to CHListSelect, but works with Surveys instead
of ChoiceGroups. |
| JSMultiList |
Generates JavaScript and other HTML tags to define a one-to-many
mapping between an HTML List and Property TextFields. |
| PreGeneration |
Generates HTML for a survey form and writes it to a table. This can
improve performance for end-users, because reading HTML from the database
is faster than generating it dynamically, especially for surveys with many
questions. |
Survey Element Classes
These classes generate HTML pages for creating, editing, saving, and deleting
survey elements.
| Class |
Description |
| Survey |
Provides a user interface for working with a survey. Uses
JSMultiList and ListSelect classes. |
| Question |
Provides a user interface for working with questions. Available
question types are: Free Text (text field), One of Many (radio buttons
or list), Many of Many (check boxes or multi-choice list). |
| ChoiceGroup |
Provides a user interface (including a list of avilable
choices) for working with choice groups. |
| Choices |
Provides a user interface for working with choices. |
Survey Response Component
This component consists of one servlet, SRServlet, which generates HTML
for a Survey. It also validates the responses and stores them in the database.
Two kinds of classes implement this component:
Servlet Implementation Classes
| Class |
Description |
| SRSessionServlet |
The main class of the Survey Response Component. It implements
the following features:
-
Generates a Survey HTML form
-
Maintains session information across invocations.
-
Validates user responses
-
Stores responses to the database
This servlet must be invoked with a SURVEY_ID and a MEMBER_ID parameter. |
| SRSession |
Maintains Survey Response session information and also
state-related information. |
| ConnectionParams |
Defines database connection parameters for the application. |
| SurveyCache |
Caches the HTML for survey forms as users respond to them. The application
reads surveys from the cache when possible, because it's faster than generating
HTML code for each user request. |
Survey Element Classes
| Class |
Description |
| Survey |
Retrieves a Survey Definition from the database and stores
it in member variables. Provides ordered list of SurveyElements, which
can be Questions and Display Elements. |
| SurveyElement |
Holds the properties for each survey question or display
element. If the survey element is a question, it holds a Question object
that stores the properties of the question. |
| Question |
Holds properties of a Question, such as format. It also
provides two methods for processing reponses:
-
validateAndStoreResponse validates the response for the question
and returns any errors.
-
saveResponse saves responses to the database if validation was
successful.
|
| Choice |
Holds the properties of each choice of a question. |
Installing the Survey Application
Using the Survey Application
Analyzing Survey Results
How it Works
Questions or comments? Post a message in OTN's Sample Code discussion
forum or send email to the author.
Case Study: OTN's Servlet-based Surveys
Author: Robert Hall,
Oracle Corporation
Date: August 1999
This document is provided for information
purposes only and the information herein is subject to change without notice.
Please report any errors herein to Oracle Corporation. Oracle Corporation
does not provide any warranties covering and specifically disclaims any
liability in connection with this document.
Oracle is a registered trademark
and Enabling the Information Age is a trademark or registered trademark
of Oracle Corporation. All other company and product names mentioned are
used for identification purposes only and may be trademarks of their respective
owners.
Oracle Corporation
World Headquarters
500 Oracle Parkway
Redwood Shores, CA94065
U.S.A.
Worldwide Inquiries:
+1.650.506.7200
Copyright © Oracle Corporation 1999,
2000
All Rights Reserved
|