Skip to content

5. L'applicazione per il calcolo delle imposte

5.1. Introduzione

Qui riprendiamo l'applicazione IMPOTS, utilizzata più volte nel manuale Java dello stesso autore. Rivediamo il problema. L'obiettivo è scrivere un'applicazione che calcoli l'imposta dovuta da un contribuente. Considereremo il caso semplificato di un contribuente che ha un solo stipendio da dichiarare:

  • calcoliamo il numero di scaglioni fiscali del dipendente come nbParts = nbEnfants / 2 + 1 se è celibe, e nbEnfants / 2 + 2 se è coniugato, dove nbEnfants è il numero di figli.
  • se ha almeno tre figli, riceve una quota aggiuntiva pari a metà
  • Calcoliamo il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
  • Calcoliamo il suo coefficiente familiare QF = R / nbParts
  • calcoliamo la sua imposta I. Consideriamo la seguente tabella:
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
39.312
0
0,65
49062

Ogni riga ha 3 campi. Per calcolare l'imposta I, trova la prima riga in cui QF <= campo1. Ad esempio, se QF = 23.000, la riga trovata sarà

    24740        0.15        2072.5

L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la condizione QF<=field1 non è mai soddisfatta, vengono utilizzati i coefficienti dell'ultima riga. Qui:

    0                0.65        49062

il che dà come risultato l'imposta I = 0,65*R - 49062*nbParts.

I dati che definiscono le diverse fasce di imposta sono memorizzati in un database ODBC-MySQL. MySQL è un DBMS di dominio pubblico che può essere utilizzato su varie piattaforme, tra cui Windows e Linux. Utilizzando questo DBMS, è stato creato un database denominato dbimpots, contenente una singola tabella chiamata impots. L'accesso al database è controllato da un nome utente/password, in questo caso admimpots/mdpimpots. La seguente schermata mostra come utilizzare il database dbimpots con 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

Il database dbimpots viene convertito in un'origine dati ODBC come segue:

  • Avviare l'Amministratore delle origini dati ODBC a 32 bit

Image

  • Utilizzare il pulsante [Aggiungi] per aggiungere una nuova origine dati ODBC

Image

  • Seleziona il driver MySQL e fai clic su [Fine]

54321

Image

  • Il driver MySQL richiede alcune informazioni:
1
il nome DSN da assegnare all'origine dati ODBC: può essere qualsiasi nome
2
il computer su cui è in esecuzione il DBMS MySQL — in questo caso, localhost. Vale la pena notare che il database potrebbe essere un database remoto. Le applicazioni locali che utilizzano l'origine dati ODBC non ne sarebbero a conoscenza. Questo sarebbe il caso, in particolare, della nostra applicazione Java.
3
il database MySQL da utilizzare. MySQL è un DBMS che gestisce database relazionali, ovvero insiemi di tabelle collegate tra loro da relazioni. Qui specifichiamo il nome del database gestito.
4
Il nome di un utente con diritti di accesso a questo database
5
la sua password

Sono state definite due classi per il calcolo dell'imposta: impots e impotsJDBC. Un'istanza della classe impots viene creata utilizzando i dati delle fasce di imposta passati come parametri in array:

// 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

La classe impotsJDBC deriva dalla precedente classe impots. Un'istanza della classe impotsJDBC viene costruita utilizzando i dati relativi alle fasce di imposta memorizzati in un database. Le informazioni necessarie per accedere a questo database vengono passate come parametri al costruttore:

// 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

Una volta creata un'istanza della classe impotsJDBC, è possibile richiamare ripetutamente il suo metodo calculate per calcolare l'imposta:

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

I tre dati richiesti possono essere ottenuti in diversi modi. Il vantaggio della classe impotsJDBC è che dobbiamo preoccuparci solo di ottenere questi dati. Una volta ottenute le tre informazioni (stato civile, numero di figli, stipendio annuo), richiamando il metodo calculer della classe impotsJDBC si ottiene l'importo dell'imposta dovuta.

5.2. Versione 1

