5. De applicatie IMPOTS
5.1. Inleiding
We nemen hier de applicatie IMPOTS weer op, die herhaaldelijk wordt gebruikt in het Java-werkboek van dezelfde auteur. Laten we de opgave nog eens in herinnering brengen. Het gaat erom een applicatie te schrijven waarmee de belasting van een belastingplichtige kan worden berekend. We gaan uit van het vereenvoudigde geval van een belastingplichtige die alleen zijn salaris hoeft aan te geven:
- we berekenen het aantal aandelen van de werknemer nbParts=nbEnfants/2 +1 als hij ongehuwd is, nbEnfants/2+2 als hij getrouwd is, waarbij nbEnfants het aantal kinderen is.
- als hij ten minste drie kinderen heeft, krijgt hij een half extra aandeel
- zijn belastbaar inkomen wordt berekend als R = 0,72 * S, waarbij S zijn jaarsalaris is
- we berekenen zijn gezinscoëfficiënt QF = R / nbParts
- zijn belasting I wordt berekend. Laten we de volgende tabel bekijken:
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 |
127860 | 0,45 | 16754,5 |
151250 | 0,50 | 23147,5 |
172040 | 0,55 | 30710 |
195000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Elke regel heeft 3 velden. Om belasting I te berekenen, zoeken we de eerste regel waar QF <= veld1. Als QF bijvoorbeeld 23000 is, vinden we de regel
Belasting I is dan gelijk aan 0,15*R - 2072,5*nbParts. Als QF zodanig is dat de relatie QF<=veld1 nooit geldt, dan worden de coëfficiënten van de laatste regel gebruikt. In dit geval:
wat de belasting I = 0,65*R - 49062*nbParts oplevert.
De gegevens die de verschillende belastingschijven definiëren, bevinden zich in een ODBC-MySQL-database. MySQL is een SGBD uit het publieke domein dat op verschillende platforms, waaronder Windows en Linux, kan worden gebruikt. Met dit SGBD is een database dbimpots aangemaakt met daarin één enkele tabel genaamd impots. De toegang tot de database wordt beheerd via een gebruikersnaam en wachtwoord, in dit geval admimpots/mdpimpots. De volgende schermafbeelding laat zien hoe de database dbimpots met MySQL kan worden gebruikt:
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
De database dbimpots wordt als volgt omgezet naar de gegevensbron ODBC:
- de 32-bits gegevensbronbeheerder ODBC wordt gestart

- gebruik de knop [Add] om een nieuwe gegevensbron ODBC toe te voegen

- we selecteren de driver MySQL en voeren [Terminer] uit
54321

- Het stuurprogramma MySQL vraagt om een aantal gegevens:
1 | de naam DSN die aan de gegevensbron ODBC moet worden gegeven – dit kan willekeurig zijn |
2 | de machine waarop SGBD en MySQL worden uitgevoerd – in dit geval localhost. Het is interessant om op te merken dat de database een externe database zou kunnen zijn. Lokale applicaties die de gegevensbron ODBC gebruiken, zouden hier niets van merken. Dit zou met name het geval zijn voor onze Java-applicatie. |
3 | de te gebruiken database MySQL. MySQL is een SGBD die relationele databases beheert; dit zijn verzamelingen van tabellen die onderling via relaties met elkaar zijn verbonden. Hier wordt de naam van de beheerde database opgegeven. |
4 | de naam van een gebruiker die toegangsrechten heeft tot deze database |
5 | zijn wachtwoord |
Er zijn twee klassen gedefinieerd om de belasting te berekenen: impots en impotsJDBC. Er wordt een instantie van de klasse impots aangemaakt met de gegevens van de belastingschijven die als parameters in arrays worden doorgegeven:
// aanmaken van een belastingklasse
public class impots{
// de gegevens die nodig zijn voor de berekening van de belasting
// zijn afkomstig van een externe bron
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// lege constructor
protected impots(){}
// constructor
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// er wordt gecontroleerd of de 3 tabellen dezelfde grootte hebben
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+")");
// het klopt
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//fabrikant
// berekening van de belasting
public long calculer(boolean marié, int nbEnfants, int salaire){
// berekening van het aantal aandelen
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// berekening belastbaar inkomen & gezinsquotiënt
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// belastingberekening
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// resultaat weergeven
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//berekenen
}//klasse
De klasse impotsJDBC is afgeleid van de vorige belastingklasse. Er wordt een instantie van de klasse impotsJDBC aangemaakt met de gegevens van de belastingschijven die in een database zijn opgeslagen. De informatie die nodig is om toegang te krijgen tot deze database wordt als parameters doorgegeven aan de constructor:
// geïmporteerde pakketten
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// toevoeging van een constructor waarmee kan worden geconstrueerd
// de tabellen ‘limites’, ‘coeffr’ en ‘coeffn’ op basis van de tabel
// belastingen uit een database
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: naam DSN van de database
// userIMPOTS, mdpIMPOTS: gebruikersnaam/wachtwoord voor toegang tot de database
// de gegevenstabellen
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// verbinding met de database
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// aanmaken van een Statement-object
Statement S=connect.createStatement();
// SELECT-query
String select="select limites, coeffr, coeffn from impots";
// uitvoering van de query
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// de huidige rij verwerken
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// volgende rij
// bronnen sluiten
RS.close();
S.close();
connect.close();
// gegevensoverdracht naar begrensde tabellen
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
}//constructor
}//klasse
Zodra een instantie van de klasse impotsJDBC is aangemaakt, kan de methode calculer herhaaldelijk worden aangeroepen om de belasting te berekenen:
De drie benodigde gegevens kunnen op verschillende manieren worden verkregen. Het voordeel van de klasse impotsJDBC is dat men zich alleen maar hoeft te bezighouden met het verkrijgen van deze gegevens. Zodra de drie gegevens (burgerlijke staat, aantal kinderen, jaarsalaris) zijn verkregen, levert het aanroepen van de methode calculer van de klasse impotsJDBC de te betalen belasting op.
5.2. Versie 1
We gaan uit van een webapplicatie die een interface HTML aan een gebruiker zou presenteren om de drie parameters te verkrijgen die nodig zijn voor de berekening van de belasting:
- de burgerlijke staat (gehuwd of ongehuwd)
- het aantal kinderen
- het jaarsalaris

