5. A aplicação de cálculo de impostos
5.1. Introdução
Aqui, revisitamos a aplicação IMPOTS, que é utilizada repetidamente no manual de Java do mesmo autor. Vamos rever o problema. O objetivo é escrever uma aplicação que calcule a obrigação fiscal de um contribuinte. Vamos considerar o caso simplificado de um contribuinte que tem apenas um único salário a declarar:
- calculamos o número de escalões de imposto do empregado como nbParts = nbEnfants / 2 + 1 se for solteiro, e nbEnfants / 2 + 2 se for casado, onde nbEnfants é o número de filhos.
- se tiver pelo menos três filhos, recebe uma meia quota adicional
- Calculamos o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- Calculamos o seu coeficiente familiar QF = R / nbParts
- calculamos o seu imposto I. Considere a seguinte tabela:
12620,0 | 0 | 0 |
13 190 | 0,05 | 631 |
15 640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31 810 | 0,2 | 3.309,5 |
39 970 | 0,25 | 4.900 |
48 360 | 0,3 | 6.898,5 |
55 790 | 0,35 | 9.316,5 |
92 970 | 0,4 | 12 106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23 147,5 |
172 040 | 0,55 | 30 710 |
195 000 | 0,60 | 39 312 |
0 | 0,65 | 49062 |
Cada linha tem 3 campos. Para calcular o imposto I, encontre a primeira linha em que QF <= campo1. Por exemplo, se QF = 23 000, a linha encontrada será
O Imposto I é então igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a condição QF<=field1 nunca seja satisfeita, então são utilizados os coeficientes da última linha. Aqui:
o que dá o imposto I = 0,65*R - 49062*nbParts.
Os dados que definem os diferentes escalões de imposto estão armazenados numa base de dados ODBC-MySQL. O MySQL é um SGBD de domínio público que pode ser utilizado em várias plataformas, incluindo Windows e Linux. Utilizando este SGBD, foi criada uma base de dados denominada dbimpots, contendo uma única tabela chamada impots. O acesso à base de dados é controlado por um nome de utilizador/palavra-passe, neste caso admimpots/mdpimpots. A captura de ecrã seguinte mostra como utilizar a base de dados dbimpots com o 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
A base de dados dbimpots é convertida numa fonte de dados ODBC da seguinte forma:
- Inicie o Administrador de Fontes de Dados ODBC de 32 bits

- Utilize o botão [Adicionar] para adicionar uma nova fonte de dados ODBC

- Selecione o controlador MySQL e clique em [Concluir]
54321

- O controlador MySQL solicita algumas informações:
1 | o nome do DSN a atribuir à fonte de dados ODBC — pode ser qualquer nome |
2 | a máquina na qual o SGBD MySQL está a ser executado — neste caso, localhost. Vale a pena notar que a base de dados poderia ser uma base de dados remota. As aplicações locais que utilizam a fonte de dados ODBC não teriam conhecimento disso. Este seria o caso, em particular, da nossa aplicação Java. |
3 | a base de dados MySQL a utilizar. O MySQL é um SGBD que gere bases de dados relacionais, que são conjuntos de tabelas ligadas entre si por relações. Aqui, especificamos o nome da base de dados que está a ser gerida. |
4 | O nome de um utilizador com direitos de acesso a esta base de dados |
5 | a sua palavra-passe |
Foram definidas duas classes para calcular o imposto: impots e impotsJDBC. Uma instância da classe impots é criada utilizando os dados das faixas de imposto passados como parâmetros em matrizes:
// creation of an impots class
public class impots{
// data required for tax calculation
// come from an external source
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// empty builder
protected impots(){}
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// check that the 3 arrays have the same size
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// it's good
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//manufacturer
// tAX CALCULATION
public long calculer(boolean marié, int nbEnfants, int salaire){
// calculating the number of shares
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calculation of taxable income & family quota
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// tAX CALCULATION
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// return result
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calculate
}//class
A classe impotsJDBC deriva da classe impots anterior. Uma instância da classe impotsJDBC é construída utilizando dados de escalões fiscais armazenados numa base de dados. A informação necessária para aceder a esta base de dados é passada como parâmetros ao construtor:
// imported packages
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// addition of a constructor for building
// limit tables, coeffr, coeffn from table
// database taxes
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: DSN database name
// userIMPOTS, mdpIMPOTS: database login/password
// data tables
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// connection to base
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// creation of a Statement object
Statement S=connect.createStatement();
// select request
String select="select limites, coeffr, coeffn from impots";
// query execution
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// running line operation
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// next line
// closing resources
RS.close();
S.close();
connect.close();
// data transfer to bounded arrays
int n=aLimites.size();
limites=new double[n];
coeffR=new double[n];
coeffN=new double[n];
for(int i=0;i<n;i++){
limites[i]=Double.parseDouble((String)aLimites.get(i));
coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
}//for
}//manufacturer
}//class
Depois de criada uma instância da classe impotsJDBC, o seu método calculate pode ser chamado repetidamente para calcular o imposto:
Os três dados necessários podem ser obtidos de várias formas. A vantagem da classe impotsJDBC é que só precisamos de nos preocupar em obter esses dados. Assim que as três informações (estado civil, número de filhos, salário anual) forem obtidas, chamar o método calculer da classe impotsJDBC dá-nos o valor do imposto a pagar.
5.2. Versão 1
Estamos no contexto de uma aplicação web que apresentaria uma interface HTML a um utilizador para obter os três parâmetros necessários para calcular o imposto:
- estado civil (casado ou não)
- número de filhos
- rendimento anual

