Skip to content

5. Die Steuerberechnungsanwendung

5.1. Einführung

Hier greifen wir die IMPOTS-Anwendung wieder auf, die im Java-Handout desselben Autors wiederholt verwendet wird. Lassen Sie uns das Problem noch einmal betrachten. Das Ziel ist es, eine Anwendung zu schreiben, die die Steuerschuld eines Steuerzahlers berechnet. Wir betrachten den vereinfachten Fall eines Steuerzahlers, der nur ein einziges Gehalt zu melden hat:

  • Wir berechnen die Anzahl der Steuerklassen des Arbeitnehmers als nbParts = nbEnfants / 2 + 1, wenn er unverheiratet ist, und als nbEnfants / 2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
  • Wenn er mindestens drei Kinder hat, erhält er einen zusätzlichen halben Anteil
  • Wir berechnen sein zu versteuerndes Einkommen R = 0,72 * S, wobei S sein Jahresgehalt ist
  • Wir berechnen den Familienkoeffizienten QF = R / nbParts
  • Wir berechnen ihre Steuer I. Betrachten Sie die folgende Tabelle:
12620,0
0
0
13.190
0,05
631
15.640
0,1
1.290,5
24.740
0,15
2.072,5
31.810
0,2
3.309,5
39.970
0,25
4.900
48.360
0,3
6.898,5
55.790
0,35
9.316,5
92.970
0,4
12.106
127.860
0,45
16.754,5
151.250
0,50
23.147,5
172.040
0,55
30.710
195.000
0,60
39.312
0
0,65
49062

Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, suchen Sie die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 23.000 ist, lautet die gefundene Zeile

    24740        0.15        2072.5

Steuer I ist dann gleich 0,15*R - 2072,5*nbParts. Wenn QF so ist, dass die Bedingung QF<=field1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:

    0                0.65        49062

was die Steuer I = 0,65*R - 49062*nbParts ergibt.

Die Daten, die die verschiedenen Steuerklassen definieren, werden in einer ODBC-MySQL-Datenbank gespeichert. MySQL ist ein Open-Source-DBMS, das auf verschiedenen Plattformen, darunter Windows und Linux, verwendet werden kann. Mit diesem DBMS wurde eine Datenbank namens dbimpots erstellt, die eine einzige Tabelle namens impots enthält. Der Zugriff auf die Datenbank wird durch einen Benutzernamen und ein Passwort gesteuert, in diesem Fall „admimpots“ und „mdpimpots“. Der folgende Screenshot zeigt, wie die Datenbank „dbimpots“ mit MySQL verwendet wird:


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

Die Datenbank „dbimpots“ wird wie folgt in eine ODBC-Datenquelle konvertiert:

  • Starten Sie den 32-Bit-ODBC-Datenquellen-Administrator

Image

  • Fügen Sie über die Schaltfläche [Hinzufügen] eine neue ODBC-Datenquelle hinzu

Image

  • Wählen Sie den MySQL-Treiber aus und klicken Sie auf [Fertigstellen]

54321

Image

  • Der MySQL-Treiber fordert bestimmte Informationen an:
1
den DSN-Namen, der der ODBC-Datenquelle zugewiesen werden soll – dieser kann beliebig sein
2
den Rechner, auf dem das MySQL-DBMS läuft – hier „localhost“. Es ist anzumerken, dass es sich bei der Datenbank um eine Remote-Datenbank handeln könnte. Lokale Anwendungen, die die ODBC-Datenquelle nutzen, würden davon nichts bemerken. Dies wäre insbesondere bei unserer Java-Anwendung der Fall.
3
die zu verwendende MySQL-Datenbank. MySQL ist ein DBMS, das relationale Datenbanken verwaltet, also Gruppen von Tabellen, die durch Beziehungen miteinander verknüpft sind. Hier geben wir den Namen der zu verwaltenden Datenbank an.
4
Der Name eines Benutzers mit Zugriffsrechten auf diese Datenbank
5
sein Passwort

Zur Berechnung der Steuer wurden zwei Klassen definiert: impots und impotsJDBC. Eine Instanz der Klasse impots wird unter Verwendung der Steuerklassen-Daten erstellt, die als Parameter in Arrays übergeben werden:

// creation of an impots class

public class impots{

  // data required for tax calculation
   // come from an external source

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

   // empty builder
  protected impots(){}

   // manufacturer
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // check that the 3 arrays have the same size
    boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
    if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
      LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
    // it's good
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//manufacturer

