Skip to content

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

    24740        0.15        2072.5

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:

    0                0.65        49062

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

Image

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

Image

  • Select the MySQL driver and click [Finish]

54321

Image

  • 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:

  public long calculate(boolean married, int numChildren, int salary){

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

Image

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");
chkoui, chknon
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
txtChildren
the taxpayer's number of children
txtSalary
their annual salary
txtTaxes
the amount of tax due
errors
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:

                <Context path="/impots" docBase="e:/data/serge/servlets/impots" />

The application directory contains the following directories and files:

Image

Image

Image

Image

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:

Image

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

Image

If the entries are correct, the tax is calculated:

Image

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:

Image

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:

Image

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

Image

  • an incorrect salary is detected

Image

  • 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)

Image

Image

Image

  • If everything is correct, the simulations are displayed

Image

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&param2=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:

"<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>"

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:

Image

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:

Image

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:

Simulation results
<ul>
    <li>yes,2,200000,22504
  <li>no,2,200000,33388
</ul>  

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