Skip to content

5. L’applicazione IMPOTS

5.1. Introduzione

Riprendiamo qui l’applicazione IMPOTS, utilizzata più volte nella dispensa su Java dello stesso autore. Ricordiamo la problematica. Si tratta di scrivere un’applicazione che consenta di calcolare l’imposta di un contribuente. Consideriamo il caso semplificato di un contribuente che abbia da dichiarare solo il proprio stipendio:

  • si calcola il numero di quote del dipendente nbParts=nbEnfants/2 +1 se non è sposato, nbEnfants/2+2 se è sposato, dove nbEnfants è il numero dei suoi figli.
  • se ha almeno tre figli, ha diritto a una mezza quota in più
  • si calcola il suo reddito imponibile R = 0,72 * S, dove S è il suo stipendio annuale
  • si calcola il suo coefficiente familiare QF = R / nbParts
  • si calcola la sua imposta I. Consideriamo la seguente tabella:
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24.740
0,15
2072,5
31810
0,2
3309,5
39.970
0,25
4900
48360
0,3
6898,5
55790
0,35
9316,5
92970
0,4
12106
127.860
0,45
16754,5
151250
0,50
23147,5
172040
0,55
30710
195000
0,60
39312
0
0,65
49062

Ogni riga ha 3 campi. Per calcolare l'imposta I, si cerca la prima riga in cui QF<=campo1. Ad esempio, se QF=23000, si troverà la riga

    24740        0.15        2072.5

L'imposta I è quindi pari a 0,15*R - 2072,5*nbParts. Se QF è tale che la relazione QF<=campo1 non è mai soddisfatta, allora vengono utilizzati i coefficienti dell’ultima riga. In questo caso:

    0                0.65        49062

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

I dati che definiscono le diverse fasce d'imposta si trovano in un database ODBC-MySQL. MySQL è un SGBD di dominio pubblico utilizzabile su diverse piattaforme, tra cui Windows e Linux. Con questo SGBD è stato creato un database denominato dbimpots contenente un'unica tabella chiamata impots. L'accesso al database è controllato da un nome utente e una password, in questo caso admimpots/mdpimpots. La schermata seguente 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 nella fonte dati ODBC nel modo seguente:

  • si avvia il gestore delle fonti dati ODBC a 32 bit

Image

  • si utilizza il pulsante [Add] per aggiungere una nuova fonte dati ODBC

Image

  • si seleziona il driver MySQL e si esegue [Terminer]

54321

Image

  • il driver MySQL richiede alcune informazioni:
1
il nome DSN da assegnare alla fonte di dati ODBC - può essere qualsiasi nome
2
il computer su cui viene eseguito il processo SGBD MySQL - in questo caso localhost. È interessante notare che il database potrebbe essere un database remoto. Le applicazioni locali che utilizzano la fonte dati ODBC non se ne accorgerebbero. Questo varrebbe in particolare per la nostra applicazione Java.
3
il database MySQL da utilizzare. MySQL è un SGBD che gestisce database relazionali, ovvero insiemi di tabelle collegate tra loro da relazioni. In questo caso, si specifica 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 delle imposte: impots e impotsJDBC. Viene creata un'istanza della classe impots con i dati delle fasce di imposta passati come parametri in array:

// creazione di una classe "imposte"

public class impots{

  // i dati necessari per il calcolo dell'imposta
   // provengono da una fonte esterna

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

   // costruttore vuoto
  protected impots(){}

   // costruttore
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // si verifica che le 3 tabelle abbiano le stesse dimensioni
    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+")");
    // va bene
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//produttore

   // calcolo dell'imposta
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // calcolo del numero di quote
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // calcolo del reddito imponibile e del quoziente familiare
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // calcolo dell'imposta
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // restituzione del risultato
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calcolare
}//classe

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

