Skip to content

5. Aplikacja IMPOTS

5.1. Wprowadzenie

W tym miejscu powracamy do aplikacji IMPOTS, która jest wielokrotnie wykorzystywana w materiałach do nauki języka Java tego samego autora. Przypomnijmy sobie stawiane zadanie. Chodzi o napisanie aplikacji umożliwiającej obliczenie podatku podatnika. Rozważamy uproszczony przypadek podatnika, który ma do zadeklarowania wyłącznie swoje wynagrodzenie:

  • liczba udziałów pracownika wynosi nbParts = nbEnfants/2 + 1, jeśli jest on osobą stanu wolnego, nbEnfants/2+2, jeśli jest żonaty, gdzie nbEnfants to liczba jego dzieci.
  • jeśli ma co najmniej troje dzieci, przysługuje mu dodatkowa połowa udziału
  • oblicza się jego dochód podlegający opodatkowaniu R = 0,72 * S, gdzie S to jego roczne wynagrodzenie
  • oblicza się jego współczynnik rodzinny QF = R / nbParts
  • oblicza się jego podatek I. Rozważmy następującą tabelę:
12620,0
0
0
13190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31810
0,2
3309,5
39 970
0,25
4900
48360
0,3
6898,5
55790
0,35
9316,5
92970
0,4
12106
127860
0,45
16754,5
151250
0,50
23147,5
172040
0,55
30710
195000
0,60
39312
0
0,65
49062

Każdy wiersz zawiera 3 pola. Aby obliczyć podatek I, należy znaleźć pierwszy wiersz, w którym QF <= pole1. Na przykład, jeśli QF = 23000, znajdziemy wiersz

    24740        0.15        2072.5

Podatek I wynosi wówczas 0,15*R – 2072,5*nbParts. Jeśli QF jest takie, że warunek QF<=pole1 nigdy nie jest spełniony, wówczas stosuje się współczynniki z ostatniego wiersza. W tym przypadku:

    0                0.65        49062

co daje podatek I = 0,65 * R – 49062 * nbParts.

Dane definiujące poszczególne progi podatkowe znajdują się w bazie danych ODBC-MySQL. MySQL jest wersją SGBD dostępną w domenie publicznej, którą można wykorzystywać na różnych platformach, w tym Windows i Linux. Za pomocą tego pliku SGBD utworzono bazę danych dbimpots zawierającą jedną tabelę o nazwie impots. Dostęp do bazy jest kontrolowany za pomocą loginu i hasła, w tym przypadku admimpots/mdpimpots. Poniższy zrzut ekranu pokazuje, jak korzystać z bazy dbimpots przy użyciu pliku MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> use dbimpots;
Database changed

mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)

mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)

mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)

mysql>quit

Baza danych dbimpots jest przekształcana w źródło danych ODBC w następujący sposób:

  • uruchamia się 32-bitowy menedżer źródeł danych ODBC

Image

  • używa się przycisku [Add], aby dodać nowe źródło danych ODBC

Image

  • wybieramy sterownik MySQL i wykonujemy [Terminer]

54321

Image

  • sterownik MySQL wymaga podania pewnych informacji:
1
nazwa DSN, jaką należy nadać źródłu danych ODBC – może być dowolna
2
maszyna, na której uruchomiono procesy SGBD i MySQL – w tym przypadku localhost. Warto zauważyć, że bazą danych może być baza zdalna. Lokalne aplikacje korzystające ze źródła danych ODBC nie zauważyłyby tej zmiany. Dotyczyłoby to w szczególności naszej aplikacji Java.
3
baza danych MySQL, z której należy korzystać. MySQL to SGBD, który obsługuje relacyjne bazy danych, czyli zbiory tabel połączonych ze sobą relacjami. W tym miejscu podaje się nazwę obsługiwanej bazy danych.
4
nazwa użytkownika posiadającego uprawnienia dostępu do tej bazy
5
jego hasło

Zdefiniowano dwie klasy służące do obliczania podatku: impots oraz impotsJDBC. Instancja klasy impots jest tworzona na podstawie danych dotyczących przedziałów podatkowych przekazanych jako parametry w tablicach:

// utworzenie klasy podatków

public class impots{

  // dane niezbędne do obliczenia podatku
   // pochodzą ze źródła zewnętrznego

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

   // pusty konstruktor
  protected impots(){}

   // konstruktor
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // sprawdzamy, czy wszystkie 3 tabele mają ten sam rozmiar
    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+")");
    // w porządku
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//konstruktor

   // obliczenie podatku
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // obliczenie liczby udziałów
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // obliczenie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // obliczenie podatku
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // zwrot wyniku
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//obliczyć
}//klasa

