5. The tax calculation application
5.1. Introduction
Here we revisit the IMPOTS application, which is used repeatedly in the Java handout by the same author. Let’s review the problem. The goal is to write an application that calculates a taxpayer’s tax liability. We’ll consider the simplified case of a taxpayer who has only a single salary to report:
- we calculate the employee’s number of tax brackets as nbParts = nbEnfants / 2 + 1 if they are unmarried, and nbEnfants / 2 + 2 if they are married, where nbEnfants is the number of children.
- if they have at least three children, they receive an additional half-share
- We calculate their taxable income R = 0.72 * S, where S is their annual salary
- We calculate their family coefficient QF = R / nbParts
- we calculate his tax I. Consider the following table:
12620.0 | 0 | 0 |
13,190 | 0.05 | 631 |
15,640 | 0.1 | 1,290.5 |
24,740 | 0.15 | 2,072.5 |
31,810 | 0.2 | 3,309.5 |
39,970 | 0.25 | 4,900 |
48,360 | 0.3 | 6,898.5 |
55,790 | 0.35 | 9,316.5 |
92,970 | 0.4 | 12,106 |
127,860 | 0.45 | 16,754.5 |
151,250 | 0.50 | 23,147.5 |
172,040 | 0.55 | 30,710 |
195,000 | 0.60 | 39312 |
0 | 0.65 | 49062 |
Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 23,000, the row found will be
Tax I is then equal to 0.15*R - 2072.5*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:
which gives tax I = 0.65*R - 49062*nbParts.
The data defining the different tax brackets is stored in an ODBC-MySQL database. MySQL is a public-domain DBMS that can be used on various platforms, including Windows and Linux. Using this DBMS, a database named dbimpots was created, containing a single table called impots. Access to the database is controlled by a username/password, in this case admimpots/mdpimpots. The following screenshot shows how to use the dbimpots database with MySQL:
C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql> use dbimpots;
Database changed
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| taxes |
+--------------------+
1 row in set (0.00 sec)
mysql> describe taxes;
+---------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limits | double | YES | | NULL | |
| coeffR | double | YES | | NULL | |
| coeffN | double | YES | | NULL | |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
mysql> select * from taxes;
+---------+--------+---------+
| limits | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13,190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24,740 | 0.15 | 2,072.5 |
| 31,810 | 0.2 | 3,309.5 |
| 39,970 | 0.25 | 4,900 |
| 48,360 | 0.3 | 6,898 |
| 55,790 | 0.35 | 9,316.5 |
| 92,970 | 0.4 | 12,106 |
| 127,860 | 0.45 | 16,754 |
| 151,250 | 0.5 | 23,147.5 |
| 172,040 | 0.55 | 30,710 |
| 195,000 | 0.6 | 39,312 |
| 0 | 0.65 | 49,062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
mysql>quit
The dbimpots database is converted into an ODBC data source as follows:
- Launch the 32-bit ODBC Data Source Administrator

- Use the [Add] button to add a new ODBC data source

- Select the MySQL driver and click [Finish]
54321

