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

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

- Seleziona il driver MySQL e fai clic su [Fine]
54321

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

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");
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 | |
il numero di figli del contribuente | |
il loro stipendio annuale | |
l'importo delle imposte dovute | |
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:
La directory dell'applicazione contiene le seguenti directory e file:




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:

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

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

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:

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:

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

- viene rilevato uno stipendio errato

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



- Se tutto è corretto, vengono visualizzate le simulazioni

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¶m2=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:
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:

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:

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