Skip to content

5. Die Anwendung IMPOTS

5.1. Einleitung

Wir greifen hier die Anwendung IMPOTS wieder auf, die im Java-Skript desselben Autors mehrfach verwendet wird. Erinnern wir uns an die Problemstellung. Es geht darum, eine Anwendung zu schreiben, mit der die Steuer eines Steuerpflichtigen berechnet werden kann. Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt anzugeben hat:

  • Wir berechnen die Anzahl der Anteile des Arbeitnehmers nbParts = nbEnfants/2 + 1, wenn er unverheiratet ist, nbEnfants/2+2, wenn er verheiratet ist, wobei nbEnfants die Anzahl seiner Kinder ist.
  • Wenn er mindestens drei Kinder hat, erhält er einen halben Anteil zusätzlich
  • sein steuerpflichtiges Einkommen R wird berechnet als R = 0,72 * S, wobei S sein Jahresgehalt ist
  • Man berechnet seinen Familienkoeffizienten QF = R / nbParts
  • Man berechnet seine Steuer I. Betrachten wir die folgende Tabelle:
12620,0
0
0
13.190
0,05
631
15640
0,1
1290,5
24.740
0,15
2072,5
31.810
0,2
3309,5
39.970
0,25
4900
48360
0,3
6898,5
55.790
0,35
9316,5
92.970
0,4
12106
127.860
0,45
16.754,5
151250
0,50
23147,5
172040
0,55
30710
195.000
0,60
39312
0
0,65
49062

Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, wird die erste Zeile gesucht, in der QF <= Feld 1 gilt. Wenn beispielsweise QF = 23000 ist, wird die Zeile

    24740        0.15        2072.5

Die Steuer I beträgt dann 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Beziehung QF <= Feld1 niemals erfüllt ist, werden die Koeffizienten der letzten Zeile verwendet. Hier:

    0                0.65        49062

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

Die Daten zur Definition der verschiedenen Steuerklassen befinden sich in einer ODBC-MySQL-Datenbank. MySQL ist ein gemeinfreies SGBD, das auf verschiedenen Plattformen, darunter Windows und Linux, verwendet werden kann. Mit diesem SGBD wurde eine Datenbank namens „dbimpots“ angelegt, die eine einzige Tabelle namens „impots“ enthält. Der Zugriff auf die Datenbank wird über 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 die Datenquelle ODBC umgewandelt:

  • Der 32-Bit-Datenquellenmanager ODBC wird gestartet

Image

  • über die Schaltfläche [Add] wird eine neue Datenquelle ODBC hinzugefügt

Image

  • Man wählt den Treiber MySQL aus und führt [Terminer] aus

54321

Image

  • Der Treiber MySQL fordert eine Reihe von Angaben an:
1
den Namen DSN, der der Datenquelle ODBC zugewiesen werden soll – dieser kann beliebig sein
2
der Maschine, auf der SGBD und MySQL ausgeführt werden – in diesem Fall localhost. Interessanterweise könnte es sich bei der Datenbank um eine entfernte Datenbank handeln. Lokale Anwendungen, die die Datenquelle ODBC verwenden, würden davon nichts bemerken. Dies wäre insbesondere bei unserer Java-Anwendung der Fall.
3
die zu verwendende Datenbank MySQL. MySQL ist ein SGBD, das relationale Datenbanken verwaltet, bei denen es sich um Gruppen von Tabellen handelt, die durch Beziehungen miteinander verbunden sind. Hier wird der Name der verwalteten Datenbank angegeben.
4
den Namen eines Benutzers, der Zugriffsrechte auf diese Datenbank hat
5
sein Passwort

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

// Anlegen einer Steuerklasse

public class impots{

  // Die für die Steuerberechnung erforderlichen Daten
   // stammen aus einer externen Quelle

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

   // leerer Konstruktor
  protected impots(){}

   // Konstruktor
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // Es wird überprüft, ob alle drei Tabellen dieselbe Größe haben
    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+")");
    // alles in Ordnung
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//Hersteller

   // Steuerberechnung
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // Berechnung der Anzahl der Anteile
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // Berechnung des steuerpflichtigen Einkommens und des Familienquotienten
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // Steuerberechnung
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // Ergebnis zurückgeben
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//berechnen
}//Klasse