   // tAX CALCULATION
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // calculating the number of shares
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

Die Klasse impotsJDBC leitet sich von der vorherigen Klasse impots ab. Eine Instanz der Klasse impotsJDBC wird unter Verwendung von Steuerklassen-Daten erstellt, die in einer Datenbank gespeichert sind. Die für den Zugriff auf diese Datenbank erforderlichen Informationen werden als Parameter an den Konstruktor übergeben:

// imported packages
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // addition of a constructor for building
   // limit tables, coeffr, coeffn from table
   // database taxes
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

     // data tables
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connection to base
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creation of a Statement object
    Statement S=connect.createStatement();
    // select request
    String select="select limites, coeffr, coeffn from impots";
    // query execution
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // running line operation
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// next line
     // closing resources
    RS.close();
    S.close();
    connect.close();
     // data transfer to bounded arrays
    int n=aLimites.size();
    limites=new double[n];
    coeffR=new double[n];
    coeffN=new double[n];
    for(int i=0;i<n;i++){
      limites[i]=Double.parseDouble((String)aLimites.get(i));
      coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
      coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
    }//for
  }//manufacturer
}//class

Sobald eine Instanz der Klasse impotsJDBC erstellt wurde, kann ihre Methode calculate wiederholt aufgerufen werden, um die Steuer zu berechnen:

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

Die drei erforderlichen Daten können auf verschiedene Weise ermittelt werden. Der Vorteil der Klasse *importsJDBC besteht darin, dass wir uns nur um die Beschaffung dieser Daten kümmern müssen. Sobald die drei Informationen (Familienstand, Anzahl der Kinder, Jahresgehalt) vorliegen, erhalten wir durch den Aufruf der Methode *calculer der Klasse importsJDBC den fälligen Steuerbetrag.

5.2. Version 1

Wir befinden uns im Kontext einer Webanwendung, die einem Benutzer eine HTML-Oberfläche bereitstellt, um die drei für die Steuerberechnung erforderlichen Parameter zu erfassen:

  • Familienstand (verheiratet oder nicht)
  • Anzahl der Kinder
  • Jahreseinkommen

Image

Das Formular wird auf der folgenden JSP-Seite angezeigt:

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

<%
    // on récupère les attributs passés par la servlet principale
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
          // raz du formulaire
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//with
      }//effacer
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/main" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                    <input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
              <input type="radio" name="optMarie" value="non" <%= chknon %>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculer"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <%
        // y-a-t-il des erreurs
      if(erreurs!=null){
          // affichage des erreurs
        out.println("<hr>");
        out.println("<font color=\"red\">");
        out.println("Les erreurs suivantes se sont produites<br>");
        out.println("<ul>");
        for(int i=0;i<erreurs.size();i++){
            out.println("<li>"+(String)erreurs.get(i));
        }
        out.println("</ul>");
        out.println("</font>");
      } 
   %>
 </body>
</html>

Die JSP-Seite zeigt lediglich Informationen an, die ihr vom Haupt-Servlet der Anwendung übergeben werden:

     // retrieve attributes passed by the main servlet
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
chkoui, chknon
Die Attribute „checked“ und uncheckedvon Optionsfeldern können die Werte „checked“ oder „uncheckedannehmen, um anzugeben, ob das entsprechende Optionsfeld ausgewählt ist oder nicht
txtChildren
die Anzahl der Kinder des Steuerzahlers
txtSalary
sein Jahresgehalt
txtTaxes
die Höhe der fälligen Steuern
Fehler
eine Liste der Fehler, falls vorhanden, wenn errors!=null

Die an den Client gesendete Seite enthält ein JavaScript-Skript mit einer „Clear“-Funktion, die der Schaltfläche „Clear“ zugeordnet ist und dazu dient, das Formular in seinen Ausgangszustand zurückzusetzen: nicht markierte Schaltflächen, leere Eingabefelder. Beachten Sie, dass dieses Ergebnis mit einer HTML-Schaltfläche „Zurücksetzen“ nicht erreicht werden könnte. Wenn diese Art von Schaltfläche verwendet wird, setzt der Browser das Formular nämlich in den Zustand zurück, in dem er es erhalten hat. In unserer Anwendung erhält der Browser jedoch Formulare, die möglicherweise nicht leer sind.

Das Haupt-Servlet der Anwendung heißt main.java, und sein Code lautet wie folgt:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;

public class main extends HttpServlet{

    // instance variables
    String msgErreur=null;
    String urlAffichageImpots=null;
    String urlErreur=null;
    String DSNimpots=null;
    String admimpots=null;
    String mdpimpots=null;
    impotsJDBC impots=null;

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