- The MySQL driver requests a certain amount of information:
1 | the DSN name to be given to the ODBC data source—it can be anything |
2 | the machine on which the MySQL DBMS is running—here, localhost. It is worth noting that the database could be a remote database. Local applications using the ODBC data source would not be aware of this. This would be the case, in particular, for our Java application. |
3 | the MySQL database to use. MySQL is a DBMS that manages relational databases, which are sets of tables linked together by relationships. Here, we specify the name of the database being managed. |
4 | The name of a user with access rights to this database |
5 | their password |
Two classes have been defined to calculate the tax: impots and impotsJDBC. An instance of the impots class is constructed using the tax bracket data passed as parameters in arrays:
// creation of an impots class
public class impots{
// the data needed to calculate the tax
// comes from an external source
protected double[] limits = null;
protected double[] taxRates = null;
protected double[] coeffN = null;
// empty constructor
protected impots(){}
// constructor
public impots(double[] LIMITS, double[] COEFFR, double[] COEFFN) throws Exception{
// Check that the three arrays are the same size
boolean OK = LIMITS.length == COEFFR.length && LIMITS.length == COEFFN.length;
if (!OK) throw new Exception("The three arrays provided do not have the same size(" +
LIMITES.length + "," + COEFFR.length + "," + COEFFN.length + ""));
// All good
this.limits = LIMITS;
this.coeffR = COEFFR;
this.coeffN = COEFFN;
}//constructor
// tax calculation
public long calculate(boolean married, int numChildren, int salary){
// calculate the number of shares
double nbParts;
if (married) nbParts = (double)nbChildren / 2 + 2;
else nbParts = (double)nbChildren / 2 + 1;
if (numberOfChildren >= 3) numberOfShares += 0.5;
// Calculate taxable income & family quotient
double income = 0.72 * salary;
double QF = income / nbParts;
// calculate tax
limits[limits.length-1] = QF + 1;
int i = 0;
while(QF > limits[i]) i++;
// return result
return (long)(income * coeffR[i] - nbParts * coeffN[i]);
}//calculate
}//class
The impotsJDBC class derives from the previous impots class. An instance of the impotsJDBC class is constructed using tax bracket data stored in a database. The information needed to access this database is passed as parameters to the constructor:
// imported packages
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// Add a constructor to create an instance
// the limit tables, coeffr, and coeffn from the
// Taxes from a database
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException, ClassNotFoundException {
// dsnIMPOTS: DSN name of the database
// userIMPOTS, mdpIMPOTS: login/password for database access
// data arrays
ArrayList aLimits = new ArrayList();
ArrayList aCoeffR = new ArrayList();
ArrayList aCoeffN = new ArrayList();
// database connection
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect = DriverManager.getConnection("jdbc:odbc:" + dsnIMPOTS, userIMPOTS, mdpIMPOTS);
// Create a Statement object
Statement S = connect.createStatement();
// SELECT query
String select = "select limites, coeffr, coeffn from impots";
// execute the query
ResultSet RS = S.executeQuery(select);
while(RS.next()){
// process the current row
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// next line
// close resources
RS.close();
S.close();
connect.close();
// transfer data to bounded arrays
int n = aLimits.size();
limits = new double[n];
coeffR = new double[n];
coeffN = new double[n];
for (int i = 0; i < n; i++) {
limits[i] = Double.parseDouble((String)aLimits.get(i));
coeffR[i] = Double.parseDouble((String)aCoeffR.get(i));
coeffN[i] = Double.parseDouble((String)aCoeffN.get(i));
}//for
}//constructor
}//class
Once an instance of the impotsJDBC class has been constructed, its calculate method can be called repeatedly to calculate the tax:
The three required pieces of data can be obtained in multiple ways. The advantage of the impotsJDBC class is that we only need to worry about obtaining this data. Once the three pieces of information (marital status, number of children, annual salary) have been obtained, calling the calculer method of the impotsJDBC class gives us the tax amount due.
5.2. Version 1
We are in the context of a web application that would present an HTML interface to a user in order to obtain the three parameters necessary for calculating the tax:
- marital status (married or not)
- number of children
- annual income