// pacchetti importati
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // aggiunta di un costruttore che consente di creare
   // le tabelle limiti, coeffr, coeffn a partire dalla tabella
   // imposte di un database
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nome DSN del database
     // userIMPOTS, mdpIMPOTS: login/password di accesso al database

     // le tabelle dei dati
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connessione al database
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creazione di un oggetto Statement
    Statement S=connect.createStatement();
    // query SELECT
    String select="select limites, coeffr, coeffn from impots";
    // esecuzione della query
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // elaborazione della riga corrente
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// riga successiva
     // chiusura delle risorse
    RS.close();
    S.close();
    connect.close();
     // trasferimento dei dati in tabelle delimitate
    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
  }//costruttore
}//classe

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

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

L'acquisizione dei tre dati necessari può avvenire in diversi modi. Il vantaggio della classe impotsJDBC è che ci si deve occupare esclusivamente di tale acquisizione. Una volta ottenute le tre informazioni (stato civile, numero di figli, stipendio annuo), la chiamata al metodo calculer della classe impotsJDBC ci fornisce l’imposta da pagare.

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 al calcolo dell’imposta:

  • lo stato civile (coniugato o no)
  • il numero di figli
  • lo stipendio annuale

Image

La visualizzazione del modulo avviene tramite la seguente pagina JSP:

<%@ page import="java.util.*" %>

<%
     // si recuperano gli attributi passati dal 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(){
           // azzeramento del modulo
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//con
      }//cancella
        </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>
    <%
         // ci sono errori
      if(erreurs!=null){
           // visualizzazione degli errori
        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 trasmesse dal servlet principale dell'applicazione:

     // si recuperano gli attributi passati dal 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");
chkoui, chknon
gli attributi dei pulsanti di opzione «sì» e «no» possono assumere i valori "checked" o "" per attivare o disattivare il pulsante di opzione corrispondente
txtEnfants
il numero di figli del contribuente
txtSalaire
il suo stipendio annuale
txtImpots
l'importo dell'imposta da pagare
erreurs
un eventuale elenco di errori se errori!=null

La pagina inviata al cliente contiene uno script JavaScript con una funzione effacer associata al pulsante "Effacer", il cui scopo è riportare il modulo allo stato iniziale: pulsante deselezionato, campi di immissione vuoti. Si noti che questo risultato non poteva essere ottenuto con un pulsante HTML di tipo "reset". Infatti, quando si utilizza questo tipo di pulsante, il browser riporta il modulo nello stato in cui lo ha ricevuto. Tuttavia, nella nostra applicazione, il browser riceve moduli che possono 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{

    // variabili di istanza
    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{

        // L'inizializzazione è andata a buon fine?
        if(msgErreur!=null){
             // si passa il controllo alla pagina di errore
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // attributi della richiesta
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // si recuperano i parametri della richiesta
        String optMarie=request.getParameter("optMarie");              // stato civile
        String txtEnfants=request.getParameter("txtEnfants");       // numero di figli
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // stipendio annuo
        if(txtSalaire==null) txtSalaire="";

         // Sono presenti tutti i parametri previsti?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // mancano dei parametri
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // si passa all'URL di visualizzazione dell'imposta
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // sono presenti tutti i parametri - li verifichiamo
        ArrayList erreurs=new ArrayList();
         // stato civile
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // errore
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // numero di figli
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // errore
            erreurs.add("Nombre d'enfants incorrect");
        }
         // stipendio
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // errore
            erreurs.add("Salaire incorrect");
        }

         // se ci sono errori, li si passa come attributi della query
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // è possibile calcolare l'imposta da pagare
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // gli altri attributi della richiesta
        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);

         // si passa il controllo all'URL di visualizzazione dell'imposta
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // si recuperano i parametri di inizializzazione
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

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

         // si crea un'istanza di impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • Il metodo init del servlet svolge due operazioni:
    • recupera i propri parametri di inizializzazione. Questi le consentono di connettersi al database ODBC contenente i dati delle diverse fasce fiscali (DSNimpots, admimpots, mdpimpots) e i 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, gli eventuali errori vengono gestiti e il messaggio di errore viene inserito 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 verifica della validità del parametro optMarie sia superflua. Si tratta infatti del valore di un pulsante di opzione e in questo contesto può assumere solo uno dei valori «oui» e «non». Ma ciò significa trascurare 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 realmente connessi a un browser. Trascurare questo aspetto può portare a falle di sicurezza nell’applicazione ed è del resto frequente trovarne anche nelle applicazioni commerciali.
    • Viene verificata la validità di ciascuno dei tre parametri recuperati. Ogni errore rilevato viene aggiunto a un elenco di errori (ArrayList errori). Se non ci sono errori, l’importo dell’imposta viene calcolato; in caso contrario, non viene calcolato.
    • Le informazioni necessarie per la visualizzazione della pagina vengono inserite negli attributi della richiesta e il modulo fiscale viene quindi visualizzato

La pagina di errore JSP è la seguente:

<%
     // jspService
   // si è verificato un errore
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- inizio della pagina 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 nel modo seguente:

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

La cartella dell’applicazione contiene le seguenti cartelle e file:

Image

Image

Image

Image

Il file di configurazione dell'applicazione impots, denominato web.xml, è 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 URL e http://localhost:8080/impots/main.

Ecco alcuni esempi di applicazione:

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

Image

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

Image

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

Image

5.3. Versione 2

Nell'esempio precedente, la validità dei parametri txtEnfants e txtSalaire del modulo viene verificata dal server. Qui ci proponiamo di verificarla tramite uno script JavaScript incluso nella pagina del modulo. È quindi il browser a effettuare la verifica dei parametri. Il server viene interpellato solo se questi sono validi. In questo modo si risparmia "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(){
.......
      }//cancella

      function calculer(){
           // verifica dei parametri prima dell'invio al server
        with(document.frmImpots){
          //numero di figli
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // il modello non è stato verificato
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //stipendio
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // il modello non è stato verificato
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//se
          // Va bene - inviamo il modulo al server
          submit();
        }//con
      }//calcolare        
        </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 Calculer non è più di tipo submit, bensì di tipo button, associato a una funzione denominata calculer. È questa funzione che effettuerà l'analisi dei campi txtEnfants e txTsalaire. Se sono corretti, i valori del modulo verranno inviati al server (submit); in caso contrario, verrà visualizzato un messaggio di errore.