        // was the initialization successful?
        if(msgErreur!=null){
             // we hand over to the error page
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // query attributes
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // retrieve query parameters
        String optMarie=request.getParameter("optMarie");              // marital status
        String txtEnfants=request.getParameter("txtEnfants");       // no. of children
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // annual salary
        if(txtSalaire==null) txtSalaire="";

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // missing parameters
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // we hand over to the tax display url
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // we have all the parameters - we check them
        ArrayList erreurs=new ArrayList();
         // marital status
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // error
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // number of children
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // error
            erreurs.add("Nombre d'enfants incorrect");
        }
         // salary
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // error
            erreurs.add("Salaire incorrect");
        }

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // you can calculate the tax payable
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // other query attributes
        if(optMarie.equals("oui")){
            request.setAttribute("chkoui","checked");
            request.setAttribute("chknon","");
        }else{
            request.setAttribute("chknon","checked");
            request.setAttribute("chkoui","");
        }
        request.setAttribute("txtEnfants",txtEnfants);
        request.setAttribute("txtSalaire",txtSalaire);
        request.setAttribute("txtImpots",txtImpots);

         // we hand over to the tax display url
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

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

         // create an instance of impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • Die init-Methode des Servlets führt zwei Aufgaben aus:
    • Es ruft seine Initialisierungsparameter ab. Diese ermöglichen es ihm, eine Verbindung zur ODBC-Datenbank herzustellen, die die Daten für die verschiedenen Steuerklassen (DSNimpots, admimpots, mdpimpots) sowie die URLs der mit der Anwendung verbundenen Seiten enthält: urlAffichageImpots für das Formular, urlErreur für die Fehlerseite.
    • Es erstellt eine Instanz der Klasse impotsJDBC

In beiden Fällen werden mögliche Fehler behandelt und die Fehlermeldung in der Variablen msgErreur gespeichert.

  • Die Methode doGET
    • prüft zunächst, ob das Servlet korrekt initialisiert wurde. Ist dies nicht der Fall, wird die Fehlerseite angezeigt
    • ruft die erwarteten Parameter aus dem Steuerformular ab: optMarie, txtEnfants, txtSalaire. Fehlt einer dieser Parameter (==null), wird ein leeres Steuerformular übermittelt. Man könnte meinen, dass eine Validierung des Parameters optMarie unnötig ist. Dieser Parameter ist der Wert eines Optionsfelds und kann hier nur einen der Werte „yes“ oder „no“ annehmen. Dabei wird jedoch übersehen, dass nichts ein Programm daran hindert, das Servlet direkt abzufragen, indem es ihm die gewünschten Parameter übermittelt. Man kann nie sicher sein, dass man tatsächlich mit einem Browser verbunden ist. Das Übersehen dieses Punktes kann zu Sicherheitslücken in der Anwendung führen, und tatsächlich sind solche Probleme sogar in kommerziellen Anwendungen häufig anzutreffen.
    • Die Gültigkeit jedes der drei abgerufenen Parameter wird überprüft. Alle gefundenen Fehler werden einer Fehlerliste (ArrayList errors) hinzugefügt. Wenn keine Fehler vorliegen, wird der Steuerbetrag berechnet; andernfalls nicht.
    • Die zur Anzeige der Seite erforderlichen Informationen werden als Request-Attribute gesetzt, und das Steuerformular wird anschließend angezeigt

Die JSP-Fehlerseite sieht wie folgt aus:

<%
    // jspService
  // une erreur s'est produite
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

Die Webanwendung heißt „impots“ und ist in der Datei „server.xml“ von Tomcat wie folgt konfiguriert:

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

Das Anwendungsverzeichnis enthält die folgenden Verzeichnisse und Dateien:

Image

Image

Image

Image

Die Konfigurationsdatei web.xml für die Anwendung „imports“ sieht wie folgt aus:

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

Das Haupt-Servlet heißt „main“ und hat den Alias „/main“. Es ist daher über die URL http://localhost:8080/impots/main erreichbar.

Hier sind einige Anwendungsbeispiele:

Um korrekt initialisiert zu werden, muss das Servlet Zugriff auf die Datenbank „mysql-dbimpots“ haben. Hier ist die Seite, die angezeigt wird, wenn beispielsweise der MySQL-Server nicht läuft und die Datenbank „mysql-dbimpots“ daher nicht erreichbar ist:

Image

Die Seite, die bei falscher Eingabe angezeigt wird, sieht wie folgt aus:

Image

Sind die Angaben korrekt, wird die Steuer berechnet:

Image

5.3. Version 2