Die Klasse impotsJDBC leitet sich von der vorherigen Klasse „impots“ ab. Eine Instanz der Klasse impotsJDBC wird mit den in einer Datenbank gespeicherten Daten zu den Steuerklassen erstellt. Die für den Zugriff auf diese Datenbank erforderlichen Informationen werden als Parameter an den Konstruktor übergeben:

// importierte Pakete
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // Hinzufügen eines Konstruktors zum Erstellen
   // die Tabellen limites, coeffr und coeffn aus der Tabelle
   // Steuerdaten aus einer Datenbank
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: Name der Datenbank DSN
     // userIMPOTS, mdpIMPOTS: Benutzername/Passwort für den Zugriff auf die Datenbank

     // die Datentabellen
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // Verbindung zur Datenbank
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // Erstellung eines Statement-Objekts
    Statement S=connect.createStatement();
    // SELECT-Abfrage
    String select="select limites, coeffr, coeffn from impots";
    // Ausführung der Abfrage
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // Auswertung der aktuellen Zeile
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// Nächste Zeile
     // Ressourcen schließen
    RS.close();
    S.close();
    connect.close();
     // Übertragung der Daten in begrenzte 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
  }//Konstruktor
}//Klasse

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

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

Die Erfassung der drei erforderlichen Daten kann auf verschiedene Arten erfolgen. Der Vorteil der Klasse impotsJDBC besteht darin, dass man sich lediglich um diese Erfassung kümmern muss. Sobald die drei Informationen (Familienstand, Anzahl der Kinder, Jahresgehalt) vorliegen, liefert uns der Aufruf der Methode calculer der Klasse impotsJDBC die zu zahlende Steuer.

5.2. Version 1

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

  • den Familienstand (verheiratet oder unverheiratet)
  • die Anzahl der Kinder
  • das Jahreseinkommen

Image

Die Anzeige des Formulars erfolgt über die folgende Seite JSP:

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