Ecco un esempio di visualizzazione in caso di errore:

Image

5.4. Versione 3

Modifichiamo leggermente l’applicazione per introdurvi il concetto di sessione. Consideriamo ora che l’applicazione sia un programma di simulazione del calcolo delle imposte. Un utente può quindi simulare diverse «configurazioni» di contribuente e vedere quale sarebbe, per ciascuna di esse, l’imposta da pagare. La pagina WEb riportata di seguito fornisce un esempio di ciò che si potrebbe ottenere:

Image

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

<?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» e si basa sul file di classe simulations.class. Ha l’alias /simulations che la rende accessibile tramite URL e http://localhost:8080/impots/simulations. Presenta gli stessi parametri di inizializzazione del servlet principale esaminato in precedenza per quanto riguarda l’accesso al database. Compare un nuovo parametro, urlSimulationsImpots, che corrisponde al URL della pagina di simulazione JSP (quella appena presentata poco sopra).

La servlet simulations.java è simile alla servlet main.java. Da essa si differenzia per i seguenti punti principali:

  • la servlet principale calcola un valore txtImpots a partire dai parametri optmarie, txtEnfants e txtSalaire e passa questo valore alla pagina di visualizzazione JSP
  • il servlet «simulazioni» calcola allo stesso modo il valore txtImpots, registra i parametri (optMarie, txtEnfants, txtsalaire, txtImpots) in un elenco denominato «simulazioni». È proprio questo elenco che viene passato come parametro alla pagina di visualizzazione JSP. Affinché tale elenco contenga tutte le simulazioni effettuate dall’utente, viene salvato come attributo della sessione corrente.