Im vorherigen Beispiel überprüft der Server die Parameter „txtEnfants“ und „txtSalaire“ im Formular. Hier schlagen wir vor, diese mithilfe eines in die Formularseite eingebundenen JavaScript-Skripts zu überprüfen. Der Browser führt dann die Parameterüberprüfung durch. Der Server wird nur kontaktiert, wenn die Parameter gültig sind. Dies spart „Bandbreite“. Die JSP-Anzeigeseite sieht dann wie folgt aus:

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

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
.......
      }//effacer

      function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer        
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
........
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
          <td><input type="button" value="Effacer" onclick="effacer()"></td>
.....
 </body>
</html>

Beachten Sie die folgenden Änderungen:

  • Die Schaltfläche „Calculate“ ist nun keine Absenden-Schaltfläche mehr, sondern eine normale Schaltfläche, die mit einer Funktion namens „calculate“ verknüpft ist. Diese Funktion überprüft die Felder „txtEnfants“ und „txtSalaire“ auf Gültigkeit. Sind diese gültig, werden die Formulardaten an den Server übermittelt; andernfalls wird eine Fehlermeldung angezeigt.

Hier ist ein Beispiel dafür, was im Falle eines Fehlers angezeigt wird:

Image

5.4. Version 3

Wir nehmen eine geringfügige Änderung an der Anwendung vor, um das Konzept einer Sitzung einzuführen. Wir betrachten die Anwendung nun als ein Tool zur Simulation von Steuerberechnungen. Ein Benutzer kann dann verschiedene „Szenarien“ für Steuerzahler simulieren und sehen, wie hoch die Steuerschuld in jedem einzelnen Fall wäre. Die folgende Webseite zeigt ein Beispiel dafür, was damit erreicht werden könnte:

Image

Das Haupt-Servlet wurde geändert und heißt nun „simulations“. Es ist innerhalb der Anwendung „impots“ wie folgt konfiguriert:

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

Das Haupt-Servlet heißt „simulations“ und basiert auf der Datei „simulations.class“. Es hat den Alias „/simulations“, wodurch es über die URL „http://localhost:8080/impots/simulations“ erreichbar ist. Es verfügt über dieselben Initialisierungsparameter wie das zuvor besprochene Haupt-Servlet in Bezug auf den Datenbankzugriff. Ein neuer Parameter kommt hinzu: „urlSimulationsImports“, bei dem es sich um die URL der Simulations-JSP-Seite handelt (die oben gerade vorgestellte).

Das Servlet „simulations.java“ ähnelt dem Servlet „main.java“. Es unterscheidet sich von diesem im Wesentlichen in folgenden Punkten:

  • Das Haupt-Servlet berechnet einen Wert für `txtImpots` auf der Grundlage der Parameter `optmarie`, `txtEnfants` und `txtSalaire` und übergibt diesen Wert an die JSP-Anzeigeseite
  • Das Servlet „simulations“ berechnet den Wert „txtImpots“ auf die gleiche Weise und speichert die Parameter (optMarie, txtEnfants, txtsalaire, txtImpots) in einer Liste namens „simulations“. Diese Liste wird als Parameter an die JSP-Anzeigeseite übergeben. Um sicherzustellen, dass diese Liste alle vom Benutzer durchgeführten Simulationen enthält, wird sie als Attribut der aktuellen Sitzung gespeichert.

Das Simulations-Servlet sieht wie folgt aus (es wurden nur die Codezeilen aufgenommen, die sich von denen in der vorherigen Anwendung unterscheiden):

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

public class simulations extends HttpServlet{

    // instance variables
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         // retrieve previous simulations from the session
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // put the simulations in the current query
        request.setAttribute("simulations",simulations);

         // other query attributes
...........

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // we hand over to the url for displaying tax calculation simulations
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // we have all the parameters - we check them
...........

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // you can calculate the tax payable
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // the current result is added to the previous simulations
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // the new simulations value is added to the session
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // other query attributes
..........

         // we hand over to the simulations display url
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parameters ok?
.........................
    }
}

Die JSP-Anzeigeseite simulationsImpots.jsp sieht nun wie folgt aus (es wurde nur der Code beibehalten, der sich von der JSP-Anzeigeseite der vorherigen Anwendung unterscheidet).

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

<%
    // on récupère les attributs passés par la servlet principale
...........
  ArrayList simulations=(ArrayList)request.getAttribute("simulations");  
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
........
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
.......................
           </form> 
</center>
    <hr>
    <%
        // y-a-t-il des erreurs ?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // résultats des simulations
        out.println("<h3>Résultats des simulations<h3>");
        out.println("<table \"border=\"1\">");
        out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>");
        for(int i=0;i<simulations.size();i++){
            String[] simulation=(String[])simulations.get(i);
            out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
        }
        out.println("</table>");
      }
   %>
 </body>
