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 |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)
 
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
 
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
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{

  // data required for tax calculation
   // come from an external source

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // empty builder
  protected impots(){}

   // manufacturer
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // check that the 3 arrays have the same size
    boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
    if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
      LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
    // it's good
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//manufacturer

   // tAX CALCULATION
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // calculating the number of shares
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*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{
  // addition of a constructor for building
   // limit tables, coeffr, coeffn from table
   // database taxes
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

     // data tables
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connection to base
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creation of a Statement object
    Statement S=connect.createStatement();
    // select request
    String select="select limites, coeffr, coeffn from impots";
    // query execution
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // running line operation
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// next line
     // closing resources
    RS.close();
    S.close();
    connect.close();
     // data transfer to bounded arrays
    int n=aLimites.size();
    limites=new double[n];
    coeffR=new double[n];
    coeffN=new double[n];
    for(int i=0;i<n;i++){
      limites[i]=Double.parseDouble((String)aLimites.get(i));
      coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
      coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
    }//for
  }//manufacturer
}//class

Once an instance of the impotsJDBC class has been constructed, its calculate method can be called repeatedly to calculate the tax:

  public long calculer(boolean marié, int nbEnfants, int salaire){

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.*" %>

<%
    // on récupère les attributs passés par la servlet principale
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
          // raz du formulaire
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//with
      }//effacer
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/main" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                    <input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
              <input type="radio" name="optMarie" value="non" <%= chknon %>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculer"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <%
        // y-a-t-il des erreurs
      if(erreurs!=null){
          // affichage des erreurs
        out.println("<hr>");
        out.println("<font color=\"red\">");
        out.println("Les erreurs suivantes se sont produites<br>");
        out.println("<ul>");
        for(int i=0;i<erreurs.size();i++){
            out.println("<li>"+(String)erreurs.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 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 txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
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 msgErreur=null;
    String urlAffichageImpots=null;
    String urlErreur=null;
    String DSNimpots=null;
    String admimpots=null;
    String mdpimpots=null;
    impotsJDBC impots=null;

    //-------- GET
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{

        // was the initialization successful?
        if(msgErreur!=null){
             // we hand over to the error page
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // query attributes
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // retrieve query parameters
        String optMarie=request.getParameter("optMarie");              // marital status
        String txtEnfants=request.getParameter("txtEnfants");       // no. of children
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // annual salary
        if(txtSalaire==null) txtSalaire="";

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // missing parameters
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // we hand over to the tax display url
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // we have all the parameters - we check them
        ArrayList erreurs=new ArrayList();
         // marital status
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // error
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // number of children
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // error
            erreurs.add("Nombre d'enfants incorrect");
        }
         // salary
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // error
            erreurs.add("Salaire incorrect");
        }

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // you can calculate the tax payable
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // other query attributes
        if(optMarie.equals("oui")){
            request.setAttribute("chkoui","checked");
            request.setAttribute("chknon","");
        }else{
            request.setAttribute("chknon","checked");
            request.setAttribute("chkoui","");
        }
        request.setAttribute("txtEnfants",txtEnfants);
        request.setAttribute("txtSalaire",txtSalaire);
        request.setAttribute("txtImpots",txtImpots);

         // we hand over to the tax display url
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

     //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parameters ok?
        if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         // create an instance of impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=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
  // une erreur s'est produite
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </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>urlAffichageImpots</param-name>
        <param-value>/impots.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>urlErreur</param-name>
        <param-value>/erreur.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>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
.......
      }//effacer

      function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer        
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
........
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
          <td><input type="button" value="Effacer" onclick="effacer()"></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>/simulationsImpots.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>urlErreur</param-name>
        <param-value>/erreur.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 msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=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();
        // put the simulations in the current query
        request.setAttribute("simulations",simulations);

         // other query attributes
...........

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // we hand over 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, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // you can calculate the tax payable
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // the current result is added to the previous simulations
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // the new simulations value is added to the session
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // other query attributes
..........

         // we hand over to the simulations display url
        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 initialization parameters
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        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.*" %>

<%
    // on récupère les attributs passés par la servlet principale
...........
  ArrayList simulations=(ArrayList)request.getAttribute("simulations");  
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
........
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
.......................
           </form> 
</center>
    <hr>
    <%
        // y-a-t-il des erreurs ?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // résultats des simulations
        out.println("<h3>Résultats des simulations<h3>");
        out.println("<table \"border=\"1\">");
        out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (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
 
 
<!-- dÚbut from page HTML -->
<html>
  <head>
        <title>impots</title>
  </head>
  <body>
        <h3>calcul d'impôts</h3>
        <hr>
    Application indisponible([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>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...................................................
      }//effacer
 
      function calculer(){
...................................................
      }//calculer
                </script>
  </head>
 
  <body background="/impots/images/standard.jpg">
        <center>
        Calcul d'imp¶ts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
      </form>
    </center>
    <hr>
    <h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</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 mnuCalculer_actionPerformed(ActionEvent e) {
    // tAX CALCULATION
         // verification URL service
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // error msg
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // focus on wrong field
            txtURLServiceImpots.requestFocus();
             // back to interface
            return;
        }
     // salary verification
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // error msg
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // focus on wrong field
      txtSalaire.requestFocus();
       // back to interface
      return;
    }
     // no. of children
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // tax calculation
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // error is displayed
            JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
        }
  }//mnuCalculer


    public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
          throws Exception{
         // tAX CALCULATION
         // urlImpots : URL of the tax department
         // married: true if married, false otherwise
         // nbEnfants : number of children
         // salary: annual salary

         // remove from urlImpots the info needed to connect to the tax server
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();

         // local data
        Socket  client=null;                        // the customer
        BufferedReader IN=null;                    // the customer's reading flow
        PrintWriter OUT=null;                        // the customer's writing flow
        String réponse=null;                        // server response
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        try{
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request 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 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // we have a URL problem
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // it's over
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // the result of the model comparison
        Matcher résultat=null;
         // simulations
        ArrayList listeSimulations=new ArrayList();

         // read all the lines to the end
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // follow-up
             // the line is compared with the error model if the simulation part has not yet been encountered
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // error msg
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // it's over
                    return listeSimulations;
                }//if
            }//if
             // the line is compared with the simulation model
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // we found a row in the
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // the simulation was a success
                simulationRéussie=true;
            }//if
        }//while
        // end
        return listeSimulations;
    }

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
        // on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        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 customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request 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 model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model of a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // we have a URL problem
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.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 HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.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 model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\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>Simulations de calculs d'impôts</title>
  </head>
  <body background="/impots/images/standard.jpg">
      <center>
          <h3>Simulations de calculs d'impôts</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 mnuCalculer = new JMenuItem();
.............


   //Building 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,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // end
            return;
        }
         // put the URL in its field
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // calculate menu disabled
    mnuCalculer.setEnabled(false);
     // spinner Children - between 0 and 20 children
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Initialize 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 model of a line 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:

Résultat des simulations
<ul>
    <li>oui,2,200000,22504
  <li>non,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>
    <entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
  <simulation marie="non" enfants="2" salaire="200000" impot="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