The form is displayed by the following JSP page:
<%@ page import="java.util.*" %>
<%
// retrieve the attributes passed by the main servlet
String chkoui = (String) request.getAttribute("chkoui");
String chknon = (String) request.getAttribute("chknon");
String txtEnfants = (String) request.getAttribute("txtEnfants");
String txtSalary = (String) request.getAttribute("txtSalary");
String txtTaxes = (String) request.getAttribute("txtTaxes");
ArrayList errors = (ArrayList) request.getAttribute("errors");
%>
<html>
<head>
<title>taxes</title>
<script language="JavaScript" type="text/javascript">
function clear(){
// clear the form
with(document.frmImpots){
optMarie[0].checked = false;
optMarie[1].checked = true;
txtChildren.value="";
txtSalary.value="";
txtTaxes.value="";
}//with
}//clear
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Tax Calculation
<hr>
<form name="frmImpots" action="/impots/main" method="POST">
<table>
<tr>
<td>Are you married?</td>
<td>
<input type="radio" name="optMarie" value="yes" <%= chkoui %>>yes
<input type="radio" name="optMarie" value="no" <%= chknon %>>no
</td>
</tr>
<tr>
<td>Number of children</td>
<td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
</tr>
<tr>
<td>Annual salary</td>
<td><input type="text" size="10" name="txtSalary" value="<%= txtSalary %>"></td>
</tr>
<tr>
<td><font color="green">Tax</font></td>
<td><input type="text" size="10" name="txtTax" value="<%= txtTax %>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="submit" value="Calculate"></td>
<td><input type="button" value="Clear" onclick="clear()"></td>
</tr>
</table>
</form>
</center>
<%
// Are there any errors
if(errors!=null){
// display errors
out.println("<hr>");
out.println("<font color=\"red\">");
out.println("The following errors occurred<br>");
out.println("<ul>");
for(int i=0;i<errors.size();i++){
out.println("<li>" + (String)errors.get(i));
}
out.println("</ul>");
out.println("</font>");
}
%>
</body>
</html>
The JSP page simply displays information passed to it by the application's main servlet:
// retrieve the attributes passed by the main servlet
String chkoui = (String) request.getAttribute("chkoui");
String chknon = (String) request.getAttribute("chknon");
String txtEnfants = (String) request.getAttribute("txtEnfants");
String txtSalary = (String) request.getAttribute("txtSalary");
String txtTaxes = (String) request.getAttribute("txtTaxes");
ArrayList errors = (ArrayList) request.getAttribute("errors");
The "checked" and "unchecked" attributes of radio buttons can have the values "checked" or "unchecked" to indicate whether the corresponding radio button is selected or not | |
the taxpayer's number of children | |
their annual salary | |
the amount of tax due | |
a list of errors, if any, if errors!=null |
The page sent to the client contains a JavaScript script with a clear function associated with the "Clear" button, whose purpose is to reset the form to its initial state: unchecked button, empty input fields. Note that this result could not be achieved with an HTML "reset" button. Indeed, when this type of button is used, the browser resets the form to the state in which it received it. However, in our application, the browser receives forms that may not be empty.
The application’s main servlet is called main.java, and its code is as follows:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class Main extends HttpServlet {
// instance variables
String errorMessage = null;
String taxDisplayURL = null;
String errorURL = null;
String taxDSN = null;
String taxAdmin = null;
String taxPassword = null;
JDBCTaxes tax = null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
// Did initialization succeed?
if(errorMessage != null){
// Redirect to the error page
request.setAttribute("errorMessage", errorMessage);
getServletContext().getRequestDispatcher(errorURL).forward(request, response);
}
// Request attributes
String chkoui = null;
String chknon = null;
String txtTaxes = null;
// retrieve the request parameters
String optMarie = request.getParameter("optMarie"); // marital status
String txtChildren = request.getParameter("txtChildren"); // number of children
if(txtChildren==null) txtChildren="";
String txtSalary = request.getParameter("txtSalary"); // annual salary
if(txtSalary==null) txtSalary="";
// Do we have all the expected parameters?
if(optSpouse == null || txtChildren == null || txtSalary == null){
// parameters are missing
request.setAttribute("chkoui", "");
request.setAttribute("chknon", "checked");
request.setAttribute("txtEnfants", "");
request.setAttribute("txtSalary", "");
request.setAttribute("txtImpots", "");
// forward to the tax display URL
getServletContext().getRequestDispatcher(taxDisplayURL).forward(request, response);
}
// we have all the parameters—let's check them
ArrayList errors = new ArrayList();
// marital status
if( ! optMarriage.equals("yes") && ! optMarriage.equals("no")){
// error
errors.add("Invalid marital status");
maritalStatus = "no";
}
// number of children
txtChildren = txtChildren.trim();
if(! Pattern.matches("^\\d+$",txtChildren)){
// error
errors.add("Incorrect number of children");
}
// salary
txtSalary = txtSalary.trim();
if(! Pattern.matches("^\\d+$",txtSalary)){
// error
errors.add("Incorrect salary");
}
// if there are errors, pass them as query parameters
if (errors.size() != 0) {
request.setAttribute("errors", errors);
txtTaxes="";
}else{
// we can calculate the tax due
try{
int numberOfChildren = Integer.parseInt(txtChildren);
int salary = Integer.parseInt(txtSalary);
txtTaxes = "" + taxes.calculate(optMarried.equals("yes"), nbChildren, salary);
} catch (Exception ex) {}
}
// other query attributes
if(optMarie.equals("yes")){
request.setAttribute("chkoui", "checked");
request.setAttribute("chkno", "");
}else{
request.setAttribute("chknon", "checked");
request.setAttribute("chkoui", "");
}
request.setAttribute("txtChildren", txtChildren);
request.setAttribute("txtSalary", txtSalary);
request.setAttribute("txtTaxes",txtTaxes);
// forward to the tax display URL
getServletContext().getRequestDispatcher(taxDisplayURL).forward(request, response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request, response);
}
//-------- INIT
public void init(){
// retrieve the initialization parameters
ServletConfig config = getServletConfig();
taxDisplayUrl = config.getInitParameter("taxDisplayUrl");
errorUrl = config.getInitParameter("errorUrl");
DSNTaxes = config.getInitParameter("DSNTaxes");
taxAdmin = config.getInitParameter("taxAdmin");
mdpimpots = config.getInitParameter("mdpimpots");
// Are the parameters OK?
if(taxDisplayUrl == null || taxDSN == null || taxAdmin == null || taxPassword == null){
errorMessage="Incorrect configuration";
return;
}
// Create an instance of impotsJDBC
try{
taxes = new taxesJDBC(taxDSN, taxadmin, taxmdp);
} catch (Exception ex) {
errorMessage = ex.getMessage();
}
}
}
- The servlet's init method does two things:
- It retrieves its initialization parameters. These allow it to connect to the ODBC database containing the data for the various tax brackets (DSNimpots, admimpots, mdpimpots) and the URLs of the pages associated with the application: urlAffichageImpots for the form, urlErreur for the error page.
- it creates an instance of the impotsJDBC class
In both cases, potential errors are handled, and the error message is stored in the msgErreur variable.
- The doGET method
- first checks that the servlet has initialized correctly. If this is not the case, the error page is displayed
- retrieves the expected parameters from the tax form: optMarie, txtEnfants, txtSalaire. If any of them is missing (==null), an empty tax form is submitted. One might think that validating the optMarie parameter is unnecessary. This parameter is the value of a radio button and can only take one of the values "yes" or "no" here. However, this overlooks the fact that nothing prevents a program from directly querying the servlet by sending it the parameters it wants. You can never be certain that you are actually connected to a browser. Overlooking this point can lead to security vulnerabilities in the application, and it is, in fact, common to find such issues even in commercial applications.
- The validity of each of the three retrieved parameters is checked. Any errors found are added to an error list (ArrayList errors). If there are no errors, the tax amount is calculated; otherwise, it is not.
- The information needed to display the page is set as request attributes, and the tax form is then displayed
The error JSP page is as follows:
<%
// jspService
// An error has occurred
String errorMessage = (String)request.getAttribute("errorMessage");
if(errorMessage==null) errorMessage="Unidentified error";
%>
<!-- start of HTML page -->
<html>
<head>
<title>taxes</title>
</head>
<body>
<h3>tax calculation</h3>
<hr>
Application unavailable(<%= msgError %>)
</body>
</html>
The web application is called impots and is configured in Tomcat's server.xml file as follows:
The application directory contains the following directories and files:




The web.xml configuration file for the impots application is as follows:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>main</servlet-name>
<servlet-class>main</servlet-class>
<init-param>
<param-name>taxDisplayUrl</param-name>
<param-value>/taxes.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNtaxes</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>urlError</param-name>
<param-value>/error.jsp</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>main</servlet-name>
<url-pattern>/main</url-pattern>
</servlet-mapping>
</web-app>
The main servlet is named main and has the alias /main. It is therefore accessible via the URL http://localhost:8080/impots/main.
Here are some application examples:
To initialize correctly, the servlet must have access to the mysql-dbimpots database. Here is the page displayed if, for example, the MySQL server is not running and the mysql-dbimpots database is therefore inaccessible:

The page displayed in case of incorrect input is as follows:

If the entries are correct, the tax is calculated:

5.3. Version 2
In the previous example, the server validates the txtEnfants and txtSalaire parameters in the form. Here, we propose validating them using a JavaScript script included in the form page. The browser then performs the parameter validation. The server is only contacted if the parameters are valid. This saves "bandwidth." The JSP display page becomes the following:
<%@ page import="java.util.*" %>
............
<html>
<head>
<title>taxes</title>
<script language="JavaScript" type="text/javascript">
function clear(){
.......
}//clear
function calculate(){
// Check parameters before sending them to the server
with(document.frmImpots){
//number of children
fields = /^\s*(\d+)\s*$/ .exec(txtChildren.value);
if(champs==null){
// the form is not validated
alert("The number of children was not provided or is incorrect");
nbEnfants.focus();
return;
}//if
//salary
fields = /^\s*(\d+)\s*$/ .exec(txtSalary.value);
if(fields==null){
// the pattern does not match
alert("Salary was not provided or is incorrect");
salary.focus();
return;
}//if
// OK—send the form to the server
submit();
}//with
}//calculate
</script>
</head>
<body background="/impots/images/standard.jpg">
........
<td><input type="button" value="Calculate" onclick="calculate()"></td>
<td><input type="button" value="Clear" onclick="clear()"></td>
.....
</body>
</html>
Note the following changes:
- The "Calculate" button is no longer a submit button but a regular button associated with a function called "calculate." This function will validate the "txtEnfants" and "txtSalaire" fields. If they are valid, the form data will be submitted to the server; otherwise, an error message will be displayed.
Here is an example of what is displayed in case of an error:

5.4. Version 3
We are making a slight modification to the application to introduce the concept of a session. We now consider the application to be a tax calculation simulation tool. A user can then simulate different taxpayer "scenarios" and see what the tax liability would be for each one. The web page below provides an example of what could be achieved:

The main servlet has been modified and is now called simulations. It is configured as follows within the impots application:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
....
</servlet>
<servlet>
<servlet-name>simulations</servlet-name>
<servlet-class>simulations</servlet-class>
<init-param>
<param-name>urlSimulationImpots</param-name>
<param-value>/taxSimulations.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNimpots</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>error-url</param-name>
<param-value>/error.jsp</param-value>
</init-param>
</servlet>
......
<servlet-mapping>
<servlet-name>simulations</servlet-name>
<url-pattern>/simulations</url-pattern>
</servlet-mapping>
</web-app>
The main servlet is called simulations and is based on the simulations.class file. It has the alias /simulations, which makes it accessible via the URL http://localhost:8080/impots/simulations. It has the same initialization parameters as the main servlet discussed earlier regarding database access. A new parameter appears, **urlSimulationsImpots,** which is the URL of the simulation JSP page (the one just presented above).
The simulations.java servlet is similar to the main.java servlet. It differs from it in the following main ways:
- The main servlet calculates a value for `txtImpots` based on the parameters `optmarie`, `txtEnfants`, and `txtSalaire`, and passes this value to the display JSP page
- The simulations servlet calculates the txtImpots value in the same way and stores the parameters (optMarie, txtEnfants, txtsalaire, txtImpots) in a list called simulations. This list is passed as a parameter to the display JSP page. To ensure that this list contains all simulations performed by the user, it is stored as an attribute of the current session.
The simulations servlet is as follows (only the lines of code that differ from those in the previous application have been included):
import java.io.*;
.......
public class simulations extends HttpServlet{
// instance variables
String errorMessage = null;
String taxSimulationURL = null;
String errorUrl = null;
...........
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
...........
// retrieve previous simulations from the session
HttpSession session = request.getSession();
ArrayList simulations = (ArrayList) session.getAttribute("simulations");
if (simulations == null) simulations = new ArrayList();
// add the simulations to the current request
request.setAttribute("simulations", simulations);
// other request attributes
...........
// Do we have all the expected parameters?
if(optMarie == null || txtEnfants == null || txtSalaire == null){
........
// we pass control to the URL for displaying tax calculation simulations
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request, response);
}
// we have all the parameters—we check them
...........
// if there are errors, we pass them as request attributes
if(errors.size()!=0){
request.setAttribute("errors", errors);
}else{
try{
// we can calculate the tax due
int numberOfChildren = Integer.parseInt(txtChildren);
int salary = Integer.parseInt(txtSalary);
txtTaxes = "" + Taxes.calculate(optMarriage.equals("yes"), nbChildren, salary);
// add the current result to the previous simulations
String[] simulation = {optMarie.equals("yes") ? "yes" : "no", txtChildren, txtSalary, txtTaxes};
simulations.add(simulation);
// The new value of simulations is stored in the session
session.setAttribute("simulations", simulations);
} catch (Exception ex) {}
}
// other request attributes
..........
// forward to the URL for displaying simulations
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request, response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request, response);
}
//-------- INIT
public void init(){
// retrieve the initialization parameters
ServletConfig config = getServletConfig();
taxSimulationUrl = config.getInitParameter("taxSimulationUrl");
errorUrl = config.getInitParameter("errorUrl");
DSNimpots = config.getInitParameter("DSNimpots");
admimpots = config.getInitParameter("admimpots");
mdpimpots = config.getInitParameter("mdpimpots");
// Parameters OK?
.........................
}
}
The display JSP page simulationsImpots.jsp now looks like this (only the code that differs from the display JSP page of the previous application has been retained).
<%@ page import="java.util.*" %>
<%
// retrieve the attributes passed by the main servlet
...........
ArrayList simulations = (ArrayList) request.getAttribute("simulations");
%>
<html>
<head>
<title>taxes</title>
<script language="JavaScript" type="text/javascript">
........
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Tax Calculator
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
.......................
</form>
</center>
<hr>
<%
// Are there any errors?
if(errors!=null){
..................
}else if(simulations.size()!=0){
// Simulation results
out.println("<h3>Simulation results<h3>");
out.println("<table \"border=\"1\">");
out.println("<tr><td>Married</td><td>Children</td><td>Annual salary (F)</td><td>Taxes payable (F)</td></tr>");
for(int i=0;i<simulations.size();i++){
String[] simulation = (String[])simulations.get(i);
out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
}
out.println("</table>");
}
%>
</body>
</html>
5.5. Version 4
We will now create a standalone application that will serve as a web client for the previous /impots/simulations application. This application will have the following graphical interface:
![]() |
No. | type | name | role |
1 | JTextField | txtTaxServiceUrl | URL of the tax calculation simulation service |
2 | JRadioButton | rdYes | Checked if married |
3 | JRadioButton | rdNo | Checked if unmarried |
4 | JSpinner | spinChildren | number of the taxpayer's children (minimum=0, maximum=20, increment=1) |
5 | JTextField | txtSalary | taxpayer's annual salary in F |
6 | JList in JScrollPane | lstSimulations | list of simulations |
The Tax menu consists of the following options:
main main | option secondary | name | role |
Taxes | |||
Calculate | mnuCalculate | Calculates the tax due when all data required for the calculation is present and correct | |
Clear | mnuClear | resets the form to its initial state | |
Exit | mnuExit | closes the application |
Operating Rules
- The Calculate menu option remains disabled if either field 1 or 5 is empty
- A syntactically incorrect URL is detected in field 1

- an incorrect salary is detected

- any server connection error is reported (in example 1 below, the port is incorrect; in example 2, the requested URL does not exist; in example 3, the MySQL database was not running)



- If everything is correct, the simulations are displayed