</html>

5.5. Version 4

Wir werden nun eine eigenständige Anwendung erstellen, die als Web-Client für die vorherige Anwendung /impots/simulations dienen soll. Diese Anwendung wird über die folgende grafische Benutzeroberfläche verfügen:

Nr.
Typ
Name
Rolle
1
JTextField
txtTaxServiceUrl
URL des Dienstes zur Simulation der Steuerberechnung
2
JRadioButton
rdYes
Angekreuzt, wenn verheiratet
3
JRadioButton
rdNo
Markiert, wenn unverheiratet
4
JSpinner
spinChildren
Anzahl der Kinder des Steuerpflichtigen (Minimum=0, Maximum=20, Schrittweite=1)
5
JTextField
txtSalary
Jahresgehalt des Steuerpflichtigen in F
6
JList in JScrollPane
lstSimulations
Liste der Simulationen

Das Menü „Steuern“ enthält die folgenden Optionen:

Hauptmenü
Hauptmenü
Option
sekundär
Name
Rolle
Steuern
   
 
Berechnen
mnuCalculate
Berechnet die fällige Steuer, wenn alle für die Berechnung erforderlichen Daten vorhanden und korrekt sind
 
Löschen
mnuClear
Setzt das Formular auf den Ausgangszustand zurück
 
Beenden
mnuExit
schließt die Anwendung

Bedienungsregeln

  • Die Menüoption „Berechnen“ bleibt deaktiviert, wenn entweder Feld 1 oder 5 leer ist
  • In Feld 1 wird eine syntaktisch falsche URL erkannt

Image

  • ein falscher Lohn festgestellt wird

Image

  • ein Serververbindungsfehler gemeldet wird (in Beispiel 1 unten ist der Port falsch; in Beispiel 2 existiert die angeforderte URL nicht; in Beispiel 3 lief die MySQL-Datenbank nicht)

Image

Image

Image

  • Wenn alles korrekt ist, werden die Simulationen angezeigt

Image

Beim Schreiben eines programmierten Web-Clients muss man genau wissen, was der Server als Antwort auf die verschiedenen möglichen Anfragen eines Clients sendet. Der Server sendet eine Reihe von HTML-Zeilen, die nützliche Informationen sowie andere Elemente enthalten, die ausschließlich dem HTML-Layout dienen. Mit regulären Ausdrücken in Java können wir die nützlichen Informationen in dem vom Server gesendeten Datenstrom finden. Dazu müssen wir das genaue Format der verschiedenen Antworten des Servers kennen. Hier verwenden wir den zuvor vorgestellten Web-Client, mit dem wir die Antwort eines Servers auf eine URL-Anfrage auf dem Bildschirm anzeigen können. Die angeforderte URL ist die unseres Dienstes zur Steuerberechnungssimulation, http://localhost:8080/impots/simulations, an den wir Parameter in der Form http://localhost:8080/impots/simulations?param1=vam1&param2=val2&... übergeben können

Lassen Sie uns die URL anfordern, während die MySQL-Datenbank, die zur Erstellung eines Steuerobjekts verwendet wird, nicht läuft:


Dos>java clientweb http://localhost:8080/impots/simulations GET
 
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
 
 
<!-- dÚbut from page HTML -->
<html>
  <head>
        <title>impots</title>
  </head>
  <body>
        <h3>calcul d'impôts</h3>
        <hr>
    Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
  </body>
</html>

Um den Fehler abzurufen, muss der Web-Client die Antwort des Web-Servers nach der Zeile durchsuchen, die den Text „Anwendung nicht verfügbar“ enthält. Starten wir nun die MySQL-Datenbank und rufen dieselbe URL auf, wobei wir ihr die erwarteten Werte übergeben (sie akzeptiert diese gleichermaßen als GET- oder POST-Anfrage – siehe den Java-Code des Servers):


Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET
 
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
 
 
<html>
 
        <head>
        <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...................................................
      }//effacer
 
      function calculer(){
...................................................
      }//calculer
                </script>
  </head>
 
  <body background="/impots/images/standard.jpg">
        <center>
        Calcul d'imp¶ts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
      </form>
    </center>
    <hr>
    <h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
</table>
 
 </body>
</html>

Hier ist das gesamte vom Server gesendete HTML-Dokument. Die Ergebnisse der verschiedenen Simulationen finden sich in der einzigen Tabelle in diesem Dokument. Der reguläre Ausdruck, mit dem wir die relevanten Informationen aus dem Dokument extrahieren können, könnte wie folgt lauten:

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