Klasa impotsJDBC wywodzi się z poprzedniej klasy impots. Instancja klasy impotsJDBC jest tworzona na podstawie danych dotyczących przedziałów podatkowych przechowywanych w bazie danych. Informacje niezbędne do uzyskania dostępu do tej bazy danych są przekazywane jako parametry do konstruktora:

// importowane pakiety
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // dodanie konstruktora umożliwiającego tworzenie
   // tabele limitów, coeffr, coeffn na podstawie tabeli
   // podatków z bazy danych
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nazwa bazy danych DSN
     // userIMPOTS, mdpIMPOTS: login/hasło dostępu do bazy

     // tabele danych
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // połączenie z bazą danych
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // utworzenie obiektu Statement
    Statement S=connect.createStatement();
    // zapytanie SELECT
    String select="select limites, coeffr, coeffn from impots";
    // wykonanie zapytania
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // przetwarzanie bieżącego wiersza
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// następny wiersz
     // zamknięcie zasobów
    RS.close();
    S.close();
    connect.close();
     // przeniesienie danych do tablic ograniczonych
    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
}//klasa

Po utworzeniu instancji klasy impotsJDBC można wielokrotnie wywoływać jej metodę calculer w celu obliczenia podatku:

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

Pozyskanie trzech niezbędnych danych może odbywać się na wiele sposobów. Zaletą klasy impotsJDBC jest to, że wystarczy skupić się właśnie na tym pozyskiwaniu. Po uzyskaniu tych trzech informacji (stan cywilny, liczba dzieci, roczne wynagrodzenie) wywołanie metody calculer klasy impotsJDBC pozwala uzyskać kwotę podatku do zapłaty.

5.2. Wersja 1

Rozważamy scenariusz aplikacji internetowej, która udostępnia użytkownikowi interfejs HTML w celu uzyskania trzech parametrów niezbędnych do obliczenia podatku:

  • stan cywilny (żonaty/zamężna lub nie)
  • liczba dzieci
  • roczne wynagrodzenie

Image

Formularz jest wyświetlany na następującej stronie: JSP:

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