When writing a programmed web client, it is necessary to know exactly what the server sends in response to the various possible requests from a client. The server sends a set of HTML lines containing useful information and other elements that are there solely for HTML layout. Java regular expressions can help us find the useful information within the stream of lines sent by the server. To do this, we need to know the exact format of the server’s various responses. Here we will use the web client we encountered earlier, which allows us to display a server’s response to a URL request on the screen. The requested URL will be that of our tax calculation simulation service, http://localhost:8080/impots/simulations, to which we can pass parameters in the form http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&...
Let’s request the URL while the MySQL database used to create a tax-type object is not running:
Dos>java clientweb http://localhost:8080/impots/simulations GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
<!-- Start of HTML page -->
<html>
<head>
<title>taxes</title>
</head>
<body>
<h3>tax calculation</h3>
<hr>
Application unavailable([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
</body>
</html>
To retrieve the error, the web client must search the web server's response for the line containing the text "Application unavailable". Now let's start the MySQL database and request the same URL, passing it the values it expects (it accepts them equally well as a GET or a POST request—see the server's Java code):
Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
<html>
<head>
<title>taxes</title>
<script language="JavaScript" type="text/javascript">
function clear(){
...................................................
}//clear
function calculate(){
...................................................
}//calculate
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Tax Calculation
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
</form>
</center>
<hr>
<h3>Simulation Results<h3>
<table "border="1">
<tr><td>Married</td><td>Children</td><td>Annual income (F)</td><td>Taxes due (F)</td></tr>
<tr><td>yes</td><td>2</td><td>200,000</td><td>22,504</td></tr>
</table>
</body>
</html>
Here is the entire HTML document sent by the server. The results of the various simulations are found in the single table contained in this document. The regular expression allowing us to extract the relevant information from the document could be the following:
where the four expressions in parentheses represent the four pieces of information to be extracted.
We now have the guidelines for what to do when the user requests a tax calculation from the previous graphical interface:
- verify that all data in the interface is valid and report any errors.
- connect to the URL specified in field 1. To do this, we will follow the model of the generic web client already presented and studied
- in the server response stream, use regular expressions to either:
- find the error message, if there is one
- find the simulation results if there are no errors
The code related to the "Calculate" menu is as follows:
void mnuCalculate_actionPerformed(ActionEvent e) {
// tax calculation
// check service URL
URL urlImpots = null;
try{
urlTaxes = new URL(txtTaxServiceURL.getText().trim());
String query = urlImpots.getQuery();
if (query != null) throw new Exception();
} catch (Exception ex) {
// Error message
JOptionPane.showMessageDialog(this, "Invalid URL. Please try again", "Error", JOptionPane.ERROR_MESSAGE);
// focus on the incorrect field
txtURLServiceImpots.requestFocus();
// return to interface
return;
}
// check salary
int salary = 0;
try{
salary = Integer.parseInt(txtSalary.getText().trim());
if (salary < 0) throw new Exception();
} catch (Exception ex) {
// error message
JOptionPane.showMessageDialog(this, "Invalid salary. Please try again", "Error", JOptionPane.ERROR_MESSAGE);
// focus on the incorrect field
txtSalary.requestFocus();
// return to the interface
return;
}
// number of children
Integer numChildren = (Integer)spinChildren.getValue();
try{
// calculate taxes
calculateTaxes(taxURL, rdYes.isSelected(), nbChildren.intValue(), salary);
} catch (Exception ex) {
// display the error
JOptionPane.showMessageDialog(this, "The following error occurred: " + ex.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
}
}//mnuCalculate
public void calculateTaxes(URL taxURL, boolean married, int numberOfChildren, int salary)
throws Exception{
// tax calculation
// taxURL: URL of the tax service
// married: true if married, false otherwise
// nbEnfants: number of children
// salary: annual salary
// retrieve the information needed to connect to the tax server from urlImpots
String path = urlImpots.getPath();
if(path.equals("")) path="/";
String query = "?" + "optMarie=" + (married ? "yes" : "no") + "&txtEnfants=" + nbEnfants + "&txtSalaire=" + salary;
String host = urlImpots.getHost();
int port = urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
// local data
Socket client = null; // the client
BufferedReader IN = null; // client read stream
PrintWriter OUT = null; // the client's write stream
String response = null; // Server response
// The pattern to look for in the HTTP headers
Pattern cookiePattern = Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the pattern for a valid response
Pattern OKResponse = Pattern.compile("^.*? 200 OK");
// result of the pattern match
Matcher result = null;
try{
// Connect to the server
client = new Socket(host, port);
// create the TCP client's input/output streams
IN = new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT = new PrintWriter(client.getOutputStream(), true);
// Request the URL - send HTTP headers
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
// read the first line of the response
response = IN.readLine();
// compare the HTTP line to the correct response template
result = responseOK.matcher(response);
if(!result.find()){
// there is a URL issue
throw new Exception("The server responded: Unknown URL [" + txtURLServiceImpots.getText().trim() + "]");
}//if(result)
// read the response until the end of the headers, looking for any cookies
while((response = IN.readLine()) != null) {
// empty line?
if(response.equals("")) break;
// non-empty HTTP line
// if we don't have the session token, look for it
if (JSESSIONID.equals("")){
// Compare the HTTP line to the cookie pattern
result = cookieTemplate.matcher(response);
if(result.find()){
// the token cookie was found
JSESSIONID = result.group(1);
}//if(result)
}//if(JSESSIONID)
}//while
// That's it for the HTTP headers—now we move on to the HTML code
// to retrieve the simulations
ArrayList simulationList = getSimulations(IN, OUT, simulations);
simulations.clear();
for (int i = 0; i < simulationsList.size(); i++) {
simulations.addElement(simulationsList.get(i));
}
// Done
client.close();
} catch (Exception ex) {
throw new Exception(ex.getMessage());
}
}//calculateTaxes
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// the pattern for a row in the simulation table
Pattern ptnSimulation = Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// the pattern for a line in the error list
Pattern ptnError = Pattern.compile("(Application unavailable.*?)\\s*$");
// the result of the pattern match
Matcher result = null;
// the simulations
ArrayList simulationList = new ArrayList();
// read all lines until the end
String line = null;
boolean simulationSuccessful = false;
while ((line = IN.readLine()) != null) {
// monitoring
// compare the line to the error pattern if the simulation section has not yet been encountered
if(! simulationSuccessful){
result = ptnError.matcher(line);
if (result.find()) {
// error message
JOptionPane.showMessageDialog(this, result.group(1), "Error", JOptionPane.ERROR_MESSAGE);
// Done
return simulationList;
}//if
}//if
// compare the line to the simulation model
result = ptnSimulation.matcher(line);
if(result.find()){
// a row from the table was found
simulationList.add(result.group(1)+":"+result.group(2)+":"+result.group(3)+
":"+result.group(4));
// the simulation was successful
simulationSuccessful = true;
}//if
}//while
// end
return simulationsList;
}
Let's explain this code a bit:
- The mnuCalculer_actionPerformed procedure checks that the interface data is valid. If it is not, an error message is displayed and the procedure terminates. If it is valid, the calculerImpots procedure is executed.
- The calculerImpots procedure begins by constructing the URL it needs to request
// We retrieve the information needed to connect to the tax server from urlImpots
String path = urlImpots.getPath();
if(path.equals("")) path="/";
String query = "?" + "optMarie=" + (married ? "yes" : "no") + "&txtEnfants=" + nbEnfants + "&txtSalaire=" + salary;
String host = urlImpots.getHost();
int port = urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
........................
- then connects to this URL by sending the appropriate HTTP headers:
// connect to the server
client = new Socket(host, port);
// create the TCP client's input/output streams
IN = new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT = new PrintWriter(client.getOutputStream(), true);
// Request the URL - send HTTP headers
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
- Once the request is made, our web client waits for the response. It first receives the HTTP headers from the server’s response. It parses these headers to find the session token. Indeed, it must send this token back to the server so that the server can keep track of the various simulations performed. Note here that it would have been simpler for the client to store the various simulations performed by the user itself. Nevertheless, we are sticking with the idea of sending back the token to provide a new example of session management. The first line of the response is handled separately. It must be in the form HTTP/version 200 OK to indicate that the requested URL does indeed exist. If it is not in this form, we conclude that the user requested an incorrect URL and inform them accordingly.
// the pattern searched for in the HTTP headers
Pattern cookiePattern = Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the pattern for a correct response
Pattern OKResponse = Pattern.compile("^.*? 200 OK");
..........
// read the first line of the response
response = IN.readLine();
// compare the HTTP line to the correct response pattern
result = responseOK.matcher(response);
if(!result.find()){
// there is a URL issue
throw new Exception("The server responded: Unknown URL [" + txtURLServiceImpots.getText().trim() + "]");
}//if(result)
// read the response until the end of the headers, looking for any cookies
while((response = IN.readLine()) != null) {
// empty line?
if(response.equals("")) break;
// non-empty HTTP line
// if we don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line to the cookie pattern
result = cookieTemplate.matcher(response);
if(result.find()){
// We found the token cookie
JSESSIONID=result.group(1);
}//if(result)
}//if(JSESSIONID)
}//while
- Once the HTTP headers have been processed, we move on to the HTML part of the response
// That's it for the HTTP headers—we move on to the HTML code
// to retrieve the simulations
ArrayList simulationList = getSimulations(IN, OUT, simulations);
simulations.clear();
for (int i = 0; i < simulationsList.size(); i++) {
simulations.addElement(simulationsList.get(i));
}
- The getSimulations procedure returns the list of simulations if they exist; this list will be empty if the server returns an error message. In this case, the error message is displayed in a message box. If the list is not empty, it is displayed in the drop-down list of the graphical user interface.
- The getSimulations procedure compares each line of the HTML response to the regular expression representing the error message (Application unavailable...) and to the regular expression representing a simulation. If the error message is found, it is displayed and the procedure terminates. If the result of a simulation is found, it is added to the list of simulations. At the end of the procedure, this list is returned as the result.
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// the pattern for a line in the simulation array
Pattern ptnSimulation = Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// the pattern for a line in the error list
Pattern ptnError = Pattern.compile("(Application unavailable.*?)\\s*$");
5.6. Version 5
Here, we transform the previous standalone graphical application into a Java applet. The graphical interface is slightly different. With the standalone application, the user provided the URL of the tax calculation simulation service themselves, and the application then connected to that URL. Here, the client application is a browser, and the user will request the URL of the HTML document containing the applet. It is important to remember that a Java applet can only establish a network connection with the server from which it was downloaded. The URL of the simulation service will therefore be on the same server as the HTML document containing the applet. In our example, this will be an applet initialization parameter placed in the txtUrlServiceImpots field, which will be non-editable by the user. This results in the following client:

The HTML document containing the applet is called simulations.htm and is as follows:
<html>
<head>
<title>Tax Calculation Simulations</title>
</head>
<body background="/impots/images/standard.jpg">
<center>
<h3>Tax Calculation Simulations</h3>
<hr>
<applet code="appletImpots.class" width="400" height="360">
<param name="urlServiceImpots" value="simulations">
</applet>
</center>
</body>
</html>
The applet has a parameter called *urlServiceImpots, which is the URL of the tax calculation simulation service. This URL is relative to the URL of the HTML document simulations.htm. Thus, if a browser retrieves this document via the URL http://localhost:8080/impots/simulations.htm, the URL of the simulation service will be http://localhost:8080/impots/simulations. If this URL had been http://stahe:8080/impots/simulations.htm, the URL of the simulation service would have been http://stahe:8080/impots/simulations*.
The applet appletImpots.java fully incorporates the code from the previous standalone graphical application while adhering to the rules for converting a graphical application into an applet.
public class appletImpots extends JApplet {
// window components
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculate = new JMenuItem();
.............
//Create the frame
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// other initializations
moreInit();
}
// form initialization
private void moreInit(){
// retrieve the urlServiceImpots parameter
String urlServiceImpots = getParameter("urlServiceImpots");
if(urlServiceImpots==null){
// missing parameter
JOptionPane.showMessageDialog(this, "The urlServiceImpots parameter of the applet has not been defined", "Error", JOptionPane.ERROR_MESSAGE);
// end
return;
}
// enter the URL in its field
String codeBase=""+getCodeBase();
if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase + urlServiceImpots);
else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
// Disable the Calculate menu
mnuCalculate.setEnabled(false);
// Children spinner - between 0 and 20 children
spinEnfants = new JSpinner(new SpinnerNumberModel(0, 0, 20, 1));
spinChildren.setBounds(new Rectangle(130, 140, 50, 27));
contentPane.add(spinChildren);
}//moreInit
//Initialize the component
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
...............
}
When a browser loads an applet, it first executes its init method. In this method, we retrieved the value of the applet’s urlServiceImpots parameter, calculated the full URL of the simulation service, and placed this value in the txtURLServiceImpots field as if the user had typed it in. Once this is done, there is no longer any difference between the two applications. In particular, the code associated with the Calculate menu is identical. Here is an example of execution:

5.7. Conclusion
We have demonstrated different versions of our client-server tax calculation application:
- Version 1: The service is provided by a set of servlets and JSP pages; the client is a browser. It performs a single simulation and does not retain any history of previous ones.
- Version 2: We add some browser-side functionality by embedding JavaScript scripts in the HTML document loaded by the browser. It validates the form parameters.
- Version 3: We enable the service to remember the various simulations performed by a client by managing a session. The HTML interface is modified accordingly to display these simulations.
- Version 4: The client is now a standalone graphical application. This allows us to revisit the development of programmed web clients.
- Version 5: The client becomes a Java applet. We now have a client-server application entirely programmed in Java, both on the server side and the client side.
At this point, a few observations can be made:
- Versions 1 through 3 support browsers with no capabilities other than the ability to execute JavaScript scripts. Note that a user always has the option to disable the execution of these scripts. The application will then only work partially in version 1 (the Clear option will not work) and not at all in versions 2 and 3 (the Clear and Calculate options will not work). It might be worth developing a version of the service that does not use JavaScript scripts.
- Version 4 requires that the client computer have a Java 2 virtual machine.
- Version 5 requires the client machine to have a browser with a Java 2 virtual machine.
When writing a web service, you must consider which types of clients you are targeting. If you want to reach the largest number of clients, you should write an application that sends only HTML to browsers (no JavaScript or applets). If you are working within an intranet and have control over the configuration of the workstations, you can then afford to be more demanding on the client side, and the previous version 5 may then be acceptable.
Versions 4 and 5 are web clients that retrieve the information they need from the HTML stream sent by the server. Very often, we have no control over this stream. This is the case when we have written a client for an existing web service on the network that is managed by someone else. Let’s take an example. Suppose our tax calculation simulation service was written by Company X. Currently, the service sends the simulations in an HTML table, and our client uses this fact to retrieve them. It thus compares each line of the server’s response to the regular expression:
// the pattern for a row in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Now suppose the application developer changes the visual appearance of the response by placing the simulations not in a table but in a list in the following format:
In this case, our web client will need to be rewritten. This is the constant threat facing web clients for applications that we do not control ourselves. XML can provide a solution to this problem:
- instead of generating HTML, the simulation service will generate XML. In our example, this could be
<simulations>
<headers married="married" children="children" salary="salary" tax="tax"/>
<simulation married="yes" children="2" salary="200000" tax="22504" />
<simulation spouse="no" children="2" salary="200000" tax="33388" />
</simulations>
- A stylesheet could be associated with this response, instructing browsers on the visual presentation of this XML response
- Programmed web clients would ignore this style sheet and retrieve the information directly from the XML stream of the response
If the service designer wishes to modify the visual presentation of the results provided, they will modify the style sheet rather than the XML. Thanks to the style sheet, browsers will display the new visual format, and programmatic web clients will not need to be modified. New versions of our simulation service could therefore be written:
- Version 6: The service provides an XML response accompanied by a stylesheet intended for browsers
- Version 7: The client is a standalone graphical application that processes the server’s XML response
- Version 8: The client is a Java applet that processes the server’s XML response