Il servlet «simulazioni» è il seguente (sono state mantenute solo le righe di codice che differiscono da quelle dell’applicazione precedente):

import java.io.*;
.......

public class simulations extends HttpServlet{

    // variabili di istanza
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         // si recuperano le simulazioni precedenti della sessione
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // si inseriscono le simulazioni nella query corrente
        request.setAttribute("simulations",simulations);

         // altri attributi della query
...........

         // sono presenti tutti i parametri previsti
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // si passa all'URL di visualizzazione delle simulazioni dei calcoli fiscali
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // si dispone di tutti i parametri - li si verifica
...........

         // se ci sono errori, li si inserisce negli attributi della richiesta
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // è possibile calcolare l'imposta da pagare
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // si aggiunge il risultato corrente alle simulazioni precedenti
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // il nuovo valore delle simulazioni viene reinserito nella sessione
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // altri attributi della query
..........

         // si passa all'URL di visualizzazione delle simulazioni
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // si recuperano i parametri di inizializzazione
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parametri ok?
.........................
    }
}

La pagina di visualizzazione JSP, derivata dalla pagina simulationsImpots.jsp, è diventata la seguente (è stato mantenuto solo il codice che differisce dalla pagina di visualizzazione JSP dell'applicazione precedente).

<%@ page import="java.util.*" %>

<%
     // si recuperano gli attributi passati dal 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>
    <%
         // Ci sono errori?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // risultati delle simulazioni
        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 dell'applicazione precedente /impots/simulations. Questa applicazione avrà la seguente interfaccia grafica:

n.
tipo
nome
ruolo
1
JTextField
txtUrlServiceImpots
URL del servizio di simulazione del calcolo delle imposte
2
JRadioButton
rdOui
spuntare se sposato
3
JRadioButton
rdNon
da spuntare se non sposato
4
JSpinner
spinEnfants
numero di figli del contribuente (minimo=0, massimo=20, incremento=1)
5
JTextField
txtSalaire
stipendio annuo in F del contribuente
6
JList in JScrollPane
lstSimulations
elenco delle simulazioni

Il menu Imposte è composto dalle seguenti opzioni:

Opzione
principale
Opzione
secondaria
nome
ruolo
Imposte
   
 
Calcola
mnuCalculer
calcola l'imposta da pagare quando tutti i dati necessari al calcolo sono presenti e corretti
 
Cancella
mnuEffacer
riporta il modulo allo stato iniziale
 
Esci
mnuQuitter
chiude l'applicazione

Regole di funzionamento

  • l'opzione di menu Calculer rimane disattivata se uno dei campi 1 o 5 è vuoto
  • viene rilevata una URL sintatticamente errata nel campo 1

Image

  • viene rilevato uno stipendio errato

Image

  • viene segnalato qualsiasi errore di connessione al server (nell'esempio 1 qui sotto, la porta è errata; nell'esempio 2, il 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 sviluppa un client web, è necessario sapere esattamente cosa invia il server in risposta alle diverse richieste possibili da parte di un client. Il server invia una serie di righe HTML che comprendono informazioni utili e altre presenti solo per motivi di impaginazione HTML. Le espressioni regolari di Java possono aiutarci a individuare le informazioni utili nel flusso delle righe inviate dal server. A tal fine, dobbiamo conoscere il formato esatto delle diverse risposte del server. In questa sede utilizzeremo il client web già visto in precedenza, che consente di visualizzare sullo schermo la risposta di un server alla richiesta di un URL. Il URL richiesto sarà quello del nostro servizio di simulazione del calcolo delle imposte http://localhost:8080/impots/simulations, al quale potremo passare dei parametri nella forma http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

Richiediamo l’URL mentre la base MySQL che consente di creare un oggetto di tipo impots non è stata avviata:


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 della pagina 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 individuare l'errore, il client web dovrà 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 indifferentemente sotto forma di GET o di POST – cfr. 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(){
...................................................
      }//cancella

      function calculer(){
...................................................
      }//calcola
                </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>

Qui è riportato il documento completo HTML inviato dal server. I risultati delle diverse simulazioni sono riportati nell'unica tabella presente in questo documento. L'espressione regolare che ci permette di recuperare 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 recuperare.

Ora disponiamo delle linee guida su cosa fare quando l’utente richiede il calcolo dell’imposta dall’interfaccia grafica precedente:

  • verificare che tutti i dati dell’interfaccia siano validi ed eventualmente segnalare gli errori;
  • connettersi all’URL indicato nel campo 1. A tal fine si seguirà il modello del client web generico già presentato e studiato
  • nel flusso della risposta del server, utilizzare espressioni regolari per:
    • individuare il messaggio di errore, se presente
    • individuare i risultati delle simulazioni se non vi sono errori

Il codice relativo al menu calculer è il seguente:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // calcolo dellimposta
         // verifica URL servizio
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // messaggio di errore
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // focus sul campo errato
            txtURLServiceImpots.requestFocus();
             // ritorno all'interfaccia
            return;
        }
     // verifica stipendio
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // messaggio di errore
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // focus sul campo errato
      txtSalaire.requestFocus();
       // torna all'interfaccia
      return;
    }
     // numero di figli
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // si calcola l'imposta
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // viene visualizzato l'errore
            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{
         // calcolo dell'imposta
         // urlImpots: URL dell'ufficio delle imposte
         // sposato: true se sposato, false altrimenti
         // nbEnfants: numero di figli
         // stipendio: stipendio annuale

         // da urlImpots si estraggono le informazioni necessarie per la connessione al server dell'agenzia delle entrate
        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();

         // dati locali
        Socket  client=null;                        // il cliente
        BufferedReader IN=null;                    // il flusso di lettura del cliente
        PrintWriter OUT=null;                        // il flusso di scrittura del client
        String réponse=null;                        // risposta del server
         // il modello ricercato nelle intestazioni HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // il modello di una risposta corretta
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // il risultato del confronto con il modello
        Matcher résultat=null;

        try{
             // si effettua la connessione al server
            client=new Socket(host,port);

            // si creano i flussi di ingresso-uscita del cliente TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // si richiede l'URL - invio delle intestazioni HTTP
            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("");

             // si legge la prima riga della risposta
            réponse=IN.readLine();
             // si confronta la riga HTTP con il modello della risposta corretta
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // si riscontra un problema con URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(risultato)

             // si legge la risposta fino alla fine delle intestazioni cercando l'eventuale cookie
            while((réponse=IN.readLine())!=null){
                 // riga vuota?
                if(réponse.equals("")) break;
                // riga HTTP non vuota
                 // se non si dispone del token di sessione, lo si cerca
                if (JSESSIONID.equals("")){
                    // si confronta la riga HTTP con il modello del cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // è stato trovato il cookie del token
                        JSESSIONID=résultat.group(1);
                    }//if(risultato)
                }//if(JSESSIONID)
            }//while

             // fine delle intestazioni HTTP - si passa al codice HTML
             // per recuperare le simulazioni
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // è tutto
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

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

        // il modello di una riga della tabella delle simulazioni
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // il modello di una riga dell'elenco degli errori
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // il risultato del confronto con il modello
        Matcher résultat=null;
         // le simulazioni
        ArrayList listeSimulations=new ArrayList();

         // vengono lette tutte le righe fino alla fine
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // monitoraggio
             // si confronta la riga con il modello di errore se la sezione delle simulazioni non è stata ancora raggiunta
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // messaggio di errore
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // operazione completata
                    return listeSimulations;
                }//se
            }//se
             // si confronta la riga con il modello di simulazione
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // è stata trovata una riga della tabella
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // la simulazione è andata a buon fine
                simulationRéussie=true;
            }//if
        }//mentre
        // fine
        return listeSimulations;
    }