<%
     // pobieranie atrybutów przekazanych przez główny serwlet
  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(){
           // wyczyszczenie formularza
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//z
      }//usunąć
        </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>
    <%
         // czy są jakieś błędy
      if(erreurs!=null){
           // wyświetlanie błędów
        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>

Strona JSP ogranicza się do wyświetlania informacji przekazanych jej przez główny serwlet aplikacji:

     // pobieramy atrybuty przekazane przez główny serwlet
  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
atrybuty przycisków opcji „tak” i „nie” – mogą przyjmować wartości "checked" lub "", aby odpowiednio włączyć lub wyłączyć dany przycisk opcji
txtEnfants
liczba dzieci podatnika
txtSalaire
jego roczne wynagrodzenie
txtImpots
kwota podatku do zapłaty
erreurs
ewentualna lista błędów, jeśli errors!=null

Strona wysyłana do klienta zawiera skrypt JavaScript z funkcją effacer powiązaną z przyciskiem „Effacer”, którego zadaniem jest przywrócenie formularza do stanu początkowego: przycisk niezaznaczony, pola wprowadzania danych puste. Należy zauważyć, że tego rezultatu nie dałoby się uzyskać przy użyciu przycisku HTML typu „reset”. W rzeczywistości, gdy stosuje się ten typ przycisku, przeglądarka przywraca formularz do stanu, w jakim go otrzymała. Tymczasem w naszej aplikacji przeglądarka otrzymuje formularze, które mogą nie być puste.

Główny serwlet aplikacji nosi nazwę main.java, a jego kod wygląda następująco:

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

public class main extends HttpServlet{

    // zmienne instancji
    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{

        // czy inicjalizacja przebiegła pomyślnie?
        if(msgErreur!=null){
             // przekazujemy kontrolę do strony błędu
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // atrybuty żądania
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // pobieramy parametry żądania
        String optMarie=request.getParameter("optMarie");              // stan cywilny
        String txtEnfants=request.getParameter("txtEnfants");       // liczba dzieci
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // roczne wynagrodzenie
        if(txtSalaire==null) txtSalaire="";

         // czy mamy wszystkie oczekiwane parametry
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // brakuje parametrów
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // przekazujemy dane do adresu URL wyświetlającego podatek
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // mamy wszystkie parametry  sprawdzamy je
        ArrayList erreurs=new ArrayList();
         // stan cywilny
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // błąd
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // liczba dzieci
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // błąd
            erreurs.add("Nombre d'enfants incorrect");
        }
         // wynagrodzenie
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // błąd
            erreurs.add("Salaire incorrect");
        }

         // jeśli występują błędy, przekazujemy je jako atrybut zapytania
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // można obliczyć podatek do zapłaty
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // pozostałe atrybuty zapytania
        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);

         // przekazujemy kontrolę do adresu URL wyświetlającego podatek
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // pobieramy parametry inicjalizacyjne
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parametry w porządku?
        if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         // tworzymy instancję impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • Metoda init serwletu wykonuje dwie czynności:
    • pobiera parametry inicjalizacyjne. Pozwalają one na połączenie się z bazą danych ODBC zawierającą dane dotyczące różnych przedziałów podatkowych (DSNimpots, admimpots, mdpimpots) oraz URL stron powiązanych z aplikacją: urlAffichageImpots dla formularza, urlErreur dla strony błędu.
    • tworzy instancję klasy impotsJDBC

W obu przypadkach obsługiwane są ewentualne błędy, a komunikat o błędzie jest zapisywany w zmiennej msgErreur.

  • Metoda doGET
    • sprawdza najpierw, czy serwlet został poprawnie zainicjowany. Jeśli tak nie jest, wyświetlana jest strona błędu
    • pobiera oczekiwane parametry z formularza podatkowego: optMarie, txtEnfants, txtSalaire. Jeśli brakuje któregoś z nich (==null), wysyłany jest pusty formularz podatkowy. Można by pomyśleć, że sprawdzanie poprawności parametru optMarie jest zbędne. Jest to wartość przycisku opcji i może on przyjąć tutaj tylko jedną z wartości: „oui” lub „non”. Zapomina się jednak, że nic nie stoi na przeszkodzie, aby program bezpośrednio zwrócił się do serwletu, wysyłając mu dowolne parametry. Nigdy nie ma pewności, że faktycznie nawiązano połączenie z przeglądarką. Pominięcie tej kwestii może prowadzić do luk w zabezpieczeniach aplikacji, a zresztą często spotyka się je nawet w aplikacjach komercyjnych.
    • Sprawdzana jest poprawność każdego z trzech pobranych parametrów. Każdy wykryty błąd jest dodawany do listy błędów (ArrayList błędy). Jeśli nie ma błędów, kwota podatku jest obliczana, w przeciwnym razie nie jest.
    • Informacje niezbędne do wyświetlenia strony są umieszczane w atrybutach żądania, a następnie wyświetlany jest formularz podatkowy

Strona błędu JSP wygląda następująco:

<%
     // jspService
   // wystąpił błąd
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- początek strony HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

Aplikacja internetowa nosi nazwę „impots” i jest skonfigurowana w pliku server.xml serwera Tomcat w następujący sposób:

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

Katalog aplikacji zawiera następujące foldery i pliki:

Image

Image

Image

Image

Plik konfiguracyjny aplikacji impots o nazwie web.xml ma następującą treść:

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

Główny serwlet nosi nazwę main i ma alias /main. Jest więc dostępny poprzez URL i http://localhost:8080/impots/main.

Oto kilka przykładów zastosowań:

Aby poprawnie się zainicjować, serwlet musi mieć dostęp do bazy danych o nazwie mysql-dbimpots. Oto strona, która się wyświetli, jeśli na przykład serwer MySQL nie jest uruchomiony, a w związku z tym baza mysql-dbimpots jest niedostępna:

Image

Strona wyświetlana w przypadku wprowadzenia nieprawidłowych danych wygląda następująco:

Image

Jeśli dane są poprawne, podatek zostanie obliczony:

Image

5.3. Wersja 2

W poprzednim przykładzie poprawność parametrów formularza txtEnfants, txtSalaire jest sprawdzana przez serwer. Proponujemy tutaj sprawdzenie ich za pomocą skryptu JavaScript umieszczonego na stronie formularza. Wówczas to przeglądarka dokonuje weryfikacji parametrów. Serwer jest angażowany tylko wtedy, gdy parametry są prawidłowe. W ten sposób oszczędzamy „przepustowość”. Strona wyświetlająca JSP wygląda następująco:

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

<html>

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

      function calculer(){
           // sprawdzanie parametrów przed wysłaniem ich na serwer
        with(document.frmImpots){
          //liczba elementów podrzędnych
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // szablon nie został zweryfikowany
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //wynagrodzenie
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // szablon nie został zweryfikowany
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//jeśli
          // w porządku – wysyłamy formularz na serwer
          submit();
        }//with
      }//obliczyć        
        </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>

Należy zwrócić uwagę na następujące zmiany:

  • przycisk Calculer nie jest już typu submit, lecz typu button powiązanego z funkcją o nazwie calculer. To właśnie ta funkcja przeprowadzi analizę pól txtEnfants i txTsalaire. Jeśli są one poprawne, wartości z formularza zostaną wysłane na serwer (submit), w przeciwnym razie wyświetlony zostanie komunikat o błędzie.

Oto przykładowy komunikat wyświetlany w przypadku błędu:

Image

5.4. Wersja 3

Nieznacznie modyfikujemy aplikację, aby wprowadzić do niej pojęcie sesji. Zakładamy teraz, że aplikacja służy do symulacji obliczania podatków. Użytkownik może wówczas symulować różne „konfiguracje” podatnika i sprawdzić, jaka byłaby kwota podatku do zapłaty w każdym z tych przypadków. Poniższa strona WEb przedstawia przykładowy wynik:

Image

Główny serwlet został zmodyfikowany i nosi teraz nazwę simulations. W aplikacji impots jest on skonfigurowany w następujący sposób:

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

Główna serwlet nosi nazwę „simulations” i opiera się na pliku klasy simulations.class. Ma alias /simulations, dzięki czemu jest dostępna poprzez URL i http://localhost:8080/impots/simulations. Posiada te same parametry inicjalizacyjne co omówiony wcześniej serwlet główny w zakresie dostępu do bazy danych. Pojawia się nowy parametr, urlSimulationsImpots, który jest odpowiednikiem URL dla strony symulacyjnej JSP (tej, która została przedstawiona nieco wyżej).

Serwlet simulations.java jest zbliżony do serwletu main.java. Różni się od niego następującymi głównymi cechami:

  • serwlet główny oblicza wartość txtImpots na podstawie parametrów optmarie, txtEnfants oraz txtSalaire i przekazuje tę wartość do strony wyświetlającej JSP
  • serwlet „simulations” oblicza w ten sam sposób wartość txtImpots, zapisuje parametry (optMarie, txtEnfants, txtsalaire, txtImpots) na liście o nazwie „symulacje”. To właśnie ta lista jest przekazywana jako parametr do strony wyświetlającej JSP. Aby lista ta zawierała wszystkie symulacje przeprowadzone przez użytkownika, jest ona zapisywana jako atrybut bieżącej sesji.

Serwlet „simulations” wygląda następująco (zachowano tylko te linijki kodu, które różnią się od kodu poprzedniej aplikacji):

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

public class simulations extends HttpServlet{

    // zmienne instancji
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         // pobieramy poprzednie symulacje z sesji
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // umieszczamy symulacje w bieżącym zapytaniu
        request.setAttribute("simulations",simulations);

         // inne atrybuty zapytania
...........

         // czy mamy wszystkie oczekiwane parametry
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // przekazujemy kontrolę do adresu URL wyświetlającego symulacje obliczeń podatkowych
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // mamy wszystkie parametry  sprawdzamy je
...........

         // jeśli występują błędy, przekazujemy je jako atrybuty zapytania
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // można obliczyć podatek do zapłaty
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // dodajemy bieżący wynik do poprzednich symulacji
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // nowa wartość symulacji jest zapisywana w sesji
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // inne atrybuty zapytania
..........

         // przekazujemy kontrolę do adresu URL wyświetlającego symulacje
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // pobieramy parametry inicjalizacyjne
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parametry w porządku?
.........................
    }
}