<%
     // Hier werden die vom Haupt-Servlet übergebenen Attribute abgerufen
  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(){
           // Formular zurücksetzen
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//mit
      }//Löschen
        </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>
    <%
         // Gibt es Fehler?
      if(erreurs!=null){
           // Anzeige der Fehler
        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 Seite JSP zeigt lediglich Informationen an, die ihr vom Haupt-Servlet der Anwendung übergeben werden:

     // Die vom Haupt-Servlet übergebenen Attribute werden abgerufen
  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
Attribute der Optionsfelder „Ja“ und „Nein“ – mögliche Werte sind "checked" oder "", um das entsprechende Optionsfeld zu aktivieren bzw. nicht zu aktivieren
txtEnfants
die Anzahl der Kinder des Steuerpflichtigen
txtSalaire
sein Jahresgehalt
txtImpots
die Höhe der zu zahlenden Steuer
erreurs
eine eventuelle Fehlerliste, falls „fehler“ != null

Die an den Kunden gesendete Seite enthält ein JavaScript-Skript mit einer Funktion effacer, die der Schaltfläche „Effacer“ zugeordnet ist und dazu dient, das Formular in seinen Ausgangszustand zurückzusetzen: Schaltfläche nicht markiert, Eingabefelder leer. Es ist zu beachten, dass dieses Ergebnis mit einer Schaltfläche vom Typ „HTML“ nicht erzielt werden konnte. Denn wenn dieser Schaltflächentyp verwendet wird, versetzt der Browser das Formular in den Zustand zurück, in dem er es empfangen 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{

    // Instanzvariablen
    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{

        // Ist die Initialisierung erfolgreich verlaufen?
        if(msgErreur!=null){
             // Es wird an die Fehlerseite weitergeleitet
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // Attribute der Anfrage
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // Die Parameter der Anfrage werden abgerufen
        String optMarie=request.getParameter("optMarie");              // Familienstand
        String txtEnfants=request.getParameter("txtEnfants");       // Anzahl der Kinder
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // Jahresgehalt
        if(txtSalaire==null) txtSalaire="";

         // Sind alle erwarteten Parameter vorhanden?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // Es fehlen Parameter
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // Weiterleitung an die URL zur Anzeige der Steuer
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // Alle Parameter sind vorhanden  sie werden überprüft
        ArrayList erreurs=new ArrayList();
         // Familienstand
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // Fehler
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // Anzahl der Kinder
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // Fehler
            erreurs.add("Nombre d'enfants incorrect");
        }
         // Gehalt
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // Fehler
            erreurs.add("Salaire incorrect");
        }

         // Falls Fehler auftreten, werden diese als Attribut der Abfrage übergeben
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // die zu zahlende Steuer kann berechnet werden
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // die übrigen Attribute der Abfrage
        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);

         // die Kontrolle wird an die URL zur Anzeige der Steuer übergeben
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // Die Initialisierungsparameter werden abgerufen
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

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

         // Es wird eine Instanz von impotsJDBC erstellt
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • Die init-Methode des Servlets führt zwei Schritte aus:
    • Sie ruft ihre Initialisierungsparameter ab. Diese ermöglichen es ihr, eine Verbindung zur Datenbank ODBC herzustellen, die die Daten der verschiedenen Steuerklassen (DSNimpots, admimpots, mdpimpots) sowie die URL der zur Anwendung gehörenden Seiten enthält: urlAffichageImpots für das Formular, urlErreur für die Fehlerseite.
    • Sie erstellt eine Instanz der Klasse impotsJDBC

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

  • Die Methode doGET
    • überprüft zunächst, ob das Servlet korrekt initialisiert wurde. Ist dies nicht der Fall, wird die Fehlerseite angezeigt
    • und ruft die erwarteten Parameter des Steuerformulars ab: optMarie, txtEnfants, txtSalaire. Fehlt einer davon (==null), wird ein leeres Steuerformular gesendet. Man könnte meinen, dass die Überprüfung der Gültigkeit des Parameters optMarie überflüssig ist. Dieser ist der Wert eines Optionsfelds und kann hier nur einen der Werte „oui“ oder „non“ annehmen. Dabei wird jedoch übersehen, dass nichts ein Programm daran hindert, das Servlet direkt abzufragen, indem es die gewünschten Parameter an es sendet. Man kann nie sicher sein, dass tatsächlich eine Verbindung zu einem Browser besteht. Das Übersehen dieses Punktes kann zu Sicherheitslücken in der Anwendung führen, und es kommt übrigens häufig vor, dass solche Lücken sogar in kommerziellen Anwendungen zu finden sind.
    • Die Gültigkeit jedes der drei abgerufenen Parameter wird überprüft. Jeder gefundene Fehler wird einer Fehlerliste hinzugefügt (ArrayList Fehler). Liegen keine Fehler vor, wird der Steuerbetrag berechnet, andernfalls nicht.
    • Die für die Anzeige der Seite erforderlichen Informationen werden als Attribute der Anfrage festgelegt, und anschließend wird das Steuerformular angezeigt.

Die Fehlerseite JSP sieht wie folgt aus:

<%
     // jspService
   // Es ist ein Fehler aufgetreten
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- Anfang der Seite 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 Tomcat-Datei server.xml wie folgt konfiguriert:

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

Der Anwendungsordner enthält die folgenden Ordner und Dateien:

Image

Image

Image

Image

Die Konfigurationsdatei „web.xml“ der Anwendung „impots“ lautet wie folgt:

<?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 URL und http://localhost:8080/impots/main erreichbar.

Hier sind einige Anwendungsbeispiele:

Um sich korrekt zu initialisieren, muss das Servlet Zugriff auf die Datenbank mysql-dbimpots haben. Hier ist die Seite, die angezeigt wird, wenn beispielsweise der Server MySQL nicht gestartet ist und die Datenbank mysql-dbimpots daher nicht erreichbar ist:

Image

Bei einer falschen Eingabe wird folgende Seite angezeigt:

Image

Bei korrekter Eingabe wird die Steuer berechnet:

Image

5.3. Version 2

Im vorangegangenen Beispiel wird die Gültigkeit der Formularparameter txtEnfants und txtSalaire vom Server überprüft. Hier wird vorgeschlagen, die Gültigkeit mithilfe eines in die Formularseite eingebundenen JavaScript-Skripts zu überprüfen. Die Überprüfung der Parameter erfolgt dann durch den Browser. Der Server wird somit nur dann angefragt, wenn die Parameter gültig sind. Auf diese Weise wird „Bandbreite“ eingespart. Die Anzeigeseite JSP sieht dann wie folgt aus:

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

<html>

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

      function calculer(){
           // Überprüfung der Parameter vor dem Senden an den Server
        with(document.frmImpots){
          //Anzahl der Kinder
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // Das Modell wurde nicht überprüft
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //Gehalt
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // Das Modell wurde nicht überprüft
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // Alles in Ordnung – das Formular wird an den Server gesendet
          submit();
        }//mit
      }//berechnen        
        </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>

Es sind folgende Änderungen zu beachten:

  • Die Schaltfläche Calculer hat nicht mehr den Typ submit, sondern den Typ button, der mit einer Funktion namens calculer verknüpft ist. Diese Funktion führt die Analyse der Felder txtEnfants und txTsalaire durch. Sind diese korrekt, werden die Werte des Formulars an den Server gesendet (Submit), andernfalls wird eine Fehlermeldung angezeigt.

Hier ein Beispiel für die Anzeige im Fehlerfall:

Image

5.4. Version 3

Wir passen die Anwendung leicht an, um das Konzept der Sitzung einzuführen. Wir betrachten die Anwendung nun als eine Simulation zur Steuerberechnung. Ein Benutzer kann dann verschiedene „Konfigurationen“ von Steuerpflichtigen simulieren und sehen, wie hoch die zu zahlende Steuer für jede dieser Konfigurationen wäre. Die untenstehende Seite WEb zeigt ein Beispiel dafür, was dabei herauskommen 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 Klassendatei simulations.class. Es hat den Alias „/simulations“, wodurch es über die Servlets URL und http://localhost:8080/impots/simulations erreichbar ist. Es verfügt über dieselben Initialisierungsparameter wie das zuvor behandelte Haupt-Servlet, was den Zugriff auf die Datenbank betrifft. Es taucht ein neuer Parameter auf: urlSimulationsImpots, bei dem es sich um das URL der Simulationsseite JSP handelt (die etwas weiter oben vorgestellt wurde).

Das Servlet simulations.java ähnelt dem Servlet main.java. Es unterscheidet sich von diesem in folgenden wesentlichen Punkten:

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

Das Servlet „simulations“ sieht wie folgt aus (es wurden nur die Codezeilen beibehalten, die sich von denen der vorherigen Anwendung unterscheiden):

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

public class simulations extends HttpServlet{

    // Instanzvariablen
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         // die vorherigen Simulationen der Sitzung werden abgerufen
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // die Simulationen werden in die aktuelle Abfrage übernommen
        request.setAttribute("simulations",simulations);

         // weitere Attribute der Abfrage
...........

         // Sind alle erwarteten Parameter vorhanden?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // die Steuerung wird an die URL zur Anzeige der Steuerberechnungssimulationen übergeben
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // Alle Parameter sind vorhanden  sie werden überprüft
...........

         // Falls Fehler vorliegen, werden diese als Attribute der Anfrage übergeben
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // Die zu zahlende Steuer kann berechnet werden
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // Das aktuelle Ergebnis wird zu den vorherigen Simulationen hinzugefügt
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // Der neue Simulationswert wird in die Sitzung zurückgeschrieben
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // weitere Attribute der Abfrage
..........

         // die Kontrolle wird an die URL zur Anzeige der Simulationen übergeben
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // Die Initialisierungsparameter werden abgerufen
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // Sind die Parameter in Ordnung?
.........................
    }
}

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

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

