Skip to content

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

    24740        0.15        2072.5

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:

    0                0.65        49062

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

Image

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

Image

  • we selecteren de driver MySQL en voeren [Terminer] uit

54321

Image

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

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

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

Image

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");
chkoui, chknon
attributen van de keuzerondjes 'ja' en 'nee' – hebben als mogelijke waarden "checked" of "" om het betreffende keuzerondje al dan niet te activeren
txtEnfants
het aantal kinderen van de belastingplichtige
txtSalaire
zijn jaarsalaris
txtImpots
het bedrag aan te betalen belasting
erreurs
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:

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

De applicatiemap bevat de volgende mappen en bestanden:

Image

Image

Image

Image

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:

Image

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

Image

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

Image

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:

Image

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:

Image

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

Image

  • er wordt een onjuist salaris gedetecteerd

Image

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

Image

Image

Image

  • Als alles correct is, worden de simulaties weergegeven

Image

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&param2=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:

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

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:

Image

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:

Image

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:

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

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