Strona wyświetlania JSP, odpowiadająca stronie simulationsImpots.jsp, wygląda teraz następująco (zachowano jedynie kod różniący się od strony wyświetlania JSP z poprzedniej aplikacji).

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

<%
     // pobieramy atrybuty przekazane przez główny serwlet
...........
  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>
    <%
         // czy występują błędy?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // wyniki symulacji
        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. Wersja 4

Teraz stworzymy samodzielną aplikację, która będzie klientem internetowym poprzedniej aplikacji /impots/simulations. Aplikacja ta będzie miała następujący interfejs graficzny:

nr
typ
nazwa
rola
1
JTextField
txtUrlServiceImpots
URL z serwisu symulacji obliczeń podatkowych
2
JRadioButton
rdOui
zaznaczone, jeśli jest w związku małżeńskim
3
JRadioButton
rdNon
zaznaczyć, jeśli nie jest żonaty/zamężna
4
JSpinner
spinEnfants
liczba dzieci podatnika (minimum = 0, maksimum = 20, przyrost = 1)
5
JTextField
txtSalaire
roczne wynagrodzenie podatnika w F
6
JList w JScrollPane
lstSimulations
lista symulacji

Menu „Podatki” składa się z następujących opcji:

opcja
główna
opcja
dodatkowa
nazwa
rola
Podatki
   
 
Oblicz
mnuCalculer
oblicza podatek do zapłaty, gdy wszystkie dane niezbędne do obliczenia są dostępne i poprawne
 