Spieghiamo brevemente questo codice:

  • la procedura mnuCalculer_actionPerformed verifica che i dati dell'interfaccia siano validi. Se non lo sono, viene generato un messaggio di errore e la procedura viene interrotta. Se invece sono validi, viene eseguita la procedura calculerImpots.
  • La procedura calculerImpots inizia creando la procedura URL che deve richiamare
         // si estraggono da urlImpots le informazioni necessarie per la connessione al server fiscale
        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:
             // ci si connette al server
            client=new Socket(host,port);

            // si creano i flussi di entrata e uscita del cliente TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // si richiede l'URL - invio delle intestazioni HTTP
            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. Innanzitutto riceve le intestazioni HTTP della risposta del server. Esamina tali intestazioni per individuare il token di sessione. Infatti, dovrà rinviare quest'ultimo al server affinché quest'ultimo possa tenere traccia delle diverse simulazioni effettuate. Si noti che sarebbe stato più semplice se il client avesse memorizzato autonomamente le diverse simulazioni effettuate dall’utente. Tuttavia, manteniamo l’idea di rinviare il token per fornire un nuovo esempio di gestione della sessione. La prima riga della risposta viene trattata a parte. Infatti, deve avere la forma HTTP/versione 200 OK per indicare che il URL richiesto esiste effettivamente. Se quindi non ha questa forma, si deduce che l’utente abbia richiesto un URL errato e glielo si comunica.
         // il modello ricercato nelle intestazioni HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // il modello di una risposta corretta
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // si legge la prima riga della risposta
            réponse=IN.readLine();
             // si confronta la riga HTTP con il modello della risposta corretta
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // si riscontra un problema con URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(risultato)

             // si legge la risposta fino alla fine delle intestazioni cercando l'eventuale cookie
            while((réponse=IN.readLine())!=null){
                 // riga vuota?
                if(réponse.equals("")) break;
                // riga HTTP non vuota
                 // se non si dispone del token di sessione, lo si cerca
                if (JSESSIONID.equals("")){
                    // si confronta la riga HTTP con il modello del cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // è stato trovato il cookie del token
                        JSESSIONID=résultat.group(1);
                    }//if(risultato)
                }//if(JSESSIONID)
            }//while
  • una volta elaborate le intestazioni HTTP, si passa alla parte HTML della risposta
             // fine delle intestazioni HTTP - si passa al codice HTML
             // per recuperare le simulazioni
            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 viene visualizzato in una finestra di messaggio. Se l'elenco non è vuoto, viene visualizzato nell'elenco a discesa dell'interfaccia grafica.
  • 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 rilevato il messaggio di errore, questo viene visualizzato e la procedura termina. Se viene individuato il risultato di una simulazione, questo viene aggiunto all’elenco delle simulazioni. Al termine della procedura, tale elenco viene restituito come risultato.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // il modello di una riga della tabella delle simulazioni
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // il modello di una riga dell’elenco degli errori
        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 il URL del servizio di simulazione del calcolo delle imposte e successivamente l'applicazione si collegava a tale URL. In questo caso, 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, nel campo txtUrlServiceImpots verrà inserito un parametro di inizializzazione dell’applet; tale campo non sarà modificabile dall’utente. Si otterrà così 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 urlServiceImpots che corrisponde al URL del servizio di simulazione del calcolo delle imposte. Questo URL è relativo e misurato rispetto all'URL del documento HTML simulations.htm. Pertanto, se un browser recupera questo documento con l'http://localhost:8080/impots/simulations.htm URL, il URL del servizio di simulazione sarà http://localhost:8080/impots/simulations.. Se questo URL fosse stato http://stahe:8080/impots/simulations.htm, il codice URL del servizio di simulazione sarebbe stato http://stahe:8080/impots/simulations.