Ci troviamo nel contesto di un'applicazione web che presenterebbe un'interfaccia HTML a un utente al fine di ottenere i tre parametri necessari per il calcolo dell'imposta:

  • stato civile (coniugato o no)
  • numero di figli
  • reddito annuo

Image

Il modulo viene visualizzato dalla seguente pagina JSP:

<%@ 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>

La pagina JSP si limita a visualizzare le informazioni che le vengono passate dal servlet principale dell'applicazione:

     // 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
Gli attributi "checked" e "unchecked" dei pulsanti di opzione possono assumere i valori "checked" o "unchecked" per indicare se il pulsante di opzione corrispondente è selezionato o meno
txtChildren
il numero di figli del contribuente
txtSalario
il loro stipendio annuale
txtTaxes
l'importo delle imposte dovute
errori
un elenco di errori, se presenti, se errors!=null

La pagina inviata al client contiene uno script JavaScript con una funzione "clear" associata al pulsante "Clear", il cui scopo è quello di riportare il modulo allo stato iniziale: pulsanti deselezionati, campi di immissione vuoti. Si noti che questo risultato non potrebbe essere ottenuto con un pulsante "reset" HTML. Infatti, quando si utilizza questo tipo di pulsante, il browser riporta il modulo allo stato in cui lo ha ricevuto. Tuttavia, nella nostra applicazione, il browser riceve moduli che potrebbero non essere vuoti.

Il servlet principale dell'applicazione si chiama main.java e il suo codice è il seguente:

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();
        }
    }
}
  • Il metodo init del servlet svolge due funzioni:
    • Recupera i propri parametri di inizializzazione. Questi gli consentono di connettersi al database ODBC contenente i dati relativi alle varie fasce di imposta (DSNimpots, admimpots, mdpimpots) e gli URL delle pagine associate all'applicazione: urlAffichageImpots per il modulo, urlErreur per la pagina di errore.
    • crea un'istanza della classe impotsJDBC

In entrambi i casi, vengono gestiti i potenziali errori e il messaggio di errore viene memorizzato nella variabile msgErreur.

  • Il metodo doGET
    • verifica innanzitutto che il servlet sia stato inizializzato correttamente. In caso contrario, viene visualizzata la pagina di errore
    • recupera i parametri previsti dal modulo fiscale: optMarie, txtEnfants, txtSalaire. Se uno di essi manca (==null), viene inviato un modulo fiscale vuoto. Si potrebbe pensare che la convalida del parametro optMarie sia superflua. Questo parametro corrisponde al valore di un pulsante di opzione e in questo caso può assumere solo uno dei valori "yes" o "no". Tuttavia, ciò trascura il fatto che nulla impedisce a un programma di interrogare direttamente il servlet inviandogli i parametri che desidera. Non si può mai essere certi di essere effettivamente connessi a un browser. Trascurare questo punto può portare a vulnerabilità di sicurezza nell'applicazione ed è, infatti, comune riscontrare tali problemi anche nelle applicazioni commerciali.
    • Viene verificata la validità di ciascuno dei tre parametri recuperati. Eventuali errori rilevati vengono aggiunti a un elenco di errori (ArrayList errors). Se non ci sono errori, viene calcolato l'importo dell'imposta; in caso contrario, non viene calcolato.
    • Le informazioni necessarie per visualizzare la pagina vengono impostate come attributi della richiesta, quindi viene visualizzato il modulo fiscale

La pagina JSP di errore è la seguente:

<%
    // 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>

L'applicazione web si chiama impots ed è configurata nel file server.xml di Tomcat come segue:

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

La directory dell'applicazione contiene le seguenti directory e file:

Image

Image

Image

Image

Il file di configurazione web.xml per l'applicazione impots è il seguente:

<?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>

Il servlet principale si chiama main e ha l'alias /main. È quindi accessibile tramite l'URL http://localhost:8080/impots/main.

Ecco alcuni esempi di applicazione:

Per inizializzarsi correttamente, il servlet deve avere accesso al database mysql-dbimpots. Ecco la pagina visualizzata se, ad esempio, il server MySQL non è in esecuzione e il database mysql-dbimpots è quindi inaccessibile:

Image

La pagina visualizzata in caso di immissione errata è la seguente:

Image

Se i dati inseriti sono corretti, viene calcolata l'imposta:

Image

5.3. Versione 2

Nell'esempio precedente, il server convalida i parametri txtEnfants e txtSalaire nel modulo. Qui, proponiamo di convalidarli utilizzando uno script JavaScript incluso nella pagina del modulo. Il browser esegue quindi la convalida dei parametri. Il server viene contattato solo se i parametri sono validi. Ciò consente di risparmiare "larghezza di banda". La pagina di visualizzazione JSP diventa la seguente:

<%@ 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>

Si notino le seguenti modifiche:

  • Il pulsante "Calcola" non è più un pulsante di invio, ma un pulsante normale associato a una funzione chiamata "calcola". Questa funzione verificherà la validità dei campi "txtEnfants" e "txtSalaire". Se sono validi, i dati del modulo verranno inviati al server; in caso contrario, verrà visualizzato un messaggio di errore.

Ecco un esempio di ciò che viene visualizzato in caso di errore:

Image

5.4. Versione 3

Stiamo apportando una leggera modifica all'applicazione per introdurre il concetto di sessione. Consideriamo ora l'applicazione come uno strumento di simulazione del calcolo delle imposte. Un utente può quindi simulare diversi "scenari" di contribuente e vedere quale sarebbe l'imposta dovuta per ciascuno di essi. La pagina web qui sotto fornisce un esempio di ciò che si potrebbe ottenere:

Image

Il servlet principale è stato modificato e ora si chiama simulations. All'interno dell'applicazione impots è configurato come segue:

<?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>

Il servlet principale si chiama simulations ed è basato sul file simulations.class. Ha l'alias /simulations, che lo rende accessibile tramite l'URL http://localhost:8080/impots/simulations. Ha gli stessi parametri di inizializzazione del servlet principale discusso in precedenza riguardo all'accesso al database. Appare un nuovo parametro, **urlSimulationsImpots,** che è l'URL della pagina JSP della simulazione (quella appena presentata sopra).

Il servlet simulations.java è simile al servlet main.java. Si differenzia da esso principalmente nei seguenti modi:

  • Il servlet principale calcola un valore per `txtImpots` in base ai parametri `optmarie`, `txtEnfants` e `txtSalaire`, e passa questo valore alla pagina JSP di visualizzazione
  • Il servlet simulations calcola il valore txtImpots allo stesso modo e memorizza i parametri (optMarie, txtEnfants, txtsalaire, txtImpots) in un elenco denominato simulations. Questo elenco viene passato come parametro alla pagina JSP di visualizzazione. Per garantire che questo elenco contenga tutte le simulazioni eseguite dall'utente, viene memorizzato come attributo della sessione corrente.

Il servlet delle simulazioni è il seguente (sono state incluse solo le righe di codice che differiscono da quelle dell'applicazione precedente):

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?
.........................
    }
}