Usuń
mnuEffacer
przywraca formularz do stanu początkowego
 
Wyjdź
mnuQuitter
zamyka aplikację

Zasady działania

  • opcja menu Calculer pozostaje wyłączona, jeśli jedno z pól 1 lub 5 jest puste
  • wykryto niepoprawną składniowo wartość URL w polu 1

Image

  • wykryto nieprawidłową kwotę wynagrodzenia

Image

  • zgłaszany jest każdy błąd połączenia z serwerem (w przykładzie 1 poniżej port jest nieprawidłowy – w przykładzie 2 żądany URL nie istnieje – w przykładzie 3 baza danych MySQL nie została uruchomiona)

Image

Image

Image

  • Jeśli wszystko jest w porządku, wyświetlane są symulacje

Image

Podczas pisania programowanego klienta internetowego konieczne jest dokładne poznanie treści, jaką serwer wysyła w odpowiedzi na różne możliwe żądania klienta. Serwer wysyła zestaw wierszy HTML zawierający informacje przydatne oraz inne, które służą wyłącznie do formatowania strony HTML. Wyrażenia regularne w Javie mogą pomóc nam w odnalezieniu przydatnych informacji w strumieniu wierszy wysyłanych przez serwer. W tym celu musimy znać dokładny format poszczególnych odpowiedzi serwera. W tym miejscu wykorzystamy już omówionego klienta internetowego, który pozwala wyświetlić na ekranie odpowiedź serwera na żądanie o numerze URL. Żądany obiekt URL będzie należał do naszej usługi symulacji obliczeń podatkowych http://localhost:8080/impots/simulations, do której będziemy mogli przekazać parametry w postaci http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

Wywołajmy URL, podczas gdy baza MySQL umożliwiająca utworzenie obiektu typu impots nie została uruchomiona:


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

Aby odczytać komunikat o błędzie, klient internetowy musi wyszukać w odpowiedzi serwera internetowego wiersz zawierający tekst „Aplikacja niedostępna”. Teraz uruchommy bazę MySQL i wywołajmy tę samą bazę URL, przekazując jej oczekiwane wartości (akceptuje je zarówno w postaci GET, jak i POST – patrz kod Java serwera):


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(){
...................................................
      }//usuń

      function calculer(){
...................................................
      }//obliczyć
                </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>

Poniżej znajduje się pełna treść dokumentu HTML przesłanego przez serwer. Wyniki poszczególnych symulacji zawarte są w jedynej tabeli występującej w tym dokumencie. Wyrażenie regularne umożliwiające wyodrębnienie istotnych informacji z dokumentu może mieć następującą postać:

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

gdzie cztery wyrażenia w nawiasach reprezentują cztery informacje, które należy pobrać.

Mamy teraz wytyczne dotyczące tego, co należy zrobić, gdy użytkownik zażąda obliczenia podatku za pomocą poprzedniego interfejsu graficznego:

  • sprawdzić, czy wszystkie dane w interfejsie są prawidłowe, i w razie potrzeby zgłosić błędy;
  • nawiązać połączenie z URL wskazanym w polu 1. W tym celu należy postępować zgodnie z modelem ogólnego klienta internetowego, który został już przedstawiony i omówiony
  • w strumieniu odpowiedzi serwera, użyj wyrażeń regularnych, aby:
    • znalezienia komunikatu o błędzie, jeśli taki występuje
    • znalezienie wyników symulacji, jeśli nie ma błędu

