Servlet examples

Examples include:


Membership example: database access

Overview

The "membership" example has a simple form used to enter data, a servlet that receives the data and organizes the response, a "bean" that is responsible for data transfer to/from the database, and a simple database. The webapp structure is:

Membership WebApp structure

The main form takes data on the name, age, gender and email address of new subscribing members to some interest group.

Membership form

The Servlet owns a connection to a database; the example code is set up to use an ODBC database (ODBC Data Source named MembersOnly mapped to an ODBC database), and a small Microsoft Access database is available. This database connection is established when the servlet is first loaded into the servlet engine. If a database connection cannot be established, later attempts to use the servlet get redirected to a page appologising for disrupted database service.

The servlet creates a SubscriberRecord object and copies form-input data into this "bean". The SubscriberRecord class has the code that validates inputs. If the data are invalid, the servlet generates a a response page with text identifying the data inputs that were missing or invalid and supplying a new data entry form with any apparently valid inputs already filled in.

If the submitted data appear valid, a new entry is made in the database and a response page is generated welcoming the new member. This has links to a couple of token HTML pages that supposedly represent a part of the members site ('this week events', and 'welcome').

Subscriber Record

This helper class holds the data elements defining a subscriber and handles transfers to/from a database (its load/store methods take an argument that defines the database connection). The class is bean like - it has "get" and "set" functions for each of the data-elements that it looks after.

import   java.sql.*;
public class SubscriberRecord {
	# static members used to define constraints such a limits on length of names
	private static final int NAMELENGTH = 30;
	...
	# instance data members
	private String 	givenName, familyName, eMail;
	private int	age;
	private String	sex;
	private int	id;
	
	# business methods, validation check and "get/set" methods for each data member
	# (except id - this is set from the database)
	public boolean isValid() { ... }

	public String getGivenName() { return givenName; }
	public void setGivenName(String aName) { ... }

	# similar methods for other data members
	...
	
	# database methods
	public boolean loadFromDatabase(int idNumber, Connection db) {
		...
	}

	public int createInDatabase( Connection db) {
		...
	}
}

The "set" methods apply business rules that check the input data. A SubscriberRecord will be left in an invalid state if any input data item is unacceptable.

	public void setSex(String gender) {
		sex = null;
		if(gender.equals("Male")) sex = gender;
		else
		if(gender.equals("Female")) sex = gender;
	}

The isValid method verifies that all inputs were acceptable.

	public boolean isValid() {
	return
		(givenName != null) &&
		  (familyName != null) &&
		    (eMail != null) &&
		      (sex != null)  &&
		      ((age>=MINAGE) && (age <= MAXAGE));
	}

The servlet can simply put all input data from the form into a subscriber record and rely on it to then confirm whether the data were acceptable.

The createInDatabase function accesses one of the database tables to determine the next membership number to allocate, then inserts a new record in the main database table. The servlet owns a single database connection and there is a possibility that two threads might simultaneously try to use this connection; the code actually using the connection is therefore within a synchronization lock.

	public int createInDatabase( Connection db) {
		int idnumber = -1;
		try {
		  synchronized(db) { 
			Statement stmt = db.createStatement ();		
			String request =
			  	"select nextnumber from memnum where fortable='members'";
			ResultSet rset = stmt.executeQuery(request);
			int value = 0;
			if(rset.next()) {
				value = rset.getInt("NEXTNUMBER");
			}
			else { stmt.close(); return -1; }
			value++;
			request = "update memnum set nextnumber=" +
				value + " where fortable='members'";
			stmt.executeUpdate(request);
			stmt.close();
			
			// Prepared statement would be preferable
			// PreparedStatement pstmt =
			// 	db.prepareStatement(
			// 		"insert into members values( ?, ?, ?, ?, ?, ?)");
			// pstmt.setInt(1, value);
			// pstmt.setString(2, givenName);
			// pstmt.setString(3, familyName);
			// pstmt.setString(4, eMail);
			// pstmt.setString(5, sex);
			// pstmt.setInt(6, age);
			// pstmt.executeUpdate();
			
			// Microsoft Access can't handle prepared statements, so give it a
			// simple statement and hope that don't have any pesky names with single quote
			// characters to cause problems!
			// If really were serious should use a StringBuffer to assemble the request.
			
			
			Statement maStmt = db.createStatement();
			String insertStr = "insert into members values (" + Integer.toString(value);
			insertStr = insertStr + ", '" + givenName;
			insertStr = insertStr + "', '" + familyName;
			insertStr = insertStr + "', '" + eMail;
			insertStr = insertStr + "', '" + sex;
			insertStr = insertStr + "', " + Integer.toString(age) + ")";
			maStmt.executeUpdate(insertStr);
			maStmt.close();
		
			idnumber = value;
			db.commit();
		  }
		}
		catch (Exception e) { 
			try { db.rollback(); } catch(Exception whatnow) { }
			// Error message to log file
			System.out.println("Error creating a subscriber record - membership servlet");
			System.out.println(e.toString());
		}
		id = idnumber;
		return idnumber;
	}