wobei die vier Ausdrücke in Klammern die vier zu extrahierenden Informationen darstellen.

Wir haben nun die Richtlinien dafür, was zu tun ist, wenn der Benutzer über die vorherige grafische Benutzeroberfläche eine Steuerberechnung anfordert:

  • Überprüfen Sie, ob alle Daten in der Oberfläche gültig sind, und melden Sie etwaige Fehler.
  • Stellen Sie eine Verbindung zu der in Feld 1 angegebenen URL her. Dazu folgen wir dem Modell des generischen Web-Clients, das bereits vorgestellt und
  • im Server-Antwort-Stream verwenden wir reguläre Ausdrücke, um entweder:
    • die Fehlermeldung zu finden, falls vorhanden
    • die Simulationsergebnisse zu finden, falls keine Fehler vorliegen

Der Code für das Menü „Berechnen“ lautet wie folgt:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // tAX CALCULATION
         // verification URL service
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // error msg
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // focus on wrong field
            txtURLServiceImpots.requestFocus();
             // back to interface
            return;
        }
     // salary verification
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // error msg
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // focus on wrong field
      txtSalaire.requestFocus();
       // back to interface
      return;
    }
     // no. of children
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // tax calculation
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // error is displayed
            JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
        }
  }//mnuCalculer


    public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
          throws Exception{
         // tAX CALCULATION
         // urlImpots : URL of the tax department
         // married: true if married, false otherwise
         // nbEnfants : number of children
         // salary: annual salary

         // remove from urlImpots the info needed to connect to the tax server
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();

         // local data
        Socket  client=null;                        // the customer
        BufferedReader IN=null;                    // the customer's reading flow
        PrintWriter OUT=null;                        // the customer's writing flow
        String réponse=null;                        // server response
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        try{
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request URL - send HTTP headers
            OUT.println("GET " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            if(! JSESSIONID.equals("")){
                OUT.println("Cookie: JSESSIONID="+JSESSIONID);
            }
            OUT.println("Connection: close");
            OUT.println("");

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // we have a URL problem
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // it's over
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

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

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // the result of the model comparison
        Matcher résultat=null;
         // simulations
        ArrayList listeSimulations=new ArrayList();

         // read all the lines to the end
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // follow-up
             // the line is compared with the error model if the simulation part has not yet been encountered
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // error msg
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // it's over
                    return listeSimulations;
                }//if
            }//if
             // the line is compared with the simulation model
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // we found a row in the
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // the simulation was a success
                simulationRéussie=true;
            }//if
        }//while
        // end
        return listeSimulations;
    }

Lassen Sie uns diesen Code kurz erläutern:

  • Die Prozedur „mnuCalculer_actionPerformed“ prüft, ob die Schnittstellendaten gültig sind. Ist dies nicht der Fall, wird eine Fehlermeldung angezeigt und die Prozedur wird beendet. Sind die Daten gültig, wird die Prozedur „calculerImpots“ ausgeführt.
  • Die Prozedur „calculerImpots“ beginnt mit der Erstellung der URL, die sie für die Anfrage benötigt
        // on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();