Kod związany z menu calculer jest następujący:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // obliczenie podatku
         // weryfikacja URL usługa
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // komunikat o błędzie
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // fokus na błędnym polu
            txtURLServiceImpots.requestFocus();
             // powrót do interfejsu
            return;
        }
     // weryfikacja wynagrodzenia
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // komunikat o błędzie
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // fokus na polu z błędem
      txtSalaire.requestFocus();
       // powrót do interfejsu
      return;
    }
     // liczba dzieci
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // obliczanie podatku
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // wyświetlanie błędu
            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{
         // obliczenie podatku
         // urlImpots: URL z urzędu skarbowego
         // stan cywilny: true, jeśli jest żonaty/zamężna, w przeciwnym razie false
         // nbEnfants: liczba dzieci
         // wynagrodzenie: roczne wynagrodzenie

         // z urlImpots pobierane  informacje niezbędne do połączenia z serwerem urzędu skarbowego
        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();

         // dane lokalne
        Socket  client=null;                        // klient
        BufferedReader IN=null;                    // strumień odczytu klienta
        PrintWriter OUT=null;                        // strumień zapisu klienta
        String réponse=null;                        // odpowiedź serwera
         // szukany wzorzec w nagłówkach HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // wzorzec prawidłowej odpowiedzi
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // wynik porównania z wzorcem
        Matcher résultat=null;

        try{
             // nawiązywanie połączenia z serwerem
            client=new Socket(host,port);

            // tworzy się strumienie wejścia-wyjścia klienta TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // wysyłamy żądanie URL  wysyłamy nagłówki 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("");

             // odczytuje się pierwszy wiersz odpowiedzi
            réponse=IN.readLine();
             // porównujemy wiersz HTTP z wzorcem prawidłowej odpowiedzi
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // wystąpił błąd dotyczący URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(wynik)

             // odczytujemy odpowiedź  do końca nagłówków, szukając ewentualnego pliku cookie
            while((réponse=IN.readLine())!=null){
                 // pusta linia?
                if(réponse.equals("")) break;
                // wiersz HTTP nie jest pusty
                 // jeśli nie ma tokenu sesji, szuka się go
                if (JSESSIONID.equals("")){
                    // porównujemy wiersz HTTP z wzorcem pliku cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // znaleziono plik cookie z tokenem
                        JSESSIONID=résultat.group(1);
                    }//if(wynik)
                }//if(JSESSIONID)
            }//while

             // koniec z nagłówkami HTTP  przechodzimy do kodu HTML
             // w celu pobrania symulacji
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // to już koniec
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

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

        // szablon jednej linii z tabeli symulacji
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // szablon wiersza z listy błędów
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // wynik porównania z modelem
        Matcher résultat=null;
         // symulacje
        ArrayList listeSimulations=new ArrayList();

         // odczytuje się wszystkie wiersze  do końca
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // kontynuacja
             // porównuje się wiersz z wzorcem błędu, jeśli nie dotarto jeszcze do części dotyczącej symulacji
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // komunikat o błędzie
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // zakończono
                    return listeSimulations;
                }//if
            }//if
             // porównujemy wiersz z modelem symulacji
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // znaleziono wiersz w tabeli
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // symulacja zakończyła się powodzeniem
                simulationRéussie=true;
            }//if
        }//while
        // koniec
        return listeSimulations;
    }

Wyjaśnijmy nieco ten kod:

  • procedura mnuCalculer_actionPerformed sprawdza, czy dane z interfejsu są prawidłowe. Jeśli nie są, generowany jest komunikat o błędzie i procedura zostaje zakończona. Jeśli są prawidłowe, uruchamiana jest procedura calculerImpots.
  • Procedura calculerImpots rozpoczyna się od utworzenia procedury URL, o którą musi poprosić
         // pobieramy z urlImpots informacje niezbędne do połączenia z serwerem podatkowym
        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();
........................
  • a następnie łączy się z tym URL, wysyłając odpowiednie nagłówki HTTP:
             // nawiązuje się połączenie z serwerem
            client=new Socket(host,port);

            // tworzy się przepływy przychodzące i wychodzące klienta TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // żądanie URL  wysyłanie nagłówków 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("");
  • Po wysłaniu żądania nasz klient internetowy oczekuje na odpowiedź. Najpierw otrzymuje nagłówki HTTP z odpowiedzi serwera. Przegląda te nagłówki w poszukiwaniu tokenu sesji. Musi bowiem odesłać ten token do serwera, aby ten mógł śledzić różne przeprowadzone symulacje. Warto tutaj zauważyć, że prostszym rozwiązaniem byłoby, gdyby to sam klient zapamiętywał poszczególne symulacje przeprowadzane przez użytkownika. Niemniej jednak pozostajemy przy koncepcji odsyłania tokenu, aby przedstawić nowy przykład zarządzania sesją. Pierwszy wiersz odpowiedzi jest przetwarzany osobno. Musi on bowiem mieć postać HTTP/wersja 200 OK, aby potwierdzić, że żądany URL rzeczywiście istnieje. Jeśli więc nie ma ona tej postaci, wnioskuje się, że użytkownik zażądał nieprawidłowego tokenu URL i informuje się go o tym.
         // wyszukiwanie wzorca w nagłówkach HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // wzór prawidłowej odpowiedzi
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // odczytuje się pierwszy wiersz odpowiedzi
            réponse=IN.readLine();
             // porównujemy wiersz HTTP z wzorem prawidłowej odpowiedzi
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // wystąpił błąd dotyczący URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(wynik)

             // odczytujemy odpowiedź  do końca nagłówków, szukając ewentualnego pliku cookie
            while((réponse=IN.readLine())!=null){
                 // pusta linia?
                if(réponse.equals("")) break;
                // wiersz HTTP nie jest pusty
                 // jeśli nie ma tokenu sesji, szuka się go
                if (JSESSIONID.equals("")){
                    // porównujemy wiersz HTTP z wzorcem pliku cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // znaleziono plik cookie z tokenem
                        JSESSIONID=résultat.group(1);
                    }//if(wynik)
                }//if(JSESSIONID)
            }//while
  • po przetworzeniu nagłówków HTTP przechodzi się do części HTML odpowiedzi
             // koniec z nagłówkami HTTP  przechodzimy do kodu HTML
             // w celu pobrania symulacji
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
  • Procedura getSimulations zwraca listę symulacji, o ile takie istnieją; lista ta będzie pusta, jeśli serwer zwróci komunikat o błędzie. W takim przypadku komunikat ten zostanie wyświetlony w oknie komunikatów. Jeśli lista nie jest pusta, zostanie wyświetlona w rozwijanej liście interfejsu graficznego.
  • Procedura getSimulations porównuje każdy wiersz odpowiedzi HTML z wyrażeniem regularnym reprezentującym komunikat o błędzie (Aplikacja niedostępna...) oraz z wyrażeniem regularnym reprezentującym symulację. W przypadku wykrycia komunikatu o błędzie jest on wyświetlany, a procedura kończy działanie. Jeśli znaleziono wynik symulacji, jest on dodawany do listy symulacji. Po zakończeniu procedury lista ta jest zwracana jako wynik.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // szablon jednej linii z tabeli symulacji
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // szablon wiersza z listy błędów
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. Wersja 5