La pagina JSP di visualizzazione simulationsImpots.jsp ora appare così (è stato mantenuto solo il codice che differisce dalla pagina JSP di visualizzazione dell'applicazione precedente).

<%@ 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. Versione 4

Ora creeremo un'applicazione autonoma che fungerà da client web per la precedente applicazione /impots/simulations. Questa applicazione avrà la seguente interfaccia grafica:

N.
Tipo
nome
ruolo
1
JTextField
txtTaxServiceUrl
URL del servizio di simulazione del calcolo delle imposte
2
JRadioButton
rdYes
Selezionato se sposato
3
JRadioButton
rdNo
Selezionato se non sposato
4
JSpinner
spinChildren
numero dei figli del contribuente (minimo=0, massimo=20, incremento=1)
5
JTextField
txtSalario
stipendio annuale del contribuente in F
6
JList in JScrollPane
lstSimulations
elenco delle simulazioni

Il menu Tax comprende le seguenti opzioni:

principale
principale
Opzione
secondaria
nome
ruolo
Imposte
   
 
Calcola
mnuCalculate
Calcola l'imposta dovuta quando tutti i dati necessari per il calcolo sono presenti e corretti
 
Cancella
mnuCancella
riporta il modulo allo stato iniziale
 
Esci
mnuExit
chiude l'applicazione

Regole di funzionamento

  • L'opzione di menu Calcola rimane disabilitata se il campo 1 o il campo 5 è vuoto
  • Nel campo 1 viene rilevato un URL sintatticamente errato

Image

  • viene rilevato uno stipendio errato

Image

  • viene segnalato un errore di connessione al server (nell'esempio 1 qui sotto, la porta è errata; nell'esempio 2, l'URL richiesto non esiste; nell'esempio 3, il database MySQL non era in esecuzione)

Image

Image

Image

  • Se tutto è corretto, vengono visualizzate le simulazioni

Image

Quando si scrive un client web programmato, è necessario sapere esattamente cosa invia il server in risposta alle varie richieste possibili da parte di un client. Il server invia una serie di righe HTML contenenti informazioni utili e altri elementi presenti esclusivamente per il layout HTML. Le espressioni regolari Java possono aiutarci a trovare le informazioni utili all’interno del flusso di righe inviate dal server. Per farlo, dobbiamo conoscere l’esatto formato delle varie risposte del server. Qui useremo il client web che abbiamo visto prima, che ci permette di visualizzare sullo schermo la risposta di un server a una richiesta URL. L'URL richiesto sarà quello del nostro servizio di simulazione del calcolo delle imposte, http://localhost:8080/impots/simulations, al quale possiamo passare dei parametri nella forma http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

Richiediamo l'URL mentre il database MySQL utilizzato per creare un oggetto di tipo fiscale non è in esecuzione:


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>

Per recuperare l'errore, il client web deve cercare nella risposta del server web la riga contenente il testo "Applicazione non disponibile". Ora avviamo il database MySQL e richiediamo lo stesso URL, passando i valori che si aspetta (li accetta sia come richiesta GET che POST — vedi il codice Java del server):


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>

Ecco l'intero documento HTML inviato dal server. I risultati delle varie simulazioni si trovano nell'unica tabella contenuta in questo documento. L'espressione regolare che ci permette di estrarre le informazioni rilevanti dal documento potrebbe essere la seguente:

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

dove le quattro espressioni tra parentesi rappresentano le quattro informazioni da estrarre.

Ora disponiamo delle linee guida su cosa fare quando l'utente richiede un calcolo delle imposte dall'interfaccia grafica precedente:

  • verificare che tutti i dati nell'interfaccia siano validi e segnalare eventuali errori.
  • connettersi all'URL specificato nel campo 1. Per farlo, seguiremo il modello del client web generico già presentato e studiato
  • nel flusso di risposta del server, utilizzare espressioni regolari per:
    • trovare il messaggio di errore, se presente
    • trovare i risultati della simulazione se non ci sono errori

Il codice relativo al menu "Calcola" è il seguente:

  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;
    }

Spieghiamo un po' questo codice:

  • La procedura mnuCalculer_actionPerformed verifica che i dati dell'interfaccia siano validi. Se non lo sono, viene visualizzato un messaggio di errore e la procedura termina. Se sono validi, viene eseguita la procedura calculerImpots.
  • La procedura calculerImpots inizia costruendo l'URL necessario per effettuare la richiesta
        // 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();