O formulário é apresentado pela seguinte página JSP:
<%@ page import="java.util.*" %>
<%
// on récupère les attributs passés par la servlet principale
String chkoui=(String)request.getAttribute("chkoui");
String chknon=(String)request.getAttribute("chknon");
String txtEnfants=(String)request.getAttribute("txtEnfants");
String txtSalaire=(String)request.getAttribute("txtSalaire");
String txtImpots=(String)request.getAttribute("txtImpots");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
// raz du formulaire
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//with
}//effacer
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/impots/main" method="POST">
<table>
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
<input type="radio" name="optMarie" value="non" <%= chknon %>>non
</td>
</tr>
<tr>
<td>Nombre d'enfants</td>
<td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
</tr>
<tr>
<td>Salaire annuel</td>
<td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
</tr>
<tr>
<td><font color="green">Impôt</font></td>
<td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
</tr>
<tr></tr>
<tr>
<td><input type="submit" value="Calculer"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
</tr>
</table>
</form>
</center>
<%
// y-a-t-il des erreurs
if(erreurs!=null){
// affichage des erreurs
out.println("<hr>");
out.println("<font color=\"red\">");
out.println("Les erreurs suivantes se sont produites<br>");
out.println("<ul>");
for(int i=0;i<erreurs.size();i++){
out.println("<li>"+(String)erreurs.get(i));
}
out.println("</ul>");
out.println("</font>");
}
%>
</body>
</html>
A página JSP simplesmente apresenta as informações que lhe são passadas pelo servlet principal da aplicação:
// retrieve attributes passed by the main servlet
String chkoui=(String)request.getAttribute("chkoui");
String chknon=(String)request.getAttribute("chknon");
String txtEnfants=(String)request.getAttribute("txtEnfants");
String txtSalaire=(String)request.getAttribute("txtSalaire");
String txtImpots=(String)request.getAttribute("txtImpots");
ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
Os atributos "checked" e "unchecked" dos botões de opção podem ter os valores "checked" ou "unchecked" para indicar se o botão de opção correspondente está selecionado ou não | |
o número de filhos do contribuinte | |
o seu salário anual | |
o montante de impostos a pagar | |
uma lista de erros, se houver, se erros!=null |
A página enviada ao cliente contém um script JavaScript com uma função «clear» associada ao botão «Limpar», cujo objetivo é repor o formulário ao seu estado inicial: botões desmarcados, campos de entrada vazios. Note que este resultado não poderia ser alcançado com um botão «reset» em HTML. Com efeito, quando este tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. No entanto, na nossa aplicação, o navegador recebe formulários que podem não estar vazios.
O servlet principal da aplicação chama-se main.java, e o seu código é o seguinte:
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;
public class main extends HttpServlet{
// instance variables
String msgErreur=null;
String urlAffichageImpots=null;
String urlErreur=null;
String DSNimpots=null;
String admimpots=null;
String mdpimpots=null;
impotsJDBC impots=null;
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
// was the initialization successful?
if(msgErreur!=null){
// we hand over to the error page
request.setAttribute("msgErreur",msgErreur);
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// query attributes
String chkoui=null;
String chknon=null;
String txtImpots=null;
// retrieve query parameters
String optMarie=request.getParameter("optMarie"); // marital status
String txtEnfants=request.getParameter("txtEnfants"); // no. of children
if(txtEnfants==null) txtEnfants="";
String txtSalaire=request.getParameter("txtSalaire"); // annual salary
if(txtSalaire==null) txtSalaire="";
// do we have all the expected parameters
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// missing parameters
request.setAttribute("chkoui","");
request.setAttribute("chknon","checked");
request.setAttribute("txtEnfants","");
request.setAttribute("txtSalaire","");
request.setAttribute("txtImpots","");
// we hand over to the tax display url
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
// we have all the parameters - we check them
ArrayList erreurs=new ArrayList();
// marital status
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// error
erreurs.add("Etat marital incorrect");
optMarie="non";
}
// number of children
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// error
erreurs.add("Nombre d'enfants incorrect");
}
// salary
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// error
erreurs.add("Salaire incorrect");
}
// if there are errors, they are passed as query attributes
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
txtImpots="";
}else{
// you can calculate the tax payable
try{
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
}catch(Exception ex){}
}
// other query attributes
if(optMarie.equals("oui")){
request.setAttribute("chkoui","checked");
request.setAttribute("chknon","");
}else{
request.setAttribute("chknon","checked");
request.setAttribute("chkoui","");
}
request.setAttribute("txtEnfants",txtEnfants);
request.setAttribute("txtSalaire",txtSalaire);
request.setAttribute("txtImpots",txtImpots);
// we hand over to the tax display url
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parameters ok?
if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// create an instance of impotsJDBC
try{
impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
}catch(Exception ex){
msgErreur=ex.getMessage();
}
}
}
- O método init do servlet faz duas coisas:
- Recupera os seus parâmetros de inicialização. Estes permitem-lhe ligar-se à base de dados ODBC que contém os dados relativos aos vários escalões de imposto (DSNimpots, admimpots, mdpimpots) e os URLs das páginas associadas à aplicação: urlAffichageImpots para o formulário, urlErreur para a página de erro.
- Cria uma instância da classe impotsJDBC
Em ambos os casos, os erros potenciais são tratados e a mensagem de erro é armazenada na variável msgErreur.
- O método doGET
- verifica primeiro se o servlet foi inicializado corretamente. Se não for esse o caso, a página de erro é apresentada
- recupera os parâmetros esperados do formulário fiscal: optMarie, txtEnfants, txtSalaire. Se algum deles estiver em falta (==null), é enviado um formulário fiscal vazio. Poder-se-ia pensar que a validação do parâmetro optMarie é desnecessária. Este parâmetro corresponde ao valor de um botão de opção e, neste caso, só pode assumir um dos valores «sim» ou «não». No entanto, isto ignora o facto de que nada impede um programa de consultar diretamente o servlet, enviando-lhe os parâmetros que deseja. Nunca se pode ter a certeza de que se está realmente ligado a um navegador. Ignorar este ponto pode levar a vulnerabilidades de segurança na aplicação e, na verdade, é comum encontrar tais problemas mesmo em aplicações comerciais.
- A validade de cada um dos três parâmetros recuperados é verificada. Quaisquer erros encontrados são adicionados a uma lista de erros (ArrayList errors). Se não houver erros, o valor do imposto é calculado; caso contrário, não é.
- As informações necessárias para apresentar a página são definidas como atributos de pedido, sendo então apresentado o formulário de impostos
A página JSP de erro é a seguinte:
<%
// jspService
// une erreur s'est produite
String msgErreur= (String)request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impots</h3>
<hr>
Application indisponible(<%= msgErreur %>)
</body>
</html>
A aplicação web chama-se impots e está configurada no ficheiro server.xml do Tomcat da seguinte forma:
O diretório da aplicação contém os seguintes diretórios e ficheiros:




O ficheiro de configuração web.xml para a aplicação impots é o seguinte:
<?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>
O servlet principal chama-se main e tem o alias /main. Por conseguinte, é acessível através do URL http://localhost:8080/impots/main.
Aqui estão alguns exemplos de aplicação:
Para ser inicializado corretamente, o servlet deve ter acesso à base de dados mysql-dbimpots. Aqui está a página apresentada se, por exemplo, o servidor MySQL não estiver a funcionar e a base de dados mysql-dbimpots estiver, por isso, inacessível:

A página exibida em caso de entrada incorreta é a seguinte:

Se os dados introduzidos estiverem corretos, o imposto é calculado:

5.3. Versão 2
No exemplo anterior, o servidor valida os parâmetros txtEnfants e txtSalaire no formulário. Aqui, propomos validá-los utilizando um script JavaScript incluído na página do formulário. O navegador realiza então a validação dos parâmetros. O servidor só é contactado se os parâmetros forem válidos. Isto poupa «largura de banda». A página de apresentação JSP fica da seguinte forma:
<%@ page import="java.util.*" %>
............
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
.......
}//effacer
function calculer(){
// vérification des paramètres avant de les envoyer au serveur
with(document.frmImpots){
//nbre d'enfants
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salaire
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// le modéle n'est pas vérifié
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//if
// c'est bon - on envoie le formulaire au serveur
submit();
}//with
}//calculer
</script>
</head>
<body background="/impots/images/standard.jpg">
........
<td><input type="button" value="Calculer" onclick="calculer()"></td>
<td><input type="button" value="Effacer" onclick="effacer()"></td>
.....
</body>
</html>
Observe as seguintes alterações:
- O botão «Calculate» já não é um botão de envio, mas sim um botão normal associado a uma função chamada «calculate». Esta função irá validar os campos «txtEnfants» e «txtSalaire». Se forem válidos, os dados do formulário serão enviados para o servidor; caso contrário, será apresentada uma mensagem de erro.
Eis um exemplo do que é apresentado em caso de erro:

5.4. Versão 3
Estamos a fazer uma ligeira modificação na aplicação para introduzir o conceito de sessão. Consideramos agora a aplicação como uma ferramenta de simulação de cálculo de impostos. Um utilizador pode então simular diferentes «cenários» de contribuintes e ver qual seria a obrigação fiscal para cada um. A página web abaixo fornece um exemplo do que poderia ser alcançado:

O servlet principal foi modificado e chama-se agora simulations. Está configurado da seguinte forma na aplicação impots:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
....
</servlet>
<servlet>
<servlet-name>simulations</servlet-name>
<servlet-class>simulations</servlet-class>
<init-param>
<param-name>urlSimulationImpots</param-name>
<param-value>/simulationsImpots.jsp</param-value>
</init-param>
<init-param>
<param-name>DSNimpots</param-name>
<param-value>mysql-dbimpots</param-value>
</init-param>
<init-param>
<param-name>admimpots</param-name>
<param-value>admimpots</param-value>
</init-param>
<init-param>
<param-name>mdpimpots</param-name>
<param-value>mdpimpots</param-value>
</init-param>
<init-param>
<param-name>urlErreur</param-name>
<param-value>/erreur.jsp</param-value>
</init-param>
</servlet>
......
<servlet-mapping>
<servlet-name>simulations</servlet-name>
<url-pattern>/simulations</url-pattern>
</servlet-mapping>
</web-app>
O servlet principal chama-se simulations e baseia-se no ficheiro simulations.class. Tem o alias /simulations, o que o torna acessível através do URL http://localhost:8080/impots/simulations. Tem os mesmos parâmetros de inicialização que o servlet principal discutido anteriormente relativamente ao acesso à base de dados. Surge um novo parâmetro, **urlSimulationsImpots,** que é o URL da página JSP de simulação (a que acabou de ser apresentada acima).
O servlet simulations.java é semelhante ao servlet main.java. Difere deste nas seguintes aspetos principais:
- O servlet principal calcula um valor para `txtImpots` com base nos parâmetros `optmarie`, `txtEnfants` e `txtSalaire`, e passa esse valor para a página JSP de exibição
- O servlet simulations calcula o valor de txtImpots da mesma forma e armazena os parâmetros (optMarie, txtEnfants, txtsalaire, txtImpots) numa lista chamada simulations. Esta lista é passada como parâmetro para a página JSP de visualização. Para garantir que esta lista contém todas as simulações realizadas pelo utilizador, é armazenada como um atributo da sessão atual.
O servlet de simulações é o seguinte (foram incluídas apenas as linhas de código que diferem das da aplicação anterior):
import java.io.*;
.......
public class simulations extends HttpServlet{
// instance variables
String msgErreur=null;
String urlSimulationImpots=null;
String urlErreur=null;
...........
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
...........
// retrieve previous simulations from the session
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// put the simulations in the current query
request.setAttribute("simulations",simulations);
// other query attributes
...........
// do we have all the expected parameters
if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
// we hand over to the url for displaying tax calculation simulations
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
// we have all the parameters - we check them
...........
// if there are errors, they are passed as query attributes
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
}else{
try{
// you can calculate the tax payable
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// the current result is added to the previous simulations
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// the new simulations value is added to the session
session.setAttribute("simulations",simulations);
}catch(Exception ex){}
}
// other query attributes
..........
// we hand over to the simulations display url
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// retrieve initialization parameters
ServletConfig config=getServletConfig();
urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parameters ok?
.........................
}
}
A página JSP de visualização simulationsImpots.jsp tem agora o seguinte aspeto (apenas foi mantido o código que difere da página JSP de visualização da aplicação anterior).
<%@ page import="java.util.*" %>
<%
// on récupère les attributs passés par la servlet principale
...........
ArrayList simulations=(ArrayList)request.getAttribute("simulations");
%>
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
........
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'impôts
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
.......................
</form>
</center>
<hr>
<%
// y-a-t-il des erreurs ?
if(erreurs!=null){
..................
}else if(simulations.size()!=0){
// résultats des simulations
out.println("<h3>Résultats des simulations<h3>");
out.println("<table \"border=\"1\">");
out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>");
for(int i=0;i<simulations.size();i++){
String[] simulation=(String[])simulations.get(i);
out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
}
out.println("</table>");
}
%>
</body>
</html>
5.5. Versão 4
Vamos agora criar uma aplicação autónoma que servirá como cliente web para a aplicação /impots/simulations anterior. Esta aplicação terá a seguinte interface gráfica:
![]() |
N.º | tipo | nome | função |
1 | JTextField | txtTaxServiceUrl | URL do serviço de simulação de cálculo de impostos |
2 | JRadioButton | rdYes | Marcado se for casado |
3 | JRadioButton | rdNo | Marcado se solteiro |
4 | JSpinner | spinChildren | número de filhos do contribuinte (mínimo=0, máximo=20, incremento=1) |
5 | JTextField | txtSalário | salário anual do contribuinte em F |
6 | JList em JScrollPane | lstSimulations | lista de simulações |
O menu «Tax» (Impostos) é composto pelas seguintes opções:
principal principal | opção secundário | nome | função |
Impostos | |||
Calcular | mnuCalculate | Calcula o imposto devido quando todos os dados necessários para o cálculo estão presentes e corretos | |
Limpar | mnuLimpar | redefine o formulário para o seu estado inicial | |
Sair | mnuExit | fecha a aplicação |
Regras de funcionamento
- A opção de menu «Calcular» permanece desativada se o campo 1 ou 5 estiver vazio
- É detetada uma URL com erros sintáticos no campo 1