<%
     // Die vom Haupt-Servlet übergebenen Attribute werden abgerufen
...........
  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>
    <%
         // Gibt es Fehler?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // Ergebnisse der Simulationen
        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 dient. Diese Anwendung wird folgende grafische Benutzeroberfläche haben:

Nr.
Typ
Name
Rolle
1
JTextField
txtUrlServiceImpots
URL des Dienstes zur Simulation der Steuerberechnung
2
JRadioButton
rdOui
ankreuzen, wenn verheiratet
3
JRadioButton
rdNon
ankreuzen, wenn unverheiratet
4
JSpinner
spinEnfants
Anzahl der Kinder des Steuerpflichtigen (Minimum=0, Maximum=20, Schrittweite=1)
5
JTextField
txtSalaire
Jahresgehalt des Steuerpflichtigen in F
6
JList in JScrollPane
lstSimulations
Liste der Simulationen

Das Menü „Steuern“ umfasst folgende Optionen:

Option
Hauptoption
Nebenoption
Nebenoption
Name
Rolle
Steuern
   
 
Berechnen
mnuCalculer
berechnet die zu zahlende Steuer, wenn alle für die Berechnung erforderlichen Daten vorhanden und korrekt sind
 
Löschen
mnuEffacer
Setzt das Formular in seinen Ausgangszustand zurück
 