........................
  • quindi si connette a questo URL inviando le intestazioni HTTP appropriate:
             // 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("");
  • Una volta effettuata la richiesta, il nostro client web attende la risposta. Per prima cosa riceve le intestazioni HTTP dalla risposta del server. Analizza queste intestazioni per trovare il token di sessione. Infatti, deve inviare questo token al server in modo che il server possa tenere traccia delle varie simulazioni eseguite. Si noti che sarebbe stato più semplice per il client memorizzare le varie simulazioni eseguite dall'utente stesso. Tuttavia, ci atteniamo all'idea di inviare nuovamente il token per fornire un nuovo esempio di gestione della sessione. La prima riga della risposta viene gestita separatamente. Deve essere nella forma HTTP/versione 200 OK per indicare che l'URL richiesto esiste effettivamente. Se non è in questa forma, concludiamo che l'utente ha richiesto un URL errato e lo informiamo di conseguenza.
         // 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
  • Una volta elaborate le intestazioni HTTP, passiamo alla parte HTML della risposta
             // 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));
            }
  • La procedura getSimulations restituisce l'elenco delle simulazioni, se presenti; tale elenco risulterà vuoto se il server restituisce un messaggio di errore. In tal caso, il messaggio di errore viene visualizzato in una finestra di dialogo. Se l'elenco non è vuoto, viene visualizzato nell'elenco a discesa dell'interfaccia grafica utente.
  • La procedura getSimulations confronta ogni riga della risposta HTML con l'espressione regolare che rappresenta il messaggio di errore (Applicazione non disponibile...) e con l'espressione regolare che rappresenta una simulazione. Se viene trovato il messaggio di errore, questo viene visualizzato e la procedura termina. Se viene trovato il risultato di una simulazione, questo viene aggiunto all'elenco delle simulazioni. Al termine della procedura, questo elenco viene restituito come risultato.
    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. Versione 5

Qui trasformiamo la precedente applicazione grafica autonoma in un'applet Java. L'interfaccia grafica è leggermente diversa. Con l'applicazione autonoma, l'utente forniva autonomamente l'URL del servizio di simulazione del calcolo delle imposte e l'applicazione si connetteva a quell'URL. Qui, l'applicazione client è un browser e l'utente richiederà l'URL del documento HTML contenente l'applet. È importante ricordare che un'applet Java può stabilire una connessione di rete solo con il server da cui è stata scaricata. L'URL del servizio di simulazione si troverà quindi sullo stesso server del documento HTML contenente l'applet. Nel nostro esempio, si tratterà di un parametro di inizializzazione dell'applet inserito nel campo txtUrlServiceImpots, che non sarà modificabile dall'utente. Il risultato è il seguente client:

Image

Il documento HTML contenente l'applet si chiama simulations.htm ed è il seguente:

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

L'applet ha un parametro chiamato *urlServiceImpots, che è l'URL del servizio di simulazione del calcolo delle imposte. Questo URL è relativo all'URL del documento HTML simulations.htm. Pertanto, se un browser recupera questo documento tramite l'URL http://localhost:8080/impots/simulations.htm, l'URL del servizio di simulazione sarà http://localhost:8080/impots/simulations. Se questo URL fosse stato http://stahe:8080/impots/simulations.htm, l'URL del servizio di simulazione sarebbe stato http://stahe:8080/impots/simulations*.

L'applet appletImpots.java incorpora integralmente il codice della precedente applicazione grafica autonoma, rispettando al contempo le regole per la conversione di un'applicazione grafica in un'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);
...............
  }

Quando un browser carica un'applet, esegue innanzitutto il suo metodo init. In questo metodo, abbiamo recuperato il valore del parametro urlServiceImpots dell'applet, calcolato l'URL completo del servizio di simulazione e inserito questo valore nel campo txtURLServiceImpots come se l'utente lo avesse digitato. Una volta fatto ciò, non vi è più alcuna differenza tra le due applicazioni. In particolare, il codice associato al menu Calculate è identico. Ecco un esempio di esecuzione:

Image

5.7. Conclusione