- for detetado um salário incorreto

- for relatado qualquer erro de ligação ao servidor (no exemplo 1 abaixo, a porta está incorreta; no exemplo 2, o URL solicitado não existe; no exemplo 3, a base de dados MySQL não estava em execução)



- Se tudo estiver correto, as simulações são apresentadas

Ao escrever um cliente web programado, é necessário saber exatamente o que o servidor envia em resposta às várias solicitações possíveis de um cliente. O servidor envia um conjunto de linhas HTML contendo informações úteis e outros elementos que existem exclusivamente para o layout HTML. As expressões regulares Java podem ajudar-nos a encontrar as informações úteis no fluxo de linhas enviadas pelo servidor. Para isso, precisamos de conhecer o formato exato das várias respostas do servidor. Aqui, utilizaremos o cliente web que vimos anteriormente, que nos permite exibir no ecrã a resposta de um servidor a um pedido de URL. O URL solicitado será o do nosso serviço de simulação de cálculo de impostos, http://localhost:8080/impots/simulations, para o qual podemos passar parâmetros na forma http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&...
Vamos solicitar a URL enquanto a base de dados MySQL utilizada para criar um objeto do tipo imposto não estiver em execução:
Dos>java clientweb http://localhost:8080/impots/simulations GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
<!-- dÚbut from page HTML -->
<html>
<head>
<title>impots</title>
</head>
<body>
<h3>calcul d'impôts</h3>
<hr>
Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
</body>
</html>
Para recuperar o erro, o cliente web deve procurar na resposta do servidor web a linha que contém o texto «Aplicação indisponível». Agora, vamos iniciar a base de dados MySQL e solicitar o mesmo URL, passando-lhe os valores que espera (aceita-os igualmente bem como um pedido GET ou POST — consulte o código Java do servidor):
Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
...................................................
}//effacer
function calculer(){
...................................................
}//calculer
</script>
</head>
<body background="/impots/images/standard.jpg">
<center>
Calcul d'imp¶ts
<hr>
<form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
</form>
</center>
<hr>
<h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
</table>
</body>
</html>
Aqui está o documento HTML completo enviado pelo servidor. Os resultados das várias simulações encontram-se na tabela única contida neste documento. A expressão regular que nos permite extrair as informações relevantes do documento poderia ser a seguinte:
onde as quatro expressões entre parênteses representam as quatro informações a extrair.
Temos agora as orientações sobre o que fazer quando o utilizador solicita um cálculo de impostos a partir da interface gráfica anterior:
- verificar se todos os dados na interface são válidos e comunicar quaisquer erros.
- ligar-se ao URL especificado no campo 1. Para tal, seguiremos o modelo do cliente web genérico já apresentado e estudado
- no fluxo de resposta do servidor, usar expressões regulares para:
- encontrar a mensagem de erro, se houver
- encontrar os resultados da simulação, caso não haja erros
O código relacionado com o menu «Calcular» é o seguinte:
void mnuCalculer_actionPerformed(ActionEvent e) {
// tAX CALCULATION
// verification URL service
URL urlImpots=null;
try{
urlImpots=new URL(txtURLServiceImpots.getText().trim());
String query=urlImpots.getQuery();
if(query!=null) throw new Exception();
}catch (Exception ex){
// error msg
JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus on wrong field
txtURLServiceImpots.requestFocus();
// back to interface
return;
}
// salary verification
int salaire=0;
try{
salaire=Integer.parseInt(txtSalaire.getText().trim());
if(salaire<0) throw new Exception();
}catch (Exception ex){
// error msg
JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// focus on wrong field
txtSalaire.requestFocus();
// back to interface
return;
}
// no. of children
Integer nbEnfants=(Integer)spinEnfants.getValue();
try{
// tax calculation
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// error is displayed
JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
}
}//mnuCalculer
public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
throws Exception{
// tAX CALCULATION
// urlImpots : URL of the tax department
// married: true if married, false otherwise
// nbEnfants : number of children
// salary: annual salary
// remove from urlImpots the info needed to connect to the tax server
String path=urlImpots.getPath();
if(path.equals("")) path="/";
String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
String host=urlImpots.getHost();
int port=urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
// local data
Socket client=null; // the customer
BufferedReader IN=null; // the customer's reading flow
PrintWriter OUT=null; // the customer's writing flow
String réponse=null; // server response
// the model searched in HTTP headers
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the model for a correct answer
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// the result of the model comparison
Matcher résultat=null;
try{
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
// read the 1st line of the answer
réponse=IN.readLine();
// compare the HTTP line with the model of the correct answer
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// we have a URL problem
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(result)
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
// empty line?
if(réponse.equals("")) break;
// line HTTP not empty
// if you don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line with the cookie template
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// we found the token cookie
JSESSIONID=résultat.group(1);
}//if(result)
}//if(JSESSIONID)
}//while
// that's it for HTTP headers - move on to HTML code
// to retrieve simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// it's over
client.close();
}catch (Exception ex){
throw new Exception(ex.getMessage());
}
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// the model of a line in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// the template for a line in the error list
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
// the result of the model comparison
Matcher résultat=null;
// simulations
ArrayList listeSimulations=new ArrayList();
// read all the lines to the end
String ligne=null;
boolean simulationRéussie=false;
while((ligne=IN.readLine())!=null){
// follow-up
// the line is compared with the error model if the simulation part has not yet been encountered
if(! simulationRéussie){
résultat=ptnErreur.matcher(ligne);
if(résultat.find()){
// error msg
JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
// it's over
return listeSimulations;
}//if
}//if
// the line is compared with the simulation model
résultat=ptnSimulation.matcher(ligne);
if(résultat.find()){
// we found a row in the
listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
":"+résultat.group(4));
// the simulation was a success
simulationRéussie=true;
}//if
}//while
// end
return listeSimulations;
}
Vamos explicar um pouco este código:
- O procedimento mnuCalculer_actionPerformed verifica se os dados da interface são válidos. Se não forem, é exibida uma mensagem de erro e o procedimento é encerrado. Se forem válidos, o procedimento calculerImpots é executado.
- O procedimento calculerImpots começa por construir o URL necessário para efetuar o pedido
// on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
String path=urlImpots.getPath();
if(path.equals("")) path="/";
String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
String host=urlImpots.getHost();
int port=urlImpots.getPort();
if(port==-1) port=urlImpots.getDefaultPort();
........................
- depois liga-se a este URL enviando os cabeçalhos HTTP apropriados:
// connect to the server
client=new Socket(host,port);
// create customer input/output flows TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// request URL - send HTTP headers
OUT.println("GET " + path + query + " HTTP/1.1");
OUT.println("Host: " + host + ":" + port);
if(! JSESSIONID.equals("")){
OUT.println("Cookie: JSESSIONID="+JSESSIONID);
}
OUT.println("Connection: close");
OUT.println("");
- Assim que o pedido é feito, o nosso cliente web aguarda a resposta. Primeiro, recebe os cabeçalhos HTTP da resposta do servidor. Analisa esses cabeçalhos para encontrar o token de sessão. De facto, tem de enviar este token de volta ao servidor para que este possa acompanhar as várias simulações realizadas. Note-se aqui que teria sido mais simples para o cliente armazenar as várias simulações realizadas pelo próprio utilizador. No entanto, mantemos a ideia de reenviar o token para fornecer um novo exemplo de gestão de sessão. A primeira linha da resposta é tratada separadamente. Deve estar no formato HTTP/versão 200 OK para indicar que o URL solicitado existe de facto. Se não estiver neste formato, concluímos que o utilizador solicitou um URL incorreto e informamo-lo em conformidade.
// the model searched in HTTP headers
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// the model of a correct answer
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........
// read the 1st line of the answer
réponse=IN.readLine();
// compare the HTTP line with the model of the correct answer
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// we have a URL problem
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(result)
// we read the response through to the end of the headers, looking for any cookies
while((réponse=IN.readLine())!=null){
// empty line?
if(réponse.equals("")) break;
// line HTTP not empty
// if you don't have the session token, look for it
if (JSESSIONID.equals("")){
// compare the HTTP line with the cookie template
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// we found the token cookie
JSESSIONID=résultat.group(1);
}//if(result)
}//if(JSESSIONID)
}//while
- Depois de processados os cabeçalhos HTTP, passamos à parte HTML da resposta
// that's it for HTTP headers - move on to HTML code
// to retrieve simulations
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
- O procedimento getSimulations devolve a lista de simulações, caso estas existam; esta lista estará vazia se o servidor devolver uma mensagem de erro. Neste caso, a mensagem de erro é apresentada numa caixa de mensagem. Se a lista não estiver vazia, é apresentada na lista suspensa da interface gráfica do utilizador.
- O procedimento getSimulations compara cada linha da resposta HTML com a expressão regular que representa a mensagem de erro (Aplicação indisponível...) e com a expressão regular que representa uma simulação. Se a mensagem de erro for encontrada, esta é apresentada e o procedimento termina. Se for encontrado o resultado de uma simulação, este é adicionado à lista de simulações. No final do procedimento, esta lista é devolvida como resultado.
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// the model of a line in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// the template for a line in the error list
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
5.6. Versão 5
Aqui, transformamos a aplicação gráfica autónoma anterior num applet Java. A interface gráfica é ligeiramente diferente. Com a aplicação autónoma, o utilizador fornecia ele próprio o URL do serviço de simulação de cálculo de impostos, e a aplicação ligava-se então a esse URL. Aqui, a aplicação cliente é um navegador, e o utilizador irá solicitar o URL do documento HTML que contém o applet. É importante lembrar que um applet Java só pode estabelecer uma ligação de rede com o servidor a partir do qual foi descarregado. A URL do serviço de simulação estará, portanto, no mesmo servidor que o documento HTML que contém o applet. No nosso exemplo, este será um parâmetro de inicialização do applet colocado no campo txtUrlServiceImpots, que não poderá ser editado pelo utilizador. Isto resulta no seguinte cliente:

O documento HTML que contém o applet chama-se simulations.htm e é o seguinte:
<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>
O applet possui um parâmetro chamado *urlServiceImpots, que é a URL do serviço de simulação de cálculo de impostos. Esta URL é relativa à URL do documento HTML simulations.htm. Assim, se um navegador recuperar este documento através da URL http://localhost:8080/impots/simulations.htm, a URL do serviço de simulação será http://localhost:8080/impots/simulations. Se esta URL fosse http://stahe:8080/impots/simulations.htm, a URL do serviço de simulação seria http://stahe:8080/impots/simulations*.
O applet appletImpots.java incorpora na íntegra o código da aplicação gráfica autónoma anterior, respeitando as regras para a conversão de uma aplicação gráfica num applet.
public class appletImpots extends JApplet {
// window components
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculer = new JMenuItem();
.............
//Building the frame
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// other initializations
moreInit();
}
// form initialization
private void moreInit(){
// retrieve the urlServiceImpots parameter
String urlServiceImpots=getParameter("urlServiceImpots");
if(urlServiceImpots==null){
// missing parameter
JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
// end
return;
}
// put the URL in its field
String codeBase=""+getCodeBase();
if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase+urlServiceImpots);
else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
// calculate menu disabled
mnuCalculer.setEnabled(false);
// spinner Children - between 0 and 20 children
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
spinEnfants.setBounds(new Rectangle(130,140,50,27));
contentPane.add(spinEnfants);
}//moreInit
//Initialize component
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
...............
}
Quando um navegador carrega um applet, executa primeiro o seu método init. Neste método, recuperámos o valor do parâmetro urlServiceImpots do applet, calculámos o URL completo do serviço de simulação e colocámos este valor no campo txtURLServiceImpots, como se o utilizador o tivesse digitado. Uma vez feito isto, já não há qualquer diferença entre as duas aplicações. Em particular, o código associado ao menu «Calculate» é idêntico. Aqui está um exemplo de execução:

5.7. Conclusão
Demonstrámos diferentes versões da nossa aplicação cliente-servidor de cálculo de impostos:
- Versão 1: O serviço é fornecido por um conjunto de servlets e páginas JSP; o cliente é um navegador. Realiza uma única simulação e não mantém qualquer histórico das simulações anteriores.
- Versão 2: Adicionamos algumas funcionalidades do lado do navegador, incorporando scripts JavaScript no documento HTML carregado pelo navegador. Este valida os parâmetros do formulário.
- Versão 3: Permitimos que o serviço memorize as várias simulações realizadas por um cliente através da gestão de uma sessão. A interface HTML é modificada em conformidade para apresentar essas simulações.
- Versão 4: O cliente é agora uma aplicação gráfica autónoma. Isto permite-nos revisitar o desenvolvimento de clientes web programados.
- Versão 5: O cliente torna-se um applet Java. Temos agora uma aplicação cliente-servidor inteiramente programada em Java, tanto no lado do servidor como no lado do cliente.
Nesta altura, podem ser feitas algumas observações:
- As versões 1 a 3 suportam navegadores sem outras capacidades além da capacidade de executar scripts JavaScript. Note-se que um utilizador tem sempre a opção de desativar a execução destes scripts. A aplicação funcionará então apenas parcialmente na versão 1 (a opção Limpar não funcionará) e de todo nas versões 2 e 3 (as opções Limpar e Calcular não funcionarão). Pode valer a pena desenvolver uma versão do serviço que não utilize scripts JavaScript.
- A versão 4 requer que o computador do cliente tenha uma máquina virtual Java 2.
- A versão 5 requer que o computador do cliente tenha um navegador com uma máquina virtual Java 2.
Ao desenvolver um serviço web, é necessário ter em conta os tipos de clientes a que se destina. Se pretender alcançar o maior número possível de clientes, deve criar uma aplicação que envie apenas HTML para os navegadores (sem JavaScript nem applets). Se estiver a trabalhar numa intranet e tiver controlo sobre a configuração das estações de trabalho, poderá então permitir-se ser mais exigente no que diz respeito ao lado do cliente, e a versão 5 anterior poderá, nesse caso, ser aceitável.
As versões 4 e 5 são clientes web que recuperam as informações de que necessitam a partir do fluxo HTML enviado pelo servidor. Muitas vezes, não temos controlo sobre este fluxo. É o que acontece quando criamos um cliente para um serviço web existente na rede que é gerido por outra entidade. Vejamos um exemplo. Suponhamos que o nosso serviço de simulação de cálculo de impostos tenha sido escrito pela Empresa X. Atualmente, o serviço envia as simulações numa tabela HTML, e o nosso cliente utiliza este facto para as recuperar. Assim, compara cada linha da resposta do servidor com a expressão regular:
// the model of a line in the simulation table
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Suponha agora que o programador da aplicação altere a aparência visual da resposta, colocando as simulações não numa tabela, mas numa lista com o seguinte formato:
Neste caso, o nosso cliente web terá de ser reescrito. Esta é a ameaça constante que os clientes web enfrentam em aplicações que não controlamos. O XML pode fornecer uma solução para este problema:
- em vez de gerar HTML, o serviço de simulação irá gerar XML. No nosso exemplo, isto poderia ser
<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>
- Uma folha de estilo poderia ser associada a esta resposta, instruindo os navegadores sobre a apresentação visual desta resposta XML
- Os clientes web programados ignorariam esta folha de estilo e recuperariam a informação diretamente do fluxo XML da resposta
Se o criador do serviço desejar modificar a apresentação visual dos resultados fornecidos, modificará a folha de estilo em vez do XML. Graças à folha de estilo, os navegadores exibirão o novo formato visual e os clientes web programados não precisarão de ser modificados. Novas versões do nosso serviço de simulação poderão, portanto, ser criadas:
- Versão 6: O serviço fornece uma resposta XML acompanhada de uma folha de estilo destinada aos navegadores
- Versão 7: O cliente é uma aplicação gráfica autónoma que processa a resposta XML do servidor
- Versão 8: O cliente é um applet Java que processa a resposta XML do servidor