W tym przypadku przekształcamy poprzednią samodzielną aplikację graficzną w aplet Java. Interfejs graficzny jest nieco inny. W przypadku aplikacji samodzielnej użytkownik sam podawał identyfikator URL usługi symulacji obliczeń podatkowych, a następnie aplikacja łączyła się z tym identyfikatorem URL. W tym przypadku aplikacją kliencką jest przeglądarka, a użytkownik zażąda adresu URL dokumentu HTML zawierającego aplet. Należy teraz pamiętać, że aplet Java może nawiązać połączenie sieciowe wyłącznie z serwerem, z którego został pobrany. Kod URL usługi symulacyjnej będzie zatem znajdował się na tym samym serwerze, co dokument HTML zawierający aplet. W naszym przykładzie w polu txtUrlServiceImpots zostanie umieszczony parametr inicjalizacyjny apletu; pole to nie będzie edytowalne przez użytkownika. W ten sposób otrzymamy następujący klient:

Image

Dokument HTML zawierający aplet nosi nazwę simulations.htm i ma następującą postać:

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

Aplet posiada parametr urlServiceImpots, który jest wartością URL usługi symulacji obliczeń podatkowych. Ten identyfikator URL jest względny i określany w odniesieniu do identyfikatora URL dokumentu o identyfikatorze HTML simulations.htm. Zatem jeśli przeglądarka pobierze ten dokument z identyfikatorem URL http://localhost:8080/impots/simulations.htm, to URL z serwisu symulacyjnego będzie wynosić http://localhost:8080/impots/simulations.. Gdyby ten plik URL miał adres http://stahe:8080/impots/simulations.htm, to URL z serwisu symulacyjnego brzmiałoby http://stahe:8080/impots/simulations.

Aplet appletImpots.java w całości przejmuje kod poprzedniej samodzielnej aplikacji graficznej, przestrzegając zasad przekształcania aplikacji graficznej w aplet.

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


   //Utworzenie ramki
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // inne inicjalizacje
    moreInit();
  }

   // inicjalizacja formularza
  private void moreInit(){
         // pobieramy parametr urlServiceImpots
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // brakujący parametr
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // koniec
            return;
        }
         // wpisujemy URL w odpowiednim polu
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // menu „Oblicz” zablokowane
    mnuCalculer.setEnabled(false);
     // pokrętło „Liczba dzieci” – od 0 do 20 dzieci
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

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

Gdy przeglądarka ładuje aplet, najpierw uruchamia jego procedurę init. W ramach tej procedury pobieramy wartość parametru urlServiceImpots z apletu, a następnie obliczamy pełną nazwę usługi symulacyjnej URL i umieszczamy tę wartość w polu txtURLServiceImpots tak, jakby to użytkownik ją wpisał. Po wykonaniu tej czynności nie ma już żadnej różnicy między obiema aplikacjami. W szczególności kod powiązany z menu Calculer jest identyczny. Oto przykład wykonania:

Image

5.7. Wnioski