Het formulier wordt weergegeven via de volgende pagina: JSP:
<%@ page import="java.util.*" %>
<%
// we halen de attributen op die door de hoofd-servlet zijn doorgegeven
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(){
// het formulier wordt gewist
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//met
}//wissen
</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>
<%
// zijn er fouten
if(erreurs!=null){
// fouten weergeven
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>
De pagina JSP geeft alleen informatie weer die door de hoofd-servlet van de applicatie wordt doorgegeven:
// de door de hoofd-servlet doorgegeven attributen worden opgehaald
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");
attributen van de keuzerondjes 'ja' en 'nee' – hebben als mogelijke waarden "checked" of "" om het betreffende keuzerondje al dan niet te activeren | |
het aantal kinderen van de belastingplichtige | |
zijn jaarsalaris | |
het bedrag aan te betalen belasting | |
een eventuele lijst met fouten als fouten!=null |
De pagina die naar de klant wordt verzonden, bevat een JavaScript-script met een functie effacer die is gekoppeld aan de knop "Effacer", die tot taak heeft het formulier terug te zetten naar de oorspronkelijke staat: knop niet aangevinkt, invoervelden leeg. Opgemerkt moet worden dat dit resultaat niet bereikt kon worden met een knop HTML van het type "reset". Wanneer dit type knop wordt gebruikt, zet de browser het formulier namelijk terug in de toestand waarin het is ontvangen. In onze applicatie ontvangt de browser echter formulieren die mogelijk niet leeg zijn.
De hoofd-servlet van de applicatie heet main.java en de code ervan is als volgt:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class main extends HttpServlet{
// instantievariabelen
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{
// Is de initialisatie goed verlopen?
if(msgErreur!=null){
// we geven het stokje door aan de foutpagina
request.setAttribute("msgErreur",msgErreur);
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// attributen van de aanvraag
String chkoui=null;
String chknon=null;
String txtImpots=null;
// de parameters van de aanvraag worden opgehaald
String optMarie=request.getParameter("optMarie"); // burgerlijke staat
String txtEnfants=request.getParameter("txtEnfants"); // aantal kinderen
if(txtEnfants==null) txtEnfants="";
String txtSalaire=request.getParameter("txtSalaire"); // jaarsalaris
if(txtSalaire==null) txtSalaire="";
// zijn alle verwachte parameters aanwezig
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// er ontbreken parameters
request.setAttribute("chkoui","");
request.setAttribute("chknon","checked");
request.setAttribute("txtEnfants","");
request.setAttribute("txtSalaire","");
request.setAttribute("txtImpots","");
// we geven het door aan de URL voor de weergave van de belasting
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
// we hebben alle parameters – we controleren ze
ArrayList erreurs=new ArrayList();
// burgerlijke staat
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// fout
erreurs.add("Etat marital incorrect");
optMarie="non";
}
// aantal kinderen
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// fout
erreurs.add("Nombre d'enfants incorrect");
}
// salaris
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// fout
erreurs.add("Salaire incorrect");
}
// als er fouten zijn, worden deze als attribuut van de query doorgegeven
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
txtImpots="";
}else{
// de te betalen belasting kan worden berekend
try{
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
}catch(Exception ex){}
}
// de overige attributen van de aanvraag
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 geven het stokje door aan de URL voor de weergave van de belasting
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// we halen de initialisatieparameters op
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;
}
// er wordt een instantie van impotsJDBC aangemaakt
try{
impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){
msgErreur=ex.getMessage();
}
}
}
- De init-methode van de servlet doet twee dingen:
- ze haalt de initialisatieparameters op. Hiermee kan de servlet verbinding maken met de database ODBC, die de gegevens van de verschillende belastingtranche (DSNimpots, admimpots, mdpimpots) bevat, en de URL van de pagina's die bij de applicatie horen: urlAffichageImpots voor het formulier, urlErreur voor de foutpagina.
- maakt het een instantie aan van de klasse impotsJDBC
In beide gevallen worden mogelijke fouten afgehandeld en wordt de foutmelding opgeslagen in de variabele msgErreur.
- De methode doGET
- controleert eerst of de servlet correct is geïnitialiseerd. Als dat niet het geval is, wordt de foutpagina weergegeven
- en haalt de verwachte parameters van het belastingformulier op: optMarie, txtEnfants, txtSalaire. Als er een ontbreekt (==null), wordt een leeg belastingformulier verzonden. Je zou kunnen denken dat het controleren van de geldigheid van de parameter optMarie overbodig is. Deze parameter is de waarde van een keuzerondje en kan hier alleen een van de waarden „oui“ of „non“ aannemen. Maar daarbij wordt vergeten dat niets een programma ervan weerhoudt om de servlet rechtstreeks te benaderen door de gewenste parameters te verzenden. Men kan er nooit zeker van zijn dat er daadwerkelijk verbinding is met een browser. Het over het hoofd zien van dit punt kan leiden tot beveiligingslekken in de applicatie en het komt overigens vaak voor dat deze zelfs in commerciële applicaties worden aangetroffen.
- De geldigheid van elk van de drie opgehaalde parameters wordt gecontroleerd. Elke gevonden fout wordt toegevoegd aan een foutenlijst (ArrayList fouten). Als er geen fouten zijn, wordt het belastingbedrag berekend; anders gebeurt dit niet.
- De informatie die nodig is om de pagina weer te geven, wordt als attributen aan de verzoek-URL toegevoegd, waarna het belastingformulier wordt weergegeven
De foutpagina JSP ziet er als volgt uit:
<%
// jspService
// er is een fout opgetreden
String msgErreur= (String)request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- begin van de pagina HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impots</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
De webapplicatie heet 'impots' en is in het Tomcat-bestand server.xml als volgt geconfigureerd:
De applicatiemap bevat de volgende mappen en bestanden:




Het configuratiebestand web.xml van de applicatie 'impots' is als volgt:
<?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>
De hoofd-servlet heet main en heeft de alias /main. Deze is dus toegankelijk via URL en http://localhost:8080/impots/main.
Hier volgen enkele toepassingsvoorbeelden:
Om correct te kunnen opstarten, moet de servlet toegang hebben tot de database mysql-dbimpots. Hieronder ziet u de pagina die wordt weergegeven als bijvoorbeeld de server MySQL niet is gestart en de database mysql-dbimpots daardoor niet toegankelijk is:

De pagina die wordt weergegeven bij een onjuiste invoer is de volgende:

Als de gegevens correct zijn ingevoerd, wordt de belasting berekend:

5.3. Versie 2
In het vorige voorbeeld wordt de geldigheid van de parameters txtEnfants en txtSalaire van het formulier door de server gecontroleerd. Hier stellen we voor om dit te controleren met een JavaScript-script dat in de formulierpagina is opgenomen. De browser voert dan de controle van de parameters uit. De server wordt dus alleen benaderd als deze geldig zijn. Zo besparen we "bandbreedte". De weergavepagina JSP ziet er dan als volgt uit:
<%@ page import="java.util.*" %>
............
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
.......
}//wissen
function calculer(){
// controle van de instellingen voordat ze naar de server worden verzonden
with(document.frmImpots){
//aantal kinderen
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// het model is niet gecontroleerd
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salaris
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// het model is niet gecontroleerd
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//als
// alles in orde – het formulier wordt naar de server verzonden
submit();
}//met
}//berekenen
</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>
Let op de volgende wijzigingen:
- de knop Calculer is niet langer van het type submit, maar van het type button, gekoppeld aan een functie met de naam calculer. Deze functie voert de analyse uit van de velden txtEnfants en txTsalaire. Als deze correct zijn, worden de waarden van het formulier naar de server verzonden (submit); anders wordt er een foutmelding weergegeven.
Hieronder ziet u een voorbeeld van de weergave bij een fout:

5.4. Versie 3
We passen de applicatie enigszins aan om het concept van een sessie te introduceren. We beschouwen de applicatie nu als een simulatieprogramma voor de berekening van belastingen. Een gebruiker kan dan verschillende „configuraties” van belastingplichtigen simuleren en zien hoeveel belasting er voor elk van deze configuraties zou moeten worden betaald. De onderstaande pagina WEb geeft een voorbeeld van wat er zou kunnen worden verkregen:

De hoofd-servlet is aangepast en heet nu ‘simulations’. Deze is als volgt geconfigureerd binnen de applicatie ‘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>
De hoofd-servlet heet ‘simulations’ en is gebaseerd op het klassebestand simulations.class. De servlet heeft de alias /simulations, waardoor deze toegankelijk is via de URL en http://localhost:8080/impots/simulations. Wat de toegang tot de database betreft, heeft deze servlet dezelfde initialisatieparameters als de eerder besproken hoofd-servlet. Er verschijnt een nieuwe parameter, urlSimulationsImpots, die overeenkomt met de URL van de simulatiepagina JSP (die zojuist iets eerder is besproken).
De servlet simulations.java lijkt sterk op de servlet main.java. Ze verschilt ervan op de volgende belangrijke punten:
- de hoofd-servlet berekent een waarde txtImpots op basis van de parameters optmarie, txtEnfants en txtSalaire en geeft deze waarde door aan de weergavepagina JSP
- de servlet ‘simulations’ berekent op dezelfde manier de waarde txtImpots, slaat de parameters (optMarie, txtEnfants, txtsalaire, txtImpots) op in een lijst met de naam ‘simulaties’. Deze lijst wordt als parameter doorgegeven aan de weergavepagina JSP. Om ervoor te zorgen dat deze lijst alle door de gebruiker uitgevoerde simulaties bevat, wordt deze opgeslagen als attribuut van de huidige sessie.
De servlet ‘simulaties’ ziet er als volgt uit (alleen de coderegels die afwijken van die in de vorige toepassing zijn behouden):
import java.io.*;
.......
public class simulations extends HttpServlet{
// instantievariabelen
String msgErreur=null;
String urlSimulationImpots=null;
String urlErreur=null;
...........
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
...........
// de eerdere simulaties van de sessie worden opgehaald
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// de simulaties worden in de huidige query geplaatst
request.setAttribute("simulations",simulations);
// andere attributen van de query
...........
// zijn alle verwachte parameters aanwezig
if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
// we geven het stokje door aan de URL voor het weergeven van de simulaties van belastingberekeningen
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
// we hebben alle parameters – we controleren ze
...........
// als er fouten zijn, worden deze doorgegeven als attributen van de aanvraag
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
}else{
try{
// de te betalen belasting kan worden berekend
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// we voegen het huidige resultaat toe aan de eerdere simulaties
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// de nieuwe simulatiewaarde wordt teruggeschreven naar de sessie
session.setAttribute("simulations",simulations);
}catch(Exception ex){}
}
// andere attributen van de query
..........
// er wordt doorgestuurd naar de URL voor het weergeven van de simulaties
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// de initialisatieparameters worden opgehaald
ServletConfig config=getServletConfig();
urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parameters in orde?
.........................
}
}
De weergavepagina JSP van simulationsImpots.jsp is als volgt geworden (alleen de code die verschilt van de weergavepagina JSP van de vorige toepassing is behouden).
<%@ page import="java.util.*" %>
<%
// de door de hoofd-servlet doorgegeven attributen worden opgehaald
...........
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>
<%
// zijn er fouten?
if(erreurs!=null){
..................
}else if(simulations.size()!=0){
// resultaten van de simulaties
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. Versie 4
We gaan nu een zelfstandige applicatie maken die als webclient voor de vorige applicatie /impots/simulations zal fungeren. Deze applicatie krijgt de volgende grafische interface:
![]() |
nr. | type | naam | rol |
1 | JTextField | txtUrlServiceImpots | URL van de dienst voor simulatie van de belastingberekening |
2 | JRadioButton | rdOui | aangevinkt indien gehuwd |
3 | JRadioButton | rdNon | aangevinkt indien ongehuwd |
4 | JSpinner | spinEnfants | aantal kinderen van de belastingplichtige (minimum=0, maximum=20, stapgrootte=1) |
5 | JTextField | txtSalaire | jaarsalaris van de belastingplichtige in F |
6 | JList in JScrollPane | lstSimulations | lijst met simulaties |
Het menu Belastingen bestaat uit de volgende opties:
optie hoofdoptie | secundaire secundaire | naam | rol |
Belastingen | |||
Berekenen | mnuCalculer | berekent de te betalen belasting wanneer alle gegevens die nodig zijn voor de berekening aanwezig en correct zijn | |
Wissen | mnuEffacer | zet het formulier terug in de oorspronkelijke staat | |
Afsluiten | mnuQuitter | sluit de applicatie af |
Werkingsregels
- de menuoptie Calculer blijft uitgeschakeld als een van de velden 1 of 5 leeg is
- er wordt een syntactisch onjuiste URL in veld 1 gedetecteerd

- er wordt een onjuist salaris gedetecteerd

- er wordt een fout bij het verbinden met de server gemeld (in voorbeeld 1 hieronder is de poort onjuist – in voorbeeld 2 bestaat de gevraagde URL niet – in voorbeeld 3 was de database MySQL niet gestart)



- Als alles correct is, worden de simulaties weergegeven

Bij het schrijven van een geprogrammeerde webclient is het noodzakelijk om precies te weten wat de server verstuurt als antwoord op de verschillende mogelijke verzoeken van een client. De server verstuurt een reeks regels HTML die zowel nuttige informatie bevatten als informatie die uitsluitend bedoeld is voor de opmaak HTML. Reguliere expressies in Java kunnen ons helpen de nuttige informatie te vinden in de stroom van regels die door de server worden verzonden. Hiervoor moeten we het exacte formaat van de verschillende antwoorden van de server kennen. Hier gebruiken we de webclient die we al eerder hebben gezien, waarmee het antwoord van een server op een verzoek van een URL op het scherm kan worden weergegeven. De aangevraagde URL is die van onze dienst voor belastingberekeningssimulaties http://localhost:8080/impots/simulations, waaraan we parameters kunnen doorgeven in de vorm http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&...
Laten we de URL opvragen terwijl de basis MySQL, waarmee een object van het type impots kan worden aangemaakt, niet is gestart:
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 van de 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>
Om de foutmelding op te vangen, moet de webclient in het antwoord van de webserver zoeken naar de regel met de tekst "Toepassing niet beschikbaar". Laten we nu de database MySQL starten en dezelfde URL opvragen door de waarden door te geven die deze verwacht (de database accepteert deze waarden zowel in de vorm van een GET als een POST – zie de Java-code van de 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(){
...................................................
}//wissen
function calculer(){
...................................................
}//berekenen
</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>
Hier zien we het volledige document HTML dat door de server is verzonden. De resultaten van de verschillende simulaties staan in de enige tabel in dit document. De reguliere expressie waarmee we de relevante informatie uit het document kunnen halen, zou als volgt kunnen zijn:
waarbij de vier uitdrukkingen tussen haakjes de vier op te halen gegevens vertegenwoordigen.
We hebben nu de richtlijnen voor wat er moet gebeuren wanneer de gebruiker vanuit de vorige grafische interface vraagt om de belasting te berekenen:
- controleer of alle gegevens in de interface geldig zijn en meld eventueel fouten.
- verbinding maken met de URL die in veld 1 is aangegeven. Hiervoor volgen we het model van de generieke webclient dat al is gepresenteerd en bestudeerd
- in de responsstroom van de server, gebruik maken van reguliere expressies om ofwel:
- de foutmelding te vinden, indien er een is
- de simulatieresultaten te vinden als er geen fout is
De code voor het menu calculer is als volgt:
void mnuCalculer_actionPerformed(ActionEvent e) {
// belasting berekenen
// controle 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){
// foutmelding
JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus op foutief veld
txtURLServiceImpots.requestFocus();
// terug naar de interface
return;
}
// salariscontrole
int salaire=0;
try{
salaire=Integer.parseInt(txtSalaire.getText().trim());
if(salaire<0) throw new Exception();
}catch (Exception ex){
// foutmelding
JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus op foutief veld
txtSalaire.requestFocus();
// terug naar de interface
return;
}
// aantal kinderen
Integer nbEnfants=(Integer)spinEnfants.getValue();
try{
// de belasting wordt berekend
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// de fout wordt weergegeven
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{
// belastingberekening
// urlImpots: URL van de belastingdienst
// getrouwd: true indien getrouwd, false anders
// nbEnfants: aantal kinderen
// salaris: jaarsalaris
// uit urlImpots worden de gegevens gehaald die nodig zijn om verbinding te maken met de belastingdienstserver
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();
// lokale gegevens
Socket client=null; // de klant
BufferedReader IN=null; // de leesstroom van de klant
PrintWriter OUT=null; // de schrijfstream van de client
String réponse=null; // antwoord van de server
// het gezochte patroon in de headers HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// het patroon van een correct antwoord
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// het resultaat van de vergelijking met het patroon
Matcher résultat=null;
try{
// er wordt verbinding gemaakt met de server
client=new Socket(host,port);
// de in- en uitgangsstromen van de client worden aangemaakt TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// URL wordt opgevraagd - verzending van de headers 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("");
// de eerste regel van het antwoord wordt gelezen
réponse=IN.readLine();
// de regel HTTP wordt vergeleken met het sjabloon van het juiste antwoord
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// er is een probleem met URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(resultaat)
// we lezen het antwoord tot het einde van de headers en zoeken naar een eventuele cookie
while((réponse=IN.readLine())!=null){
// lege regel?
if(réponse.equals("")) break;
// regel HTTP is niet leeg
// als het sessietoken ontbreekt, wordt ernaar gezocht
if (JSESSIONID.equals("")){
// vergelijk de regel HTTP met het cookiemodel
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// het token-cookie is gevonden
JSESSIONID=résultat.group(1);
}//if(resultaat)
}//if(JSESSIONID)
}//terwijl
// dat was het voor de headers HTTP - we gaan verder met de code HTML
// om de simulaties op te halen
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// het is klaar
client.close();
}catch (Exception ex){
throw new Exception(ex.getMessage());
}
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// het voorbeeld van een rij uit de tabel met simulaties
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// het voorbeeld van een rij uit de foutenlijst
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
// het resultaat van de vergelijking met het model
Matcher résultat=null;
// de simulaties
ArrayList listeSimulations=new ArrayList();
// alle regels tot het einde worden gelezen
String ligne=null;
boolean simulationRéussie=false;
while((ligne=IN.readLine())!=null){
// vervolg
// de regel wordt vergeleken met het foutmodel als het gedeelte met simulaties nog niet is bereikt
if(! simulationRéussie){
résultat=ptnErreur.matcher(ligne);
if(résultat.find()){
// foutmelding
JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
// klaar
return listeSimulations;
}//if
}//als
// de rij wordt vergeleken met het simulatiemodel
résultat=ptnSimulation.matcher(ligne);
if(résultat.find()){
// er is een rij in de tabel gevonden
listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
":"+résultat.group(4));
// de simulatie is geslaagd
simulationRéussie=true;
}//if
}//terwijl
// einde
return listeSimulations;
}
Laten we deze code even toelichten:
- de procedure mnuCalculer_actionPerformed controleert of de gegevens van de interface geldig zijn. Als dat niet het geval is, wordt er een foutmelding weergegeven en wordt de procedure beëindigd. Als de gegevens wel geldig zijn, wordt de procedure calculerImpots uitgevoerd.
- De procedure calculerImpots begint met het samenstellen van de URL die zij moet opvragen
// uit urlImpots wordt de informatie gehaald die nodig is om verbinding te maken met de belastingserver
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();
........................
- en maakt vervolgens verbinding met deze URL door de juiste HTTP-headers te verzenden:
// er wordt verbinding gemaakt met de server
client=new Socket(host,port);
// de in- en uitgaande stromen van de klant worden aangemaakt TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// de URL wordt opgevraagd - verzending van de kopteksten 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("");
- Zodra het verzoek is verzonden, wacht onze webclient op het antwoord. Hij ontvangt eerst de headers HTTP van het antwoord van de server. Hij doorzoekt deze headers om het sessietoken te vinden. Dit token moet hij namelijk terugsturen naar de server, zodat deze de verschillende uitgevoerde simulaties kan bijhouden. Hierbij moet worden opgemerkt dat het eenvoudiger zou zijn geweest als de client zelf de verschillende door de gebruiker uitgevoerde simulaties had onthouden. Toch houden we vast aan het idee om het token terug te sturen om een nieuw voorbeeld van sessiebeheer te geven. De eerste regel van het antwoord wordt apart verwerkt. Deze moet namelijk de vorm HTTP/versie 200 OK hebben om aan te geven dat de aangevraagde URL inderdaad bestaat. Als de regel dus niet deze vorm heeft, wordt geconcludeerd dat de gebruiker een onjuiste URL heeft aangevraagd en wordt hem dit meegedeeld.
// het gezochte patroon in de headers HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// het patroon van een correct antwoord
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........
// de eerste regel van het antwoord wordt gelezen
réponse=IN.readLine();
// de regel HTTP wordt vergeleken met het patroon van het juiste antwoord
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// er is een probleem met URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(resultaat)
// we lezen het antwoord tot het einde van de headers en zoeken naar een eventuele cookie
while((réponse=IN.readLine())!=null){
// lege regel?
if(réponse.equals("")) break;
// regel HTTP is niet leeg
// als het sessietoken ontbreekt, wordt ernaar gezocht
if (JSESSIONID.equals("")){
// vergelijk de regel HTTP met het cookiemodel
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// het token-cookie is gevonden
JSESSIONID=résultat.group(1);
}//if(resultaat)
}//if(JSESSIONID)
}//terwijl
- nadat de kopteksten HTTP zijn verwerkt, gaan we verder met het gedeelte HTML van het antwoord
// dat was het voor de headers HTTP - we gaan verder met de code HTML
// om de simulaties op te halen
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
- De procedure getSimulations geeft de lijst met simulaties weer, indien deze bestaan. Deze lijst is leeg als de server een foutmelding terugstuurt. In dat geval wordt de foutmelding in een berichtvenster weergegeven. Als de lijst niet leeg is, wordt deze weergegeven in de vervolgkeuzelijst van de grafische interface.
- De procedure getSimulations vergelijkt elke regel van het antwoord van HTML met de reguliere expressie die de foutmelding weergeeft (Toepassing niet beschikbaar...) en met de reguliere expressie die een simulatie weergeeft. Als de foutmelding wordt aangetroffen, wordt deze weergegeven en wordt de procedure beëindigd. Als het resultaat van een simulatie wordt gevonden, wordt dit toegevoegd aan de lijst met simulaties. Aan het einde van de procedure wordt deze lijst als resultaat geretourneerd.
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// het sjabloon van een rij in de simulatietabel
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// het sjabloon voor een regel uit de foutenlijst
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
5.6. Versie 5
Hier zetten we de vorige zelfstandige grafische applicatie om in een Java-applet. De grafische interface is enigszins anders. Bij de zelfstandige applicatie voerde de gebruiker zelf de URL van de dienst voor het simuleren van belastingberekeningen in, waarna de applicatie verbinding maakte met deze URL. Hier is de clienttoepassing een browser en zal de gebruiker de URL opvragen van het document HTML dat de applet bevat. We moeten nu in gedachten houden dat een Java-applet alleen een netwerkverbinding kan openen met de server waarvan deze is gedownload. De URL van de simulatieservice bevindt zich dus op dezelfde server als het document HTML dat de applet bevat. In ons voorbeeld wordt een initialisatieparameter van de applet in het veld txtUrlServiceImpots geplaatst, een veld dat niet door de gebruiker kan worden bewerkt. We krijgen dan de volgende client:

Het document HTML dat de applet bevat, heet simulations.htm en ziet er als volgt uit:
<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>
De applet heeft een parameter urlServiceImpots, die overeenkomt met de URL van de dienst voor belastingberekeningssimulaties. Deze URL is relatief en wordt gemeten ten opzichte van de URL van het document HTML simulations.htm. Als een browser dit document ophaalt met de URL http://localhost:8080/impots/simulations.htm, dan is de URL van de simulatiedienst http://localhost:8080/impots/simulations.. Als deze URL http://stahe was geweest:8080/impots/simulations.htm, dan zou de URL van de simulatiedienst http://stahe:8080/impots/simulations zijn geweest.
De applet appletImpots.java neemt de code van de vorige zelfstandige grafische applicatie volledig over, met inachtneming van de regels voor het omzetten van een grafische applicatie naar een applet.
public class appletImpots extends JApplet {
// de onderdelen van het venster
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculer = new JMenuItem();
.............
//Het frame opbouwen
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// overige initialisaties
moreInit();
}
// het formulier initialiseren
private void moreInit(){
// de parameter urlServiceImpots wordt opgehaald
String urlServiceImpots=getParameter("urlServiceImpots");
if(urlServiceImpots==null){
// parameter ontbreekt
JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
// einde
return;
}
// we plaatsen de URL in het betreffende veld
String codeBase=""+getCodeBase();
if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase+urlServiceImpots);
else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
// menu 'Berekenen' uitgeschakeld
mnuCalculer.setEnabled(false);
// spinner ‘Kinderen’ – tussen 0 en 20 kinderen
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
spinEnfants.setBounds(new Rectangle(130,140,50,27));
contentPane.add(spinEnfants);
}//moreInit
//Component initialiseren
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
...............
}
Wanneer een browser een applet laadt, voert hij eerst de init-procedure uit. Daarin hebben we de waarde van de parameter urlServiceImpots van de applet opgehaald, vervolgens de volledige naam van de URL van de simulatieservice berekend en deze waarde in het veld txtURLServiceImpots geplaatst alsof de gebruiker deze zelf had ingevoerd. Nu dit is gebeurd, is er geen verschil meer tussen de twee applicaties. Met name is de code die bij het menu Calculer hoort identiek. Hier volgt een voorbeeld van de uitvoering:

5.7. Conclusie
We hebben verschillende versies van onze client-server-applicatie voor belastingberekening laten zien:
- versie 1: de dienst wordt verzorgd door een reeks servlets en JSP-pagina’s; de client is een browser. Deze voert slechts één simulatie uit en onthoudt eerdere simulaties niet.
- versie 2: er worden enkele mogelijkheden aan de browserzijde toegevoegd door JavaScript-scripts op te nemen in het document HTML dat door de browser wordt geladen. Deze controleert de geldigheid van de formulierparameters.
- versie 3: de service kan de verschillende simulaties die door een client zijn uitgevoerd onthouden door een sessie te beheren. De interface HTML is dienovereenkomstig aangepast om deze weer te geven.
- versie 4: de client is nu een zelfstandige grafische applicatie. Hierdoor kunnen we terugkeren naar het schrijven van geprogrammeerde webclients.
- versie 5: de client wordt een Java-applet. We beschikken nu over een client-servertoepassing die volledig in Java is geprogrammeerd, zowel aan de serverzijde als aan de clientzijde.
Op dit punt kunnen we enkele opmerkingen maken:
- versies 1 tot en met 3 ondersteunen browsers die alleen JavaScript-scripts kunnen uitvoeren. Opgemerkt moet worden dat een gebruiker altijd de mogelijkheid heeft om de uitvoering van deze scripts te blokkeren. De applicatie zal dan slechts gedeeltelijk functioneren in versie 1 (de optie ‘Wissen’ werkt niet) en helemaal niet in de versies 2 en 3 (de opties ‘Wissen’ en ‘Berekenen’ werken niet). Het zou interessant kunnen zijn om een versie van de dienst te ontwikkelen die geen gebruik maakt van JavaScript-scripts.
- Voor versie 4 moet de clientcomputer beschikken over een Java 2-virtuele machine.
- Voor versie 5 moet de client een browser hebben met een Java 2-virtuele machine.
Bij het schrijven van een webservice moet men zich afvragen op welke soorten clients men zich richt. Als men een zo groot mogelijk aantal clients wil bereiken, schrijft men een applicatie die alleen HTML naar de browsers stuurt (geen JavaScript of applets). Als men binnen een intranet werkt en controle heeft over de configuratie van de computers daarbinnen, kan men zich toestaan om hogere eisen te stellen aan de client en is de vorige versie 5 dan aanvaardbaar.
De versies 4 en 5 zijn webclients die de informatie die ze nodig hebben, vinden in de door de server verzonden HTML-stream. Vaak hebben we geen controle over deze stream. Dit is het geval wanneer we een client hebben geschreven voor een bestaande webservice op het netwerk die door iemand anders wordt beheerd. Laten we een voorbeeld nemen. Stel dat onze dienst voor belastingberekeningen is geschreven door bedrijf X. Momenteel verstuurt de dienst de berekeningen in een tabel met de naam HTML en onze client maakt hiervan gebruik om ze op te halen. Hij vergelijkt daarbij elke regel van het antwoord van de server met de volgende reguliere expressie:
// het sjabloon voor een rij in de simulatietabel
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Stel nu dat de ontwikkelaar van de applicatie het uiterlijk van het antwoord wijzigt door de simulaties niet in een tabel te plaatsen, maar in een lijst in de vorm:
In dit geval moet onze webclient opnieuw worden geschreven. Dit is het voortdurende risico dat webclients van applicaties met zich meebrengen waarover men zelf geen controle heeft. XML kan een oplossing voor dit probleem bieden:
- in plaats van HTML te genereren, zal de simulatieservice XML genereren. In ons voorbeeld zou dat kunnen zijn
<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>
- aan dit antwoord zou een stylesheet kunnen worden gekoppeld dat browsers aangeeft hoe dit antwoord er visueel uit moet zien XML
- de geprogrammeerde webclients zouden dit stylesheet negeren en de informatie rechtstreeks uit de XML-stream van het antwoord ophalen
Als de ontwikkelaar van de dienst de visuele weergave van de resultaten wil aanpassen, wijzigt hij het stylesheet en niet de XML. Dankzij het stylesheet geven browsers de nieuwe visuele weergave weer en hoeven de geprogrammeerde webclients zelf niet te worden aangepast. Er kunnen dus nieuwe versies van onze simulatiedienst worden geschreven:
- versie 6: de dienst levert een XML-antwoord, vergezeld van een stylesheet voor browsers
- versie 7: de client is een zelfstandige grafische applicatie die gebruikmaakt van het antwoord XML van de server
- versie 8: de client is een Java-applet die gebruikmaakt van het antwoord XML van de server