........................
  • und stellt dann eine Verbindung zu dieser URL her, indem die entsprechenden HTTP-Header gesendet werden:
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request URL - send HTTP headers
            OUT.println("GET " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            if(! JSESSIONID.equals("")){
                OUT.println("Cookie: JSESSIONID="+JSESSIONID);
            }
            OUT.println("Connection: close");
            OUT.println("");
  • Sobald die Anfrage gestellt wurde, wartet unser Web-Client auf die Antwort. Er empfängt zunächst die HTTP-Header aus der Antwort des Servers. Er analysiert diese Header, um das Session-Token zu finden. Tatsächlich muss er dieses Token an den Server zurücksenden, damit der Server den Überblick über die verschiedenen durchgeführten Simulationen behalten kann. Beachten Sie hierbei, dass es für den Client einfacher gewesen wäre, die verschiedenen vom Benutzer durchgeführten Simulationen selbst zu speichern. Dennoch halten wir an der Idee fest, das Token zurückzusenden, um ein neues Beispiel für die Sitzungsverwaltung zu liefern. Die erste Zeile der Antwort wird separat behandelt. Sie muss die Form HTTP/Version 200 OK haben, um anzuzeigen, dass die angeforderte URL tatsächlich existiert. Ist dies nicht der Fall, schließen wir daraus, dass der Benutzer eine falsche URL angefordert hat, und informieren ihn entsprechend.
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model of a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // we have a URL problem
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while
  • Sobald die HTTP-Header verarbeitet wurden, fahren wir mit dem HTML-Teil der Antwort fort
             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
  • Die Prozedur getSimulations gibt die Liste der Simulationen zurück, sofern vorhanden; diese Liste ist leer, wenn der Server eine Fehlermeldung zurückgibt. In diesem Fall wird die Fehlermeldung in einem Meldungsfeld angezeigt. Ist die Liste nicht leer, wird sie in der Dropdown-Liste der grafischen Benutzeroberfläche angezeigt.
  • Die Prozedur „getSimulations“ vergleicht jede Zeile der HTML-Antwort mit dem regulären Ausdruck, der die Fehlermeldung (Anwendung nicht verfügbar...) darstellt, sowie mit dem regulären Ausdruck, der eine Simulation darstellt. Wird die Fehlermeldung gefunden, wird sie angezeigt und die Prozedur wird beendet. Wird das Ergebnis einer Simulation gefunden, wird es der Liste der Simulationen hinzugefügt. Am Ende der Prozedur wird diese Liste als Ergebnis zurückgegeben.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. Version 5

Hier wandeln wir die vorherige eigenständige grafische Anwendung in ein Java-Applet um. Die grafische Oberfläche unterscheidet sich geringfügig. Bei der eigenständigen Anwendung gab der Benutzer die URL des Dienstes zur Steuerberechnungssimulation selbst ein, und die Anwendung stellte dann eine Verbindung zu dieser URL her. Hier ist die Client-Anwendung ein Browser, und der Benutzer fordert die URL des HTML-Dokuments an, das das Applet enthält. Es ist wichtig zu beachten, dass ein Java-Applet nur eine Netzwerkverbindung zu dem Server herstellen kann, von dem es heruntergeladen wurde. Die URL des Simulationsdienstes befindet sich daher auf demselben Server wie das HTML-Dokument, das das Applet enthält. In unserem Beispiel handelt es sich dabei um einen Applet-Initialisierungsparameter, der im Feld „txtUrlServiceImpots“ platziert wird und vom Benutzer nicht bearbeitet werden kann. Dies führt zu folgendem Client:

Image

Das HTML-Dokument, das das Applet enthält, heißt simulations.htm und sieht wie folgt aus:

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

Das Applet verfügt über einen Parameter namens *urlServiceImpots, bei dem es sich um die URL des Dienstes zur Simulation der Steuerberechnung handelt. Diese URL ist relativ zur URL des HTML-Dokuments simulations.htm. Wenn ein Browser dieses Dokument also über die URL http://localhost:8080/impots/simulations.htm abruft, lautet die URL des Simulationsdienstes http://localhost:8080/impots/simulations. Wäre diese URL http://stahe:8080/impots/simulations.htm gewesen, hätte die URL des Simulationsdienstes http://stahe:8080/impots/simulations* gelautet.

Das Applet appletImpots.java enthält den gesamten Code der vorherigen eigenständigen grafischen Anwendung und hält dabei die Regeln für die Umwandlung einer grafischen Anwendung in ein Applet ein.

public class appletImpots extends JApplet {
  // window components
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //Building the frame
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // other initializations
    moreInit();
  }

   // form initialization
  private void moreInit(){
         // retrieve the urlServiceImpots parameter
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // missing parameter
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // end
            return;
        }
         // put the URL in its field
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // calculate menu disabled
    mnuCalculer.setEnabled(false);
     // spinner Children - between 0 and 20 children
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Initialize component
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
...............
  }

Wenn ein Browser ein Applet lädt, führt er zunächst dessen init-Methode aus. In dieser Methode haben wir den Wert des Parameters urlServiceImpots des Applets abgerufen, die vollständige URL des Simulationsdienstes berechnet und diesen Wert in das Feld txtURLServiceImpots eingefügt, als hätte der Benutzer ihn selbst eingegeben. Sobald dies geschehen ist, gibt es keinen Unterschied mehr zwischen den beiden Anwendungen. Insbesondere ist der Code, der mit dem Menü „Calculate“ verbunden ist, identisch. Hier ist ein Beispiel für die Ausführung:

Image

5.7. Fazit