L'applet appletImpots.java riprende integralmente il codice della precedente applicazione grafica autonoma, rispettando le regole di trasformazione di un'applicazione grafica in applet.

public class appletImpots extends JApplet {
  // i componenti della finestra
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //Costruire il frame
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // altre inizializzazioni
    moreInit();
  }

   // inizializzazione del modulo
  private void moreInit(){
         // si recupera il parametro urlServiceImpots
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // parametro mancante
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // fine
            return;
        }
         // si inserisce URL nel relativo campo
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // menu «Calcola» disabilitato
    mnuCalculer.setEnabled(false);
     // selettore «Bambini» – da 0 a 20 bambini
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Inizializza il componente
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
...............
  }

Quando un browser carica un'applet, esegue innanzitutto la sua procedura init. In essa, abbiamo recuperato il valore del parametro urlServiceImpots dell’applet, quindi abbiamo calcolato il nome completo del servizio di simulazione URL e inserito tale valore nel campo txtURLServiceImpots come se fosse stato digitato dall’utente. Fatto ciò, non vi è più alcuna differenza tra le due applicazioni. In particolare, il codice associato al menu Calculer è 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 sola simulazione e non conserva le precedenti.
  • versione 2: si aggiungono alcune funzionalità lato browser inserendo script JavaScript nel documento HTML caricato dal browser stesso. Il browser verifica la validità dei parametri del modulo.
  • versione 3: si consente al servizio di ricordare le diverse simulazioni effettuate da un client gestendo una sessione. L’interfaccia HTML viene modificata di conseguenza per visualizzarle.
  • versione 4: il client è ora un’applicazione grafica autonoma. Ciò ci consente di tornare alla scrittura di client web programmati.
  • versione 5: il client si trasforma in un’applet Java. Si ottiene così un’applicazione client-server interamente programmata in Java, sia sul lato server che sul lato client.

