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

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

- wybieramy sterownik MySQL i wykonujemy [Terminer]
54321

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

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");
atrybuty przycisków opcji „tak” i „nie” – mogą przyjmować wartości "checked" lub "", aby odpowiednio włączyć lub wyłączyć dany przycisk opcji | |
liczba dzieci podatnika | |
jego roczne wynagrodzenie | |
kwota podatku do zapłaty | |
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:
Katalog aplikacji zawiera następujące foldery i pliki:




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:

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

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

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:

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:

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

- wykryto nieprawidłową kwotę wynagrodzenia

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



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

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¶m2=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ć:
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 są 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ź aż 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 aż 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ź aż 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:

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:

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