OTN's Servlet-based Surveys

Part 4 - How it Works

August 1999
Oracle8i

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:&nbsp;<BR>");
        l_out.println("&nbsp;. Enter the Question Name, Text and choose a ");
        l_out.print("Question Format<BR>");
        l_out.println("&nbsp;. Choose the type of question  and enter the ");
        l_out.print("properties<BR>");
        l_out.println("&nbsp;. 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

Survey Definition Schema
 
 
 
 
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

Survey Definition Schema
 

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



 
 
 
 
E-mail this page
Printer View Printer View
Oracle Is The Information Company About Oracle | Oracle RSS Feeds | Careers | Contact Us | Site Maps | Legal Notices | Terms of Use | Privacy