Wir haben verschiedene Versionen unserer Client-Server-Anwendung zur Steuerberechnung vorgestellt:

  • Version 1: Der Dienst wird durch eine Reihe von Servlets und JSP-Seiten bereitgestellt; der Client ist ein Browser. Er führt eine einzelne Simulation durch und speichert keine Historie früherer Simulationen.
  • Version 2: Wir fügen einige browserbasierte Funktionen hinzu, indem wir JavaScript-Skripte in das vom Browser geladene HTML-Dokument einbetten. Diese validieren die Formularparameter.
  • Version 3: Wir ermöglichen es dem Dienst, sich die verschiedenen von einem Client durchgeführten Simulationen zu merken, indem wir eine Sitzung verwalten. Die HTML-Oberfläche wird entsprechend angepasst, um diese Simulationen anzuzeigen.
  • Version 4: Der Client ist nun eine eigenständige grafische Anwendung. Dies ermöglicht es uns, die Entwicklung programmierter Web-Clients erneut zu betrachten.
  • Version 5: Der Client wird zu einem Java-Applet. Wir verfügen nun über eine vollständig in Java programmierte Client-Server-Anwendung, sowohl auf der Server- als auch auf der Client-Seite.

An dieser Stelle lassen sich einige Beobachtungen anstellen:

  • Die Versionen 1 bis 3 unterstützen Browser, die keine anderen Funktionen als die Ausführung von JavaScript-Skripten bieten. Beachten Sie, dass ein Benutzer jederzeit die Möglichkeit hat, die Ausführung dieser Skripte zu deaktivieren. Die Anwendung funktioniert dann in Version 1 nur teilweise (die Option „Löschen“ funktioniert nicht) und in den Versionen 2 und 3 überhaupt nicht (die Optionen „Löschen“ und „Berechnen“ funktionieren nicht). Es könnte sich lohnen, eine Version des Dienstes zu entwickeln, die keine JavaScript-Skripte verwendet.
  • Version 4 setzt voraus, dass der Client-Computer über eine Java 2 Virtual Machine verfügt.
  • Für Version 5 muss der Client-Computer über einen Browser mit einer Java 2 Virtual Machine verfügen.

Bei der Entwicklung eines Webdienstes müssen Sie berücksichtigen, welche Arten von Clients Sie ansprechen möchten. Wenn Sie eine möglichst große Anzahl von Clients erreichen möchten, sollten Sie eine Anwendung entwickeln, die ausschließlich HTML an Browser sendet (kein JavaScript oder Applets). Wenn Sie in einem Intranet arbeiten und die Konfiguration der Arbeitsplätze kontrollieren können, können Sie auf der Client-Seite höhere Anforderungen stellen, und die vorherige Version 5 könnte dann akzeptabel sein.

Die Versionen 4 und 5 sind Web-Clients, die die benötigten Informationen aus dem vom Server gesendeten HTML-Stream abrufen. Sehr oft haben wir keine Kontrolle über diesen Stream. Dies ist der Fall, wenn wir einen Client für einen bestehenden Webdienst im Netzwerk geschrieben haben, der von jemand anderem verwaltet wird. Nehmen wir ein Beispiel. Angenommen, unser Dienst zur Simulation von Steuerberechnungen wurde von Unternehmen X geschrieben. Derzeit sendet der Dienst die Simulationen in einer HTML-Tabelle, und unser Client nutzt diese Tatsache, um sie abzurufen. Er vergleicht daher jede Zeile der Serverantwort mit dem regulären Ausdruck:

         // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");

Nehmen wir nun an, der Anwendungsentwickler ändert das visuelle Erscheinungsbild der Antwort, indem er die Simulationen nicht in einer Tabelle, sondern in einer Liste im folgenden Format anordnet:

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

In diesem Fall muss unser Web-Client neu geschrieben werden. Dies ist die ständige Gefahr, der Web-Clients für Anwendungen ausgesetzt sind, die wir nicht selbst kontrollieren. XML kann eine Lösung für dieses Problem bieten:

  • Anstatt HTML zu generieren, erzeugt der Simulationsdienst XML. In unserem Beispiel könnte dies
<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>
  • Dieser Antwort könnte ein Stylesheet zugeordnet werden, das den Browsern Anweisungen zur visuellen Darstellung dieser XML-Antwort gibt
  • Programmierte Web-Clients würden dieses Stylesheet ignorieren und die Informationen direkt aus dem XML-Stream der Antwort abrufen

Wenn der Dienstentwickler die visuelle Darstellung der bereitgestellten Ergebnisse ändern möchte, ändert er das Stylesheet und nicht das XML. Dank des Stylesheets zeigen Browser das neue visuelle Format an, und programmgesteuerte Web-Clients müssen nicht angepasst werden. Neue Versionen unseres Simulationsdienstes könnten daher wie folgt geschrieben werden:

  • Version 6: Der Dienst liefert eine XML-Antwort zusammen mit einem Stylesheet, das für Browser bestimmt ist
  • Version 7: Der Client ist eine eigenständige grafische Anwendung, die die XML-Antwort des Servers verarbeitet
  • Version 8: Der Client ist ein Java-Applet, das die XML-Antwort des Servers verarbeitet