Beenden
mnuQuitter
beendet die Anwendung

Funktionsregeln

  • Der Menüpunkt Calculer bleibt deaktiviert, wenn eines der Felder 1 oder 5 leer ist
  • eine syntaktisch fehlerhafte URL in Feld 1 erkannt wird

Image

  • ein falscher Lohn erkannt wird

Image

  • Jeder Fehler bei der Verbindung zum Server wird gemeldet (im Beispiel 1 unten ist der Port falsch – im Beispiel 2 existiert die angeforderte URL nicht – im Beispiel 3 war die Datenbank MySQL nicht gestartet)

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 Zeilen HTML, die nützliche Informationen sowie andere enthalten, die nur dem Layout dienen HTML. Reguläre Ausdrücke in Java können uns dabei helfen, die nützlichen Informationen in der Flut der vom Server gesendeten Zeilen zu finden. Dazu müssen wir das genaue Format der verschiedenen Serverantworten kennen. Hier verwenden wir den bereits vorgestellten Webclient, mit dem sich die Antwort eines Servers auf die Anfrage nach einer URL auf dem Bildschirm anzeigen lässt. Die angeforderte URL ist die unseres Dienstes zur Simulation von Steuerberechnungen http://localhost:8080/impots/simulations, an den wir Parameter in der Form http://localhost:8080/impots/simulations?param1=vam1&param2=val2&... übergeben können

Rufen wir nun die URL auf, während die Basis MySQL, mit der ein Objekt vom Typ impots erstellt werden kann, nicht gestartet ist:


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 von der Seite 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 Webclient in der Antwort des Webservers nach der Zeile mit dem Text „Anwendung nicht verfügbar“ suchen. Starten wir nun die Datenbank MySQL und fragen wir dieselbe Datenbank URL ab, indem wir ihr die erwarteten Werte übergeben (sie erwartet diese Werte entweder in Form von GET oder POST – siehe 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(){
...................................................
      }//löschen

      function calculer(){
...................................................
      }//berechnen
                </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 finden wir das gesamte vom Server gesendete Dokument HTML. Die Ergebnisse der verschiedenen Simulationen sind in der einzigen Tabelle dieses Dokuments enthalten. 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 abzurufenden Informationen darstellen.

Wir haben nun die Leitlinien dafür, was zu tun ist, wenn der Benutzer über die vorherige grafische Benutzeroberfläche die Berechnung der Steuer anfordert:

  • Überprüfen, ob alle Daten der Benutzeroberfläche gültig sind, und gegebenenfalls Fehler melden.
  • eine Verbindung zu dem in Feld 1 angegebenen URL herstellen. Dazu folgen wir dem bereits vorgestellten und untersuchten Modell des generischen Web-Clients
  • Im Antwortstrom des Servers reguläre Ausdrücke verwenden, um entweder:
    • die Fehlermeldung zu finden, falls vorhanden
    • die Simulationsergebnisse zu finden, falls kein Fehler vorliegt

Der Code für das Menü calculer lautet wie folgt:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // Steuerberechnung
         // Überprüfung 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){
             // Fehlermeldung
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // Fokus auf fehlerhaftes Feld
            txtURLServiceImpots.requestFocus();
             // Zurück zur Benutzeroberfläche
            return;
        }
     // Gehaltsüberprüfung
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // Fehlermeldung
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // Fokus auf fehlerhaftes Feld
      txtSalaire.requestFocus();
       // Zurück zur Benutzeroberfläche
      return;
    }
     // Anzahl der Kinder
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // Die Steuer wird berechnet
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // der Fehler wird angezeigt
            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{
         // Steuerberechnung
         // urlImpots: URL vom Finanzamt
         // verheiratet: true, wenn verheiratet, andernfalls false
         // nbEnfants: Anzahl der Kinder
         // Gehalt: Jahresgehalt

         // Aus urlImpots werden die für die Verbindung zum Steuer-Server erforderlichen Informationen entnommen
        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 Daten
        Socket  client=null;                        // der Client
        BufferedReader IN=null;                    // der Lesestrom des Kunden
        PrintWriter OUT=null;                        // Schreib-Stream des Clients
        String réponse=null;                        // Antwort des Servers
         // das in den Headern gesuchte Muster HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // das Muster einer korrekten Antwort
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // Ergebnis des Vergleichs mit dem Muster
        Matcher résultat=null;

        try{
             // Verbindung zum Server wird hergestellt
            client=new Socket(host,port);

            // Die Ein- und Ausgabeströme des Clients werden erstellt: TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // Anfrage an URL  Senden der Header 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("");

             // Die erste Zeile der Antwort wird gelesen
            réponse=IN.readLine();
             // Die Zeile HTTP wird mit dem Muster der korrekten Antwort verglichen
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // Es gibt ein Problem mit URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(Ergebnis)

             // Die Antwort wird bis zum Ende der Header gelesen, um nach einem möglichen Cookie zu suchen
            while((réponse=IN.readLine())!=null){
                 // Leere Zeile?
                if(réponse.equals("")) break;
                // Zeile HTTP ist nicht leer
                 // Wenn das Session-Token fehlt, wird danach gesucht
                if (JSESSIONID.equals("")){
                    // die Zeile HTTP wird mit dem Cookie-Muster verglichen
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // Das Token-Cookie wurde gefunden
                        JSESSIONID=résultat.group(1);
                    }//if(Ergebnis)
                }//if(JSESSIONID)
            }//while

             // Das war's mit den Headern HTTP – weiter geht's mit dem Code HTML
             // um die Simulationen abzurufen
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

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

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

        // das Muster einer Zeile aus der Simulationstabelle
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // das Muster einer Zeile aus der Fehlerliste
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // das Ergebnis des Vergleichs mit dem Modell
        Matcher résultat=null;
         // die Simulationen
        ArrayList listeSimulations=new ArrayList();

         // Es werden alle Zeilen bis zum Ende gelesen
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // Weiter
             // die Zeile wird mit dem Fehlermodell verglichen, sofern der Abschnitt Simulationen noch nicht erreicht wurde
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // Fehlermeldung
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // Fertig
                    return listeSimulations;
                }//if
            }//if
             // Die Zeile wird mit dem Simulationsmodell verglichen
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // Es wurde eine Zeile aus der Tabelle gefunden
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // Die Simulation war erfolgreich
                simulationRéussie=true;
            }//if
        }//while
        // Ende
        return listeSimulations;
    }