A questo punto, è possibile fare alcune osservazioni:

  • le versioni da 1 a 3 supportano browser che non dispongono di 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 nulla nelle versioni 2 e 3 (le opzioni «Cancella» e «Calcola» non funzioneranno). Potrebbe essere interessante prevedere 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 dotato di una macchina virtuale Java 2.

Quando si scrive un servizio Web, è necessario chiedersi a quali tipi di client ci si rivolge. Se si vuole raggiungere il maggior numero possibile di client, si scriverà un'applicazione che invii ai browser solo HTML (senza JavaScript né applet). Se si lavora all’interno di una intranet e si ha il controllo sulla configurazione dei computer che la compongono, è possibile permettersi di essere più esigenti a livello di client e la precedente versione 5 può quindi essere accettabile.

Le versioni 4 e 5 sono client web che recuperano le informazioni di cui hanno bisogno all’interno del flusso HTML inviato dal server. Molto spesso non si ha il controllo su questo flusso. È il caso in cui si sia scritto un client per un servizio web esistente sulla rete e gestito da qualcun altro. Facciamo un esempio. Supponiamo che il nostro servizio di simulazioni per il calcolo delle imposte sia stato sviluppato da una 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:

         // il modello di una riga della tabella delle simulazioni
        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 nella forma:

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 grava sui client web delle applicazioni di cui non si ha il controllo diretto. XML può fornire una soluzione a questo problema:

  • invece di generare HTML, il servizio di simulazione genererà XML. Nel nostro esempio, 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 l'aspetto visivo da attribuire a tale 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 e non il XML. Grazie al foglio di stile, i browser visualizzeranno la nuova forma visiva e i client web programmati non dovranno essere modificati. Potrebbero quindi essere scritte nuove versioni del nostro servizio di simulazioni:

  • 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 utilizza la risposta XML del server
  • versione 8: il client è un’applet Java che utilizza la risposta XML del server