Abbiamo illustrato diverse versioni della nostra applicazione client-server per il calcolo delle imposte:

  • Versione 1: Il servizio è fornito da un insieme di servlet e pagine JSP; il client è un browser. Esegue una singola simulazione e non conserva alcuna cronologia di quelle precedenti.
  • Versione 2: aggiungiamo alcune funzionalità lato browser incorporando script JavaScript nel documento HTML caricato dal browser. Convalida i parametri del modulo.
  • Versione 3: Abbiamo abilitato il servizio a ricordare le varie simulazioni eseguite da un client gestendo una sessione. L'interfaccia HTML è stata modificata di conseguenza per visualizzare queste simulazioni.
  • Versione 4: il client è ora un'applicazione grafica autonoma. Questo ci permette di riprendere lo sviluppo di client web programmati.
  • Versione 5: Il client diventa un applet Java. Ora disponiamo di un'applicazione client-server interamente programmata in Java, sia sul lato server che sul lato client.

A questo punto, si possono fare alcune osservazioni:

  • Le versioni da 1 a 3 supportano browser che non hanno altre funzionalità se non quella di eseguire script JavaScript. Si noti che un utente ha sempre la possibilità di disabilitare l'esecuzione di questi script. L'applicazione funzionerà quindi solo parzialmente nella versione 1 (l'opzione Cancella non funzionerà) e per niente nelle versioni 2 e 3 (le opzioni Cancella e Calcola non funzioneranno). Potrebbe valere la pena sviluppare una versione del servizio che non utilizzi script JavaScript.
  • La versione 4 richiede che il computer client disponga di una macchina virtuale Java 2.
  • La versione 5 richiede che il computer client disponga di un browser con una macchina virtuale Java 2.

Quando si sviluppa un servizio web, è necessario valutare a quali tipi di client ci si rivolge. Se si desidera raggiungere il maggior numero possibile di utenti, è consigliabile sviluppare un'applicazione che invii ai browser solo codice HTML (senza JavaScript né applet). Se invece si opera all'interno di una rete intranet e si ha il controllo sulla configurazione delle postazioni di lavoro, è possibile permettersi requisiti più esigenti sul lato client, e in tal caso la precedente versione 5 potrebbe risultare accettabile.

Le versioni 4 e 5 sono client web che recuperano le informazioni di cui hanno bisogno dal flusso HTML inviato dal server. Molto spesso, non abbiamo alcun controllo su questo flusso. Questo è il caso in cui abbiamo scritto un client per un servizio web esistente sulla rete che è gestito da qualcun altro. Facciamo un esempio. Supponiamo che il nostro servizio di simulazione del calcolo delle imposte sia stato scritto dalla Società X. Attualmente, il servizio invia le simulazioni in una tabella HTML e il nostro client sfrutta questa caratteristica per recuperarle. Confronta quindi ogni riga della risposta del server con l'espressione regolare:

         // 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>");

Supponiamo ora che lo sviluppatore dell'applicazione modifichi l'aspetto visivo della risposta inserendo le simulazioni non in una tabella ma in un elenco nel seguente formato:

Résultat des simulations
<ul>
    <li>oui,2,200000,22504
  <li>non,2,200000,33388
</ul>  

In questo caso, il nostro client web dovrà essere riscritto. Questa è la minaccia costante che i client web devono affrontare per le applicazioni che non controlliamo direttamente. L'XML può fornire una soluzione a questo problema:

  • invece di generare HTML, il servizio di simulazione genererà XML. Nel nostro esempio, questo potrebbe essere
<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 questa risposta potrebbe essere associato un foglio di stile, che indichi ai browser la presentazione visiva di questa risposta XML
  • I client web programmati ignorerebbero questo foglio di stile e recupererebbero le informazioni direttamente dal flusso XML della risposta

Se il progettista del servizio desidera modificare la presentazione visiva dei risultati forniti, modificherà il foglio di stile anziché l'XML. Grazie al foglio di stile, i browser visualizzeranno il nuovo formato visivo e i client web programmati non dovranno essere modificati. Si potrebbero quindi scrivere nuove versioni del nostro servizio di simulazione:

  • Versione 6: Il servizio fornisce una risposta XML accompagnata da un foglio di stile destinato ai browser
  • Versione 7: il client è un'applicazione grafica autonoma che elabora la risposta XML del server
  • Versione 8: il client è un applet Java che elabora la risposta XML del server