Lassen Sie uns diesen Code etwas näher erläutern:

  • Die Prozedur mnuCalculer_actionPerformed überprüft, ob die Daten der Schnittstelle gültig sind. Sind sie es nicht, wird eine Fehlermeldung ausgegeben und die Prozedur beendet. Sind sie es, wird die Prozedur calculerImpots ausgeführt.
  • Die Prozedur calculerImpots beginnt damit, die Prozedur URL zu erstellen, die sie aufrufen muss
         // Aus urlImpots werden die für die Verbindung zum Steuerserver erforderlichen Informationen entnommen
        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 diesem URL her, indem es die entsprechenden HTTP-Header sendet:
             // Es wird eine Verbindung zum Server hergestellt
            client=new Socket(host,port);

            // Die Ein- und Ausgangsströme des Kunden werden angelegt: TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // Anforderung des URL  Versand der Kopfdaten 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("");
  • Sobald die Anfrage gestellt wurde, wartet unser Webclient auf die Antwort. Zunächst erhält er die Header HTTP aus der Antwort des Servers. Er durchsucht diese Header nach dem Sitzungstoken. Dieses muss er nämlich an den Server zurücksenden, damit dieser die verschiedenen durchgeführten Simulationen nachverfolgen kann. An dieser Stelle sei angemerkt, dass es einfacher gewesen wäre, wenn der Client die verschiedenen vom Benutzer durchgeführten Simulationen selbst gespeichert hätte. Dennoch behalten wir das Konzept der Rücksendung des Tokens bei, um ein weiteres Beispiel für die Sitzungsverwaltung zu liefern. Die erste Zeile der Antwort wird gesondert behandelt. Sie muss nämlich die Form „HTTP/Version 200 OK“ haben, um anzuzeigen, dass das angeforderte „URL“ tatsächlich existiert. Wenn sie also nicht diese Form hat, wird daraus geschlossen, dass der Benutzer ein falsches URL angefordert hat, und dies wird ihm mitgeteilt.
         // Das gesuchte Muster wird in den Kopfzeilen gesucht HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // das Muster einer korrekten Antwort
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // die erste Zeile der Antwort wird gelesen
            réponse=IN.readLine();
             // Die Zeile HTTP wird mit dem Muster der korrekten Antwort verglichen
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // Es liegt ein Problem mit URL vor
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(Ergebnis)

             // Die Antwort wird bis zum Ende der Header gelesen, wobei nach einem eventuellen Cookie gesucht wird
            while((réponse=IN.readLine())!=null){
                 // Leere Zeile?
                if(réponse.equals("")) break;
                // Zeile HTTP nicht leer
                 // Wenn das Session-Token fehlt, wird danach gesucht
                if (JSESSIONID.equals("")){
                    // die Zeile HTTP wird mit dem Cookie-Muster verglichen
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // Das Token-Cookie wurde gefunden
                        JSESSIONID=résultat.group(1);
                    }//if(Ergebnis)
                }//if(JSESSIONID)
            }//while
  • Nachdem die Kopfzeilen HTTP ausgewertet wurden, geht es weiter mit dem Teil HTML der Antwort
             // Das war's mit den Headern HTTP – weiter geht's mit dem Code HTML
             // um die Simulationen abzurufen
            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 Antwort von HTML 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 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{

        // das Muster einer Zeile aus der Simulationstabelle
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // die Vorlage für eine Zeile der Fehlerliste
        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 Benutzeroberfläche unterscheidet sich geringfügig. Bei der eigenständigen Anwendung gab der Benutzer selbst die URL des Dienstes zur Simulation von Steuerberechnungen ein, woraufhin die Anwendung eine Verbindung zu dieser URL herstellte. Hier ist die Client-Anwendung ein Browser, und der Benutzer fordert die URL des Dokuments HTML an, das das Applet enthält. Man muss nun bedenken, dass ein Java-Applet nur eine Netzwerkverbindung zu dem Server herstellen kann, von dem es heruntergeladen wurde. Das URL des Simulationsdienstes befindet sich daher auf demselben Server wie das Dokument HTML, das das Applet enthält. In unserem Beispiel wird ein Initialisierungsparameter des Applets in das Feld txtUrlServiceImpots eingefügt, das für den Benutzer nicht bearbeitbar ist. Damit ergibt sich folgender Client:

Image

Das Dokument HTML, 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 urlServiceImpots, der dem URL des Dienstes zur Simulation der Steuerberechnung entspricht. Diese URL ist relativ und wird im Verhältnis zur URL des Dokuments HTML simulations.htm gemessen. Wenn also ein Browser dieses Dokument mit dem URL „http://localhost:8080/impots/simulations.htm“ abruft, dann wäre der URL des Simulationsdienstes http://localhost:8080/impots/simulations.. Wäre dieser URL http://stahe gewesen:8080/impots/simulations.htm, wäre die URL des Simulationsdienstes http://stahe:8080/impots/simulations gewesen.

Das Applet appletImpots.java übernimmt den Code der vorherigen eigenständigen grafischen Anwendung vollständig und beachtet dabei die Regeln für die Umwandlung einer grafischen Anwendung in ein Applet.

public class appletImpots extends JApplet {
  // die Komponenten des Fensters
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //Rahmen erstellen
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // weitere Initialisierungen
    moreInit();
  }

   // Initialisierung des Formulars
  private void moreInit(){
         // wird der Parameter urlServiceImpots abgerufen
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // Parameter fehlt
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // Ende
            return;
        }
         // URL wird in das entsprechende Feld eingefügt
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // Menü „Berechnen“ deaktiviert
    mnuCalculer.setEnabled(false);
     // Auswahlfeld „Kinder“ – zwischen 0 und 20 Kindern
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Komponente initialisieren
  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-Prozedur aus. In dieser haben wir den Wert des Parameters urlServiceImpots des Applets abgerufen, anschließend den vollständigen Namen des Simulationsdienstes URL berechnet und diesen Wert in das Feld txtURLServiceImpots eingegeben, so als hätte der Benutzer ihn selbst eingegeben. Damit gibt es keinen Unterschied mehr zwischen den beiden Anwendungen. Insbesondere ist der dem Menü Calculer zugeordnete Code identisch. Hier ein Ausführungsbeispiel:

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 Seiten (JSP) bereitgestellt, der Client ist ein Browser. Er führt nur eine einzige Simulation durch und speichert die vorherigen nicht.
  • Version 2: Es werden einige browserbasierte Funktionen hinzugefügt, indem JavaScript-Skripte in das vom Browser geladene Dokument HTML eingebunden werden. Diese überprüfen die Gültigkeit der Formularparameter.
  • Version 3: Durch die Verwaltung einer Sitzung wird es dem Dienst ermöglicht, sich die verschiedenen von einem Client durchgeführten Simulationen zu merken. Die Schnittstelle HTML wird entsprechend angepasst, um diese anzuzeigen.
  • Version 4: Der Client ist nun eine eigenständige grafische Anwendung. Dies ermöglicht es uns, wieder auf die Entwicklung programmierter Web-Clients zurückzugreifen.
  • Version 5: Der Client wird zu einem Java-Applet. Damit verfügen wir ü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 Anmerkungen machen:

  • Die Versionen 1 bis 3 unterstützen Browser, die lediglich über die Fähigkeit verfügen, JavaScript-Skripte auszuführen. Es ist zu beachten, dass ein Benutzer jederzeit die Möglichkeit hat, die Ausführung dieser Skripte zu unterbinden. 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 interessant sein, eine Version des Dienstes zu entwickeln, die ohne JavaScript-Skripte auskommt.
  • Für Version 4 muss der Client-Rechner über eine Java 2-Virtual Machine verfügen.
  • Für Version 5 muss der Client-Rechner über einen Browser mit einer Java-2-Virtual-Machine verfügen.

Wenn man einen Webdienst entwickelt, muss man sich fragen, welche Arten von Clients man ansprechen möchte. Wenn man möglichst viele Clients ansprechen möchte, sollte man eine Anwendung schreiben, die an die Browser ausschließlich HTML sendet (kein JavaScript und keine Applets). Arbeitet man in einem Intranet und hat die Kontrolle über die Konfiguration der dortigen Rechner, kann man sich höhere Anforderungen an den Client leisten, und die vorherige Version 5 kann dann akzeptabel sein.

Die Versionen 4 und 5 sind Web-Clients, die die benötigten Informationen aus dem vom Server gesendeten Datenstrom „HTML“ beziehen. Sehr oft hat man keinen Einfluss auf diesen Datenstrom. Dies ist der Fall, wenn man einen Client für einen im Netzwerk vorhandenen Webdienst geschrieben hat, der von jemand anderem verwaltet wird. Nehmen wir ein Beispiel. Nehmen wir an, unser Dienst zur Simulation von Steuerberechnungen wurde von einem Unternehmen X entwickelt. Derzeit sendet der Dienst die Simulationen in einer Tabelle vom Typ HTML, und unser Client nutzt diese Tatsache, um sie abzurufen. Er vergleicht dabei jede Zeile der Serverantwort mit dem regulären Ausdruck:

         // Das Modell einer Zeile der Simulationstabelle
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");

Nehmen wir nun an, der Entwickler der Anwendung ändert das Erscheinungsbild der Antwort, indem er die Simulationen nicht mehr in einem Array, sondern in einer Liste in folgender Form bereitstellt:

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

In diesem Fall muss unser Webclient neu geschrieben werden. Das ist die ständige Gefahr, der Webclients von Anwendungen ausgesetzt sind, die man nicht selbst kontrolliert. XML kann eine Lösung für dieses Problem bieten:

  • Anstatt HTML zu generieren, wird der Simulationsdienst XML generieren. In unserem Beispiel könnte dies lauten
<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 vorgibt, wie diese Antwort visuell dargestellt werden soll XML
  • Die programmierten Web-Clients würden dieses Stylesheet ignorieren und die Informationen direkt aus dem Datenstrom „XML“ der Antwort abrufen

Möchte der Entwickler des Dienstes die visuelle Darstellung der bereitgestellten Ergebnisse ändern, muss er das Stylesheet anpassen und nicht den Datenstrom XML. Dank des Stylesheets zeigen die Browser die neue visuelle Darstellung an, und die programmierten Web-Clients müssen nicht geändert werden. So könnten neue Versionen unseres Simulationsdienstes entwickelt werden:

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