The code is updating two tables; these two updates are handled as a single transaction.

Database

The database has two tables - memnum and members. The memnum table is used to allocated membership numbers; it has rows with "nextnumber" (integer) and "fortable" (string identifier) fields. The nextnumber field contains the data used to determine the next unique identifier number that is to be assigned. (Many database systems have autoincrement data types that handle this allocation task automatically; unfortunately implementations vary. An alternative way of finding the next number to allocated would be to select the maximum id currently used in a table and add one.) The main table is the members table with its records of members - as in the Microsoft Access table supplied:

membernum	GIVENNAME	FAMILYNAME	EMAIL	SEX	AGE
1		James		Dane		JD432	Male	32
2		Sandra		Wood		SW03	Female	30
3		Jack		Dancer		jd432	Male	54
4		Sam		Gamges		sg22	Male	24

(The SQL definition would be something like:

create table members (
	membernum	integer,
	Givenname	varchar(50),
	Familyname	varchar(50),
	Email		varchar(50),
	Sex		varchar(8),
	Age		integer
);

The Servlet

The InfoServlet servlet defines:

The data members include the single database connection, an strings that hold the username and password for the database (not applicable with Microsof Access but generally necessary). The username and password data are taken as initialization parameters defined in the web.xml deployment file. The functions used to set up (and close) the database connection are:

public class InfoServlet extends HttpServlet { 
	private  String userName;
	private  String userPassword;
	private static final String dbDriverName = 
				"sun.jdbc.odbc.JdbcOdbcDriver";
	private static final String dbURL = 
				"jdbc:odbc:MembersOnly";
	private Connection dbConnection;

	private void connectToDatabase() { 
		try {
			Class.forName (dbDriverName);
			dbConnection = DriverManager.getConnection(
				dbURL,
				userName, userPassword);
			// Take charge of transactions programmatically
			dbConnection.setAutoCommit(false);
		}
		catch(Exception e) {  
			System.out.println("InfoServlet - failed to connect to database");
			System.out.println(e.toString());
		}
	}
	
	public void init() {
		//System.out.println("Infoservlet initializing");
		userName = getInitParameter("User");
		userPassword = getInitParameter("Password");
		connectToDatabase();
	}

	public void destroy() {
		if(dbConnection != null) {
			try {
				dbConnection.close();
			} catch(Exception e) { }
		}
	}

The doPost method creates its SubscriberRecord and fills it with the form data, checks for validity and maybe persists it to the database, and then generates an appropriate response page


	public void doPost (HttpServletRequest request,
                       HttpServletResponse response)  throws ServletException, IOException
	{ 
		if(dbConnection==null) {
			response.sendRedirect("/membership/NoDB.html");
			return;
			}
		SubscriberRecord aRecord = new SubscriberRecord();
		aRecord.setGivenName(request.getParameter("GivenName"));
		aRecord.setFamilyName(request.getParameter("FamilyName"));
		int anAge = 0;
		try {
			String ageStr = request.getParameter("Age");
			anAge = Integer.parseInt(ageStr.trim());

		}
		catch(NumberFormatException nfe) { }
		// Similar code for other inputs
		...
		if(!aRecord.isValid()) {
			// send error response with partly filled form
			sendRetryResponse(response, aRecord);
			return;
		}
		int membernum = aRecord.createInDatabase(dbConnection);
		if(membernum<1)  {
			// must have had a real database error
			response.sendRedirect("/membership/NoDB.html");
			return;
		}	

		sendWelcome(response, membernum, request.getParameter("GivenName"));
	}

The private methods sendRetryResponse and sendWelcome both set the response type to text/html and then output large amounts of standard text and HTML markup tags.

Deployment

The only interesting feature of the deployment file is its use of initialization parameters:

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
    
<web-app>
	<servlet>
		<servlet-name>infoservlet</servlet-name>
		<servlet-class>InfoServlet</servlet-class>
		<init-param>
			<param-name>User</param-name>
			<param-value>HSimpson</param-value>
		</init-param>
		<init-param>
			<param-name>Password</param-name>
			<param-value>Duh</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>infoservlet</servlet-name>
		<url-pattern>/subscriber</url-pattern>
	</servlet-mapping>
</web-app>


Graphical response page

Histogram servlet output

Servlets can generate graphic response pages. It is simply a matter of:

  1. setting the response type to image/gif or image/jpg,
  2. generating an image with standard AWT graphics calls working on a BufferedImage structure,
  3. using a codec to convert this BufferedImage to a block of bytes with a GIF encoding or a JPEG encoding,
  4. writing the image to the output stream of the response object.

You have two choices for the codec that does the image conversion:

Acme.JPM.Encoders.GifEncoder;
com.sun.image.codec.jpeg.*;
The GIF encoder is part of a package that can be downloaded from Acme Acme.com (yes, there really is an Acme on the web, but it doesn't deal in items that would appeal to the "Road Runner" from the TV cartoons). Sun's JPEG codec is part of the standard Java runtime, it simply isn't documented anywhere. It is probably easiest to use the Sun codec.

The Graphic webapp is particularly simple. The following code assumes that it will be created as the webapp graphic; the servlet will be invoked by pointing a browser at http://localhost:8080/graphic/UtilityHistogram. This webapp requires only a web.xml deployment file, and the HistogramServlet. The web.xml file merely identifies the servlet:

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
    
<web-app>
	<servlet>
		<servlet-name>graphicservlet</servlet-name>
		<servlet-class>HistogramServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>graphicservlet</servlet-name>
		<url-pattern>/UtilityHistogram</url-pattern>
	</servlet-mapping>
</web-app>

The servlet code contains instructions for both codecs with one commented out.

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.awt.*;
import java.awt.image.*;
//import Acme.JPM.Encoders.GifEncoder;
import com.sun.image.codec.jpeg.*;

public class HistogramServlet extends HttpServlet { 
	
	public void doGet (HttpServletRequest request,
                       HttpServletResponse response)
		       throws ServletException, IOException
	{ 
		Graphics g = null; 
		try {
			ServletOutputStream out = response.getOutputStream();
//			response.setContentType("image/gif");
			response.setContentType("image/jpeg");

			BufferedImage bi = new BufferedImage(400,600, 
				BufferedImage.TYPE_INT_RGB);
			g = bi.getGraphics();
			g = bi.getGraphics();
			g.setColor(Color.white);
			g.fillRect(0,0,400,600);
			g.setColor(Color.black);
			g.drawString("Usage Histogram (jpeg)", 20,20);	
//			g.drawString("Usage Histogram (gif)", 20,20);	
			g.setColor(Color.black);
			g.drawString("Apr 2000", 10, 40);
			g.setColor(Color.blue);
			g.fillRect(80, 30, 100, 20);
			g.setColor(Color.black);
			g.drawString("Apr 2001", 10, 64);
			g.setColor(Color.red);
			g.fillRect(80, 54, 54, 20);
			g.setColor(Color.black);
			g.drawString("Apr 2002", 10, 88);
			g.setColor(Color.cyan);
			g.fillRect(80, 78, 155, 20);
			g.setColor(Color.black);
			g.drawString("Apr 2003", 10, 112);
			g.setColor(Color.yellow);
			g.fillRect(80, 102, 185, 20);
			//GifEncoder encoder = new GifEncoder(bi, out);
			//encoder.encode();
			JPEGImageEncoder encoder =
				JPEGCodec.createJPEGEncoder(out);
			JPEGEncodeParam param =
				encoder.getDefaultJPEGEncodeParam(bi);
			// Quality - 100% 1.0; good 0.75, medium 0.5, smudge 0.25
			float quality = 0.75f;
			param.setQuality(quality, true);
			encoder.encode(bi, param);
			encoder = null;
	} finally { if(g!=null) g.dispose(); } }
}

If you want to use the GIF encoder, download the package from Acme ( as a tar.gz file), then decompress and untar it in your webapps/graphic/WEB-INF/classes directory (WinZip understands how to deal with tar.gz files).

"Headless Server" problem

If you are running your Tomcat on a "headless server", you are likely to encounter problems at the point where you try to create the BufferedImage. You will receive a null pointer instead of an image area.

What is a "headless server" and what is the problem?

Problem is caused by java.awt code asking for details of the screen where the image is to be displayed before it creates the BufferedImage. It shouldn't as the screen doesn't matter. A "headless server" is a machine that does not have a console screen associated with the process that is making the request. So problem is likely to arise if you are running on a shared server machine, or you are connected to a workstation using something like rlogin or ssh that gives you a text only connection.

Try the following: before starting Tomcat, set the environment variable as follows (this if for Unix, make suitable substitution for Windows):

export CATALINA_OPTS="-Djava.awt.headless=true"

Sessions and session data

Overview

This example is a reworking of the micro-Amazon example in the PHP section. It uses cookies (or URL rewriting if necessary) to identify a client. It uses Session data - a "shopping cart" that gets filled in with items that a customer wishes to order.

The web site is a members only sales cooperative dealing with computers, books, and furniture. Only people who have established a membership, using the membership servlet illustrated above, are allowed to shop.

The site has:

The code of these servlets uses URL rewriting to encode the session identifier into all URLs in the HTML pages that they generate. Consequently, the system will work even for a user who has disabled cookies in their browser. A cookieless customer will receive pages like:

<p>
You may continue shopping or proceed to checkout.
<ul>
<li><a href="/shop1/Furniture;jsessionid=799EFFC9DD76850CDC3167BF7929F13E">Furniture</a></li>
...
</ul>

instead of a page with simple links

<li><a href="/shop1/Furniture">Furniture</a></li>

and a HTTP header that sets the jsessionid cookie. (You can test your implementation of URL rewriting for cookieless customers by temporarily disabling acceptance of cookies by your browser.)

Session related code and data

The LoginServlet etablishes the session once it has validated the user name and membership number entered in the login form. The code required is simply:

		HttpSession current = request.getSession(true);

This is executed just before the start of the code that generates a response page. The servlet infrastructure will arrange for a "cookie" to be added to the header of the generated response.

The code generating URLs that are to appear in pages generated by these servlets has the following typical form:

		out.print("<li><a href=\"");	
		deptcode = response.encodeURL("/shop1/Computers");
		out.print(deptcode);
		out.println("\">Computers</a>");

The response.encodeURL() method may add the session id to the supplied URL.

The first page returned to the client will contain both a cookie and the rewritten URLs. The client's next response determines how the servlet engine proceeds. If a cookie is returned with the session identifier, subsequent calls to encodeURL are no-ops. If however the session id was instead found only in the request path of the invoked servlet, then the servlet engine will stop trying to add cookies and simply rely on the URL rewriting.

The PurchaseServlet creates and subsequently uses the actual session data:

	private void processOrderItems(HttpSession current,
		HttpServletRequest request) throws ServletException
	{
		Vector v = (Vector) current.getAttribute("shoppingtrolley");
		if(v==null) v = new Vector();
		String[] items = request.getParameterValues("buys");
		if(items!=null) {
			int len = items.length;
			for(int i=0;i<len;i++)
				v.addElement(items[i]);
			current.setAttribute("shoppingtrolley", v);
		}
	}

A simple Vector of Strings is a somewhat oversimplified version of the typical shopping cart but it is sufficient for this illustrative example.

Deployment

The web.xml file is a little lengthier now because it has to define several servlets:

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
    
<web-app>
	<servlet>
		<servlet-name>loginservlet</servlet-name>
		<servlet-class>LoginServlet</servlet-class>
		<init-param>
			<param-name>User</param-name>
			<param-value>HSimpson</param-value>
		</init-param>
		<init-param>
			<param-name>Password</param-name>
			<param-value>Duh</param-value>
		</init-param>
	</servlet>

	<servlet>
		<servlet-name>booksservlet</servlet-name>
		<servlet-class>BooksServlet</servlet-class>
	</servlet>
	
	...
	and some more
	...
	
	<servlet-mapping>
		<servlet-name>loginservlet</servlet-name>
		<url-pattern>/login</url-pattern>
	</servlet-mapping>

	and some more
	...
	
	<servlet-mapping>
		<servlet-name>checkoutservlet</servlet-name>
		<url-pattern>/Checkout                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      </url-pattern>
	</servlet-mapping>
	
</web-app>

Older versions of Tomcat were fairly tolerant of web.xml files that did things like declare one servlet and its mappings, then declare a second servlet and its mappings. The current Tomcat is much more pedantic insisting that the web.xml file obey the rules.

The rules (the "document type definition") really are at http://java.sun.com/j2ee/dtds/web-app_2_2.dtd. The rule defining the correct order of the main elements in a web.xml file is:

<!ELEMENT web-app (icon?, display-name?, description?, distributable?,
context-param*, servlet*, servlet-mapping*, session-config?,
mime-mapping*, welcome-file-list?, error-page*, taglib*,
resource-ref*, security-constraint*, login-config?, security-role*,
env-entry*, ejb-ref*)>

This says that all servlet entries must come before the servlet-mapping entries.

This webapplication consists of several HTML pages, a deployment file, and the servlets. It is however more clearly defined than the collection of PHP scripts and static HTML pages used for the corresponding example in the PHP section. The deployment file identifies the components and there is a defined form for the web archive file that can be used to distribute components.

This web archive is atypical. The code isn't compiled. You will have to get in and unzip and then edit and compile it; you have to edit at least the database details. Although this (zipped) war file does not really a package a distributable servlet it illustrates the principle of distributing all the components of a webapplication as a single entity. (Zipping a war file? File is already compressed so don't gain much further compression. This is an attempt to get around a proble that occurs with some browsers that think a ".war" file is some strange kind of text file.) The webapp should be installed as the "context" shop1 (the term "context" really just identifies the name of the tom/webapps/ sub directory with the application files).


Users and roles: controlling access and servlet behavior

Overview

The final example illustrates how access to servlets can be restricted to particular classes of users (users in specific "roles") and how the behavior of individual servlets can be made to adapt to the role of the user. It also illustrates application data that are shared by all the servlets in a webapp.

The example is the one where all employees in a company can create database records of the number of hours that they work on tasks; the entries in the database identify the employee, the number of hours of work, and the type of work (selected from a predefined list). (The Acme company whose records are used in this example has no relation to that which owns the www.acme.com web site!) Employees can obtain summaries of all the work hours that are recorded for them and the pay that they are due. A manager can also obtain details of the hours recorded for each person that they manage. Finally, the boss can change the rates paid for different types of work, and can define new types of task.

This webapp has three servlets and makes use of two helper classes. The servlets are

The helper classes are DBInfo and RatesRecord. The DBInfo class helps to isolate details of the database used from the rest of the application. The RatesRecord class is a helper bean used to store current pay rates.

The structure for the webapplication is:

Webapp structure

The webapp uses the security "roles" - worker, manager, and boss. The web.xml deployment file contains deployment restrictions on the servlets; these restrictions identify the "role" that must be filled for a user to access a servlet, and a login mechanism that allows users to identify themselves (user name and password).

Database

The application requires a simple database with three tables (example Access database). The tables in the Access database are:

manages

employee	manager
David		Claire
Susan		Claire
Terry		Claire
Martin		Samuel
Leila		Samuel
Keith		Samuel
Claire		Colin
Samuel		Colin

(Table definition is simply two varchar fields.)

Work

name	Activity		Hours
Claire	managing		18
Claire	sales presentations	8
Claire	meetings		7
David	design			6
David	coding			7
David	testing			9
Colin	conference		6
Colin	golf			4
Colin	client entertainment	6
Colin	business travel		8
Colin	networking		5
Colin	conference		5
Terry	coding			18
Leila	design			6

(Table definition is two varchar fields and a double.)

Rates

Activity		Rate
thinking		1.5
design			2.5
documenting		2.5
coding			5
testing			3
managing		15
conference		125
networking		60
business lunch		150
business travel		200
customer liason		25
debugging		7.5
meetings		20
sales presentations	30
golf			115
client entertainment	180

(Table definition is a varchar field and a double.)

Users and roles

The users for the webapp and their permitted roles are defined by entries in the tom/conf/tomcat-users.xml file:

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="worker" description="humble employee"/>
  <role rolename="boss" description="overpaid "/>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <role rolename="manager"/>
  <role rolename="admin"/>
  <group groupname="workers" description="Happy employees at Acme"/>
  <user username="Susan" password="sue" fullName="Susan Songster" roles="worker"/>
  <user username="Martin" password="mm" fullName="Martin Maybe"/>
  <user username="Peter" password="retep" fullName="Peter Stone" roles="worker"/>
  <user username="Samuel" password="leumas" fullName="Sam Gamges" roles="worker,manager"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="David" password="dai" fullName="David Ventables" roles="worker"/>
  <user username="Claire" password="erialc" fullName="Claire Babbs" roles="worker,manager"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="Leila" password="aliel" fullName="Leila Khalin" roles="worker"/>
  <user username="Terry" password="MOI" fullName="Terrence Mulligan" roles="worker"/>
  <user username="Colin" password="Password" fullName="Colin The Boss" roles="worker,manager,boss"/>
  <user username="admin" password="Nqv8st" roles="admin,manager"/>
  <user username="Keith" password="k001" fullName="Keith Bond" roles="worker"/>
</tomcat-users>

It is simplest just to edit the tomcat-users.xml file. You can use the "Administrator" utility in Tomcat to define users, roles, and groups; but there doesn't appear to be a dialog in the current version that allows you to assign roles to users. (The role "manager" used in the example fortuitously grants Acme's managers the right to manage your Tomcat.)

Entries in the web.xml file identify the roles that are relevant to a web-app and the specific constraints that apply to servlets:

	<servlet>
		<servlet-name>
			HoursServlet 
        	</servlet-name>
		<servlet-class>
            		WorkerServlet
        	</servlet-class>
	</servlet>
	
	<servlet>
		<servlet-name>
			RatesServlet
        	</servlet-name>
		<servlet-class>
            		RateChangeServlet
        	</servlet-class>
	</servlet>
	
	more specifications
	...
	<servlet-mapping>
		<servlet-name>
			RatesServlet
		</servlet-name>
		<url-pattern>
			Rates
		</url-pattern>
	</servlet-mapping>
	
	this constraint identifies the "Hours" URL, which is mapped to the WorkerServlet
	as being accessible to all persons in role worker
	
	<security-constraint>
		<web-resource-collection>
			<web-resource-name>
                		AcmeCompany
			</web-resource-name>
			<url-pattern>
				/Hours
			</url-pattern>
			<http-method>
				GET
			</http-method>
			<http-method>
				POST
			</http-method>
 	        </web-resource-collection>
		<auth-constraint>
			<role-name>
				worker
			</role-name>
		</auth-constraint>
	</security-constraint>
	
	
	This one identifies that the /Rates url, mapped to the RateChangeServlet
	can only be used by those in boss role
	
	<security-constraint>
		<web-resource-collection>
			<web-resource-name>
                		AcmeCompany
			</web-resource-name>
			<url-pattern>
				/Rates
			</url-pattern>
			<http-method>
				GET
			</http-method>
			<http-method>
				POST
			</http-method>
 	        </web-resource-collection>
		<auth-constraint>
			<role-name>
				boss
			</role-name>
		</auth-constraint>
	</security-constraint>

	other stuff
	
	specification of roles relevant to this webapp
	
	<security-role>
		<role-name>
 			worker
		</role-name>
	</security-role>
	<security-role>
		<role-name>
 			manager
		</role-name>
	</security-role>
	<security-role>
		<role-name>
 			boss
		</role-name>
	</security-role>

Login

The typical mechanism for simple login checks done with Tomcat (or other servlet engine) is for the application programmer to define a login page (and an errors page that is shown if a password is invalid), and for the servlet engine to actually enforce the login mechanism.

A login page has a form with defined input fields and action:

<HTML>
<TITLE>Acme Record's Login</TITLE> 
<BODY>
<FORM METHOD="POST" ACTION="j_security_check"> 
<table align="center" border="2">
<caption>Enter your name and password</caption>
<tr>
	<th ALIGN="RIGHT"><b>Name</b></th>       
	<TD><INPUT TYPE="TEXT" NAME="j_username" VALUE="" SIZE="15"></TD>
</tr> 
<TR>
	<TH ALIGN="RIGHT"><B>Password:</B></th>
	<TD>
	<INPUT TYPE=PASSWORD NAME="j_password" VALUE="" SIZE=15>
	</TD>
</TR>                                                           
<TR>
	<TD COLSPAN=2 Align=CENTER>
	<INPUT TYPE=submit VALUE="  OK   ">   
	</TD>
</TR>
</TABLE>
</FORM>                                                              
</BODY>
</HTML>

This inputs from this form are handled by a built-in j_security_check part of the servlet engine.

The web.xml file has a login-config element that identifies the pages that the web-application author has supplied for handling the login data entry and error reporting:


	<login-config>
		<auth-method>
			FORM
	        </auth-method> 
		<form-login-config>
			<form-login-page>
				/loginpage.html
			</form-login-page>
			<form-error-page>
				/errorpage.html
			</form-error-page>
		</form-login-config>
	</login-config> 

For example, consider a request for http://localhost/combo/Rates; this is handled roughly as follows:

Don't try to go directly to the login page (http://localhost:8080/combo/loginpage.html) even if your browser offers you this as a short cut. It won't work that way. It will submit a request that asks to run a combo/j_security_check servlet, and there isn't any such beast so you get a page not found response.

Application data

These servlets all need to use the data from the "Rates" table that define job types and pay rates. These data don't often change. It could be costly to constantly go back to the disk and reload the data. So, a memory resident copy is used. (It is also used as a way of getting a simple illustration of shared application data.)

The RatesRecord class is the memory resident copy of the data. Its definition is:

import java.util.*;
import java.sql.*;

public class RatesRecord {
	private Hashtable	rates = new Hashtable();
	
	public Enumeration keys() { return rates.keys(); }

	public double	getRate(String task) {
		double rate = 0.0;
		try {
			Double data = (Double) rates.get(task);
			rate = data.doubleValue();
		}
		catch (Exception e) { }
		return rate;
	}


	public RatesRecord() {
		loadTable();
	}


	private void loadTable() {
		Connection db = DBInfo.connectToDatabase();
		if(db != null) {
		    try {
			Statement stmt = 
				db.createStatement ();						
			String request =
					"select * from rates";

			ResultSet rset = stmt.executeQuery(request);
			
			while(rset.next()) {
				String key = rset.getString("ACTIVITY");
				double val = rset.getDouble("RATE");
				Double dval = new Double(val);
				rates.put(key,dval);
			}
			stmt.close();
			db.close();
		    }
		    catch(Exception e) { }
		}


	}

}

When instantiated, it loads its data from the table (it grabs a temporary connection to the database to do this.) It holds the rates data in a hashtable, and provides access via a method that provides an Enumeration of all the different task types and a method that returns the rate for a particular task.

The webapp, i.e. the collection of three servlets, need a single copy of a RatesRecord. This is thus a simple instance of what servlets refer to as ServletContext data and JSPs refer to as application data. The ServletContext is a structure that holds information about a webapp. Like the Session object used earlier, a ServletContext can act as a kind of associative array holding data objects that are associated with an identifier key. Here we need a RatesRecord object, the key is RatesTable. A servlet can ask for the "RatesTable" object:


		ServletContext ctx = getServletContext();
		RatesRecord rates = (RatesRecord)
			ctx.getAttribute("RatesTable");
		if(rates==null) {
			rates = new RatesRecord();
			ctx.setAttribute("RatesTable", rates);
		}

The code picks up a reference to the context, asks the context object for the RatesTable. If there is none, then this is the first servlet to run; it had better create a RatesRecord (which automatically loads its own data) and place it in the ServletContext.

Typically, the RatesRecord object will be created by a user invoking http://localhost:8080/combo/Hours (the URL that invokes the servlet that records hours worked) or http://localhost:8080/combo/ShowRecord (the URL that invokes the servlet for viewing a person's work record). Once created, the RatesRecord remains in memory.

If the boss choose to run the RateChangeServlet, then this will update the database by changing an existing rate or adding a new task type and rate. This servlet uses the existing RatesRecord to determine whether it is adding new data or updating old data. Once it has done its task, it removes the RatesRecord from the shared ServletContext (application) area. A new copy with the updated information will be instantiated as soon as it is required.

Database connection

The DBInfo class represents a first step at moving details of the database out of the application.

import java.sql.*;

public class DBInfo {
	public static final String userName = "HSimpson";
	public static final String userPassword = "Duh";
/*
	public static final String dbDriverName = 
				"oracle.jdbc.driver.OracleDriver";
	public static final String dbURL = 
				"jdbc:oracle:thin:@wraith:1521:csci";
*/
	private static final String dbDriverName = 
				"sun.jdbc.odbc.JdbcOdbcDriver";
	private static final String dbURL = 
				"jdbc:odbc:Acme";
	public static final Connection connectToDatabase() {
		Connection dbConnection = null;
		try {
    			Class.forName (dbDriverName);
    			dbConnection = DriverManager.getConnection(
				dbURL,
				userName, userPassword);

		}
		catch(Exception e) {  }
		return dbConnection;
	}
}

The class is simply static code that provides the database connection. Different segments can be commented out allowing it to use an ODBC database or an Oracle database depending on where the application is to be deployed.

The JDBC library is evolving and code like this that loads drivers and uses the DriverManager to get a connection will eventually be deprecated. The preferred style is to use DataSources; these offer a more complete and effective method of separating deployment issues regarding databases from coding issues. An application simply has a symbolic name for the database that it is to use. At run time, it contacts some form of directory service to find out what database is mapped to this name. It gets back an object that was previously registered with the directory service. This object is a pooled connection to the appropriate database, any drivers will also have been loaded.

This is the JNDI mechanism to which you will see references in the Tomcat documentation. JNDI is simply Java's common interface that allows applications to use a variety of different directory services where details of databases (and other resources) may be stored.

The use of JNDI and DataSources was pioneered in the more sophisticated Enterprise Java Beans environment but it is now being adopted for servlets. The Administrator utility with Tomcat provides some mechanisms for setting up such datasources.

It isn't appropriate to use JNDI and datasources in your first servlet exercises. But you should look to these extensions if you plan more serious servlet develpments.

Customizing servlet behavior to suit roles

The behaviors of the WorkerServlet and ShowRecordServlet are adapted to the roles of their users. For example, the list of tasks that appear in the hours worked form that is generated by the doGet method of the WorkerServlet must be appropriate to the user. Ordinary programmers see tasks such as coding and documentation, managers see tasks like managing and customer relations, the boss sees tasks like travel, business lunches, and customer entertainment.

This is achieved by code that checks the role of the user. The showOptions routine is used to generate a set of option entries in a select input field of a generated form. It uses the role of the user to determine the appropriate options according to pay rate (as obtained from the shared "application" or "ServletContext" RatesRecord object)

	private void showOptions(HttpServletRequest req, PrintWriter out)
	{
		ServletContext ctx = getServletContext();
		RatesRecord rates = (RatesRecord)
			ctx.getAttribute("RatesTable");
		if(rates==null) {
			rates = new RatesRecord();
			ctx.setAttribute("RatesTable", rates);
		}
		double low = 0.0;
		double high = Double.MAX_VALUE;
		if(req.isUserInRole("boss")) {
			low = 25.0;
		}
		else
		if(req.isUserInRole("manager")) {
			low = 5.0;
			high = 30.0;
		}
		else high = 5;
		Enumeration activities = rates.keys();
		while(activities.hasMoreElements()) {
			String activity = (String) activities.nextElement();

			double dval =rates.getRate(activity);

			if((dval<low) || (dval>high)) continue;
			out.print("<option ");
			out.print("value=\"" + activity +"\">");
			out.println(activity);
		}


	}

Identifying the user

Sometimes it is necessary to know the identity of the user. In this example, this matters in the CheckRecords servlet. When used by a user with role manager, this prompts for an employees name. If the name entered is that of the manager then it proceeds to report on the manager's own work record, otherwise it must check that the name entered is that of an employee directly supervised by the manager.

The code in this case is:

	public void doPost (HttpServletRequest request,
                       HttpServletResponse response)
 		throws ServletException, IOException
 	{
		// Pick up name entered in form
		String person = request.getParameter("ENAME");
		if(person==null) {
			response.sendRedirect("/combo/BadData.html");
			return;
		}
		// Pick up name of user (getRemoteUser may be deprecated in future,
		// use alternative based on getUserPrincipal)
		String client = request.getRemoteUser();
		if(client.equals(person)) {
			generateReport(client,response);
			return;
			}
		if(!request.isUserInRole("manager")) {
			response.sendRedirect("/combo/NoAccess.html");
			return;
		}

		String mm = getManager(person);
		if(!client.equals(mm)) {
			response.sendRedirect("/combo/NoAccess.html");
			return;
		}
		generateReport(person,response);
	}

Distribution: war file etc

This is getting into the realm of a moderately serious webapplication with a variety of servlets, helper classes, and a sophisticated deployment that specifies a login authentication mechanism and authorization controls on sevlets. Everything can be neatly packaged up in a single deployable "war" resource (once again, a zipped war file that hopefully will be correctly downloaded by all browsers.). The war file is again not a deployable application; you must edit database details and recompile the code.