Przedstawiliśmy różne wersje naszej aplikacji klient-serwer do obliczania podatków:

  • wersja 1: usługa jest obsługiwana przez zestaw serwletów i stron JSP, a klientem jest przeglądarka. Przeprowadza ona tylko jedną symulację i nie zapamiętuje poprzednich.
  • wersja 2: dodano kilka funkcji po stronie przeglądarki poprzez umieszczenie skryptów JavaScript w dokumencie HTML ładowanym przez przeglądarkę. Skrypty te sprawdzają poprawność parametrów formularza.
  • wersja 3: umożliwiono serwisowi zapamiętywanie różnych symulacji przeprowadzonych przez klienta poprzez zarządzanie sesją. Interfejs HTML został odpowiednio zmodyfikowany, aby je wyświetlać.
  • wersja 4: klient jest teraz samodzielną aplikacją graficzną. Pozwala nam to powrócić do tworzenia programowanych klientów internetowych.
  • wersja 5: klient przekształca się w aplet Java. Otrzymujemy w ten sposób aplikację klient-serwer w całości zaprogramowaną w języku Java, zarówno po stronie serwera, jak i po stronie klienta.

W tym miejscu można poczynić kilka uwag:

  • wersje od 1 do 3 obsługują przeglądarki, które nie mają żadnych innych możliwości poza wykonywaniem skryptów JavaScript. Należy zauważyć, że użytkownik zawsze ma możliwość zablokowania wykonywania tych skryptów. Wówczas aplikacja będzie działać tylko częściowo w wersji 1 (opcja „Usuń” nie będzie działać), a w ogóle nie będzie działać w wersjach 2 i 3 (opcje „Usuń” i „Oblicz” nie będą działać). Interesujące mogłoby być stworzenie wersji serwisu, która nie wykorzystuje skryptów JavaScript.
  • Wersja 4 wymaga, aby komputer kliencki posiadał maszynę wirtualną Java 2.
  • Wersja 5 wymaga, aby komputer kliencki posiadał przeglądarkę z wirtualną maszyną Java 2.

Tworząc serwis internetowy, należy zadać sobie pytanie, do jakich typów klientów chcemy dotrzeć. Jeśli chcemy dotrzeć do jak największej liczby klientów, napiszemy aplikację, która wysyła do przeglądarek wyłącznie kod HTML (bez JavaScriptu ani apletów). Jeśli pracujemy w intranecie i mamy kontrolę nad konfiguracją stanowisk w nim, możemy pozwolić sobie na większe wymagania wobec klienta, a poprzednia wersja 5 może wówczas być akceptowalna.

Wersje 4 i 5 to klienci internetowi, którzy pobierają potrzebne informacje ze strumienia HTML wysyłanego przez serwer. Bardzo często nie mamy kontroli nad tym strumieniem. Dzieje się tak, gdy napisaliśmy klienta dla istniejącej w sieci usługi internetowej, którą zarządza ktoś inny. Weźmy przykład. Załóżmy, że nasza usługa symulacji obliczeń podatkowych została stworzona przez firmę X. Obecnie usługa ta wysyła symulacje w tabeli HTML, a nasz klient wykorzystuje ten fakt, aby je pobrać. W ten sposób porównuje każdy wiersz odpowiedzi serwera z wyrażeniem regularnym:

         // szablon jednej linii tabeli symulacji
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");

Załóżmy teraz, że twórca aplikacji zmieni wygląd odpowiedzi, umieszczając symulacje nie w tabeli, ale na liście w postaci:

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

W takim przypadku nasz klient internetowy będzie wymagał przeróbki. Jest to stałe zagrożenie dla klientów internetowych aplikacji, nad którymi nie mamy kontroli. XML może stanowić rozwiązanie tego problemu:

  • zamiast generować HTML, usługa symulacji wygeneruje XML. W naszym przykładzie mogłoby to wyglądać następująco:
<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>
  • do tej odpowiedzi można by przypisać arkusz stylów, wskazujący przeglądarkom, jak ta odpowiedź ma wyglądać XML
  • klienty internetowe ignorowałyby ten arkusz stylów i pobierałyby informacje bezpośrednio ze strumienia XML zawartego w odpowiedzi

Jeśli twórca serwisu zechce zmienić wygląd wyników, które udostępnia, zmodyfikuje arkusz stylów, a nie plik XML. Dzięki arkuszowi stylów przeglądarki wyświetlą nowy wygląd, a programowe klienty internetowe nie będą wymagały modyfikacji. Można by zatem stworzyć nowe wersje naszego serwisu symulacyjnego:

  • wersja 6: usługa dostarcza odpowiedź XML wraz z arkuszem stylów przeznaczonym dla przeglądarek
  • wersja 7: klientem jest samodzielna aplikacja graficzna wykorzystująca odpowiedź XML z serwera
  • wersja 8: klientem jest aplet Java wykorzystujący odpowiedź serwera o treści XML