5. A aplicação IMPOTS
5.1. Introdução
Retomamos aqui a aplicação IMPOTS, que é utilizada em várias ocasiões no manual de Java do mesmo autor. Recorde-se a problemática. Trata-se de escrever uma aplicação que permita calcular o imposto de um contribuinte. Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:
- calcula-se o número de quotas do trabalhador nbParts = nbEnfants/2 + 1 se não for casado, nbEnfants/2 + 2 se for casado, em que nbEnfants é o número de filhos que tem.
- se tiver pelo menos três filhos, tem mais meia quota
- calcula-se o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- calcula-se o seu coeficiente familiar QF = R / nbParts
- calcula-se o seu imposto I. Consideremos a seguinte tabela:
12620,0 | 0 | 0 |
13 190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24 740 | 0,15 | 2072,5 |
31 810 | 0,2 | 3309,5 |
39 970 | 0,25 | 4900 |
48 360 | 0,3 | 6898,5 |
55 790 | 0,35 | 9316,5 |
92 970 | 0,4 | 12106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23147,5 |
172040 | 0,55 | 30710 |
195 000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Cada linha tem 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF <= campo1. Por exemplo, se QF = 23000, encontrar-se-á a linha
O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF<=campo1 nunca for verificada, então são utilizados os coeficientes da última linha. Neste caso:
o que resulta no imposto I = 0,65*R - 49062*nbParts.
Os dados que definem as diferentes faixas de imposto encontram-se numa base de dados ODBC-MySQL. MySQL é um SGBD de domínio público que pode ser utilizado em diferentes plataformas, incluindo Windows e Linux. Com este SGBD, foi criada uma base de dados dbimpots contendo uma única tabela denominada 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 é transformada na fonte de dados ODBC da seguinte forma:
- é iniciado o gestor de fontes de dados ODBC de 32 bits

- utiliza-se o botão [Add] para adicionar uma nova fonte de dados ODBC

- seleciona-se o controlador MySQL e executa-se [Terminer]
54321

- O controlador MySQL solicita uma série de informações:
1 | o nome DSN a atribuir à fonte de dados ODBC — pode ser qualquer um |
2 | a máquina na qual o SGBD e o MySQL são executados — neste caso, o localhost. É importante referir que a base de dados pode ser uma base de dados remota. As aplicações locais que utilizam a fonte de dados ODBC não se aperceberiam disso. Seria esse o caso, nomeadamente, da nossa aplicação Java. |
3 | a base de dados MySQL a utilizar. MySQL é um SGBD que gere bases de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, indica-se o nome da base de dados 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. É criada uma instância da classe impots com os dados das faixas de imposto passados como parâmetros em tabelas:
// criação de uma classe de impostos
public class impots{
// os dados necessários para o cálculo do imposto
// provêm de uma fonte externa
protected double[] limites=null;
protected double[] coeffR=null;
protected double[] coeffN=null;
// construtor vazio
protected impots(){}
// construtor
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// verifica-se se as 3 tabelas têm o mesmo tamanho
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+")");
// está tudo bem
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//criador
// cálculo do imposto
public long calculer(boolean marié, int nbEnfants, int salaire){
// cálculo do número de quotas
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// cálculo do rendimento tributável e do quociente familiar
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// cálculo do imposto
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// retorno do resultado
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calcular
}//classe
A classe impotsJDBC deriva da classe «impots» anterior. É criada uma instância da classe impotsJDBC com os dados das faixas de imposto armazenados numa base de dados. As informações necessárias para aceder a esta base de dados são passadas como parâmetros ao construtor:
// pacotes importados
import java.sql.*;
import java.util.*;
public class impotsJDBC extends impots{
// adição de um construtor que permite construir
// as tabelas «limites», «coeffr» e «coeffn» a partir da tabela
// impostos de uma base de dados
public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
throws SQLException,ClassNotFoundException{
// dsnIMPOTS: nome DSN da base de dados
// userIMPOTS, mdpIMPOTS: nome de utilizador/palavra-passe de acesso à base de dados
// as tabelas de dados
ArrayList aLimites=new ArrayList();
ArrayList aCoeffR=new ArrayList();
ArrayList aCoeffN=new ArrayList();
// ligação à base de dados
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
// criação de um objeto Statement
Statement S=connect.createStatement();
// consulta SELECT
String select="select limites, coeffr, coeffn from impots";
// execução da consulta
ResultSet RS=S.executeQuery(select);
while(RS.next()){
// análise da linha atual
aLimites.add(RS.getString("limites"));
aCoeffR.add(RS.getString("coeffr"));
aCoeffN.add(RS.getString("coeffn"));
}// linha seguinte
// encerramento de recursos
RS.close();
S.close();
connect.close();
// transferência de dados para tabelas delimitadas
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
}//construtor
}//classe
Depois de criada uma instância da classe impotsJDBC, é possível chamar repetidamente o seu método calculer para calcular o imposto:
A obtenção dos três dados necessários pode ser feita de várias formas. A vantagem da classe impotsJDBC é que basta preocuparmo-nos apenas com essa obtenção. Assim que as três informações (estado civil, número de filhos, salário anual) forem obtidas, a chamada ao método calculer da classe impotsJDBC fornece-nos o imposto a pagar.
5.2. Versão 1
Estamos no contexto de uma aplicação web que apresentaria uma interface HTML a um utilizador, a fim de obter os três parâmetros necessários para o cálculo do imposto:
- o estado civil (casado ou solteiro)
- o número de filhos
- o salário anual

A apresentação do formulário é efetuada pela seguinte página JSP:
<%@ page import="java.util.*" %>
<%
// recuperam-se os atributos passados pela servlet principal
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(){
// limpa o formulário
with(document.frmImpots){
optMarie[0].checked=false;
optMarie[1].checked=true;
txtEnfants.value="";
txtSalaire.value="";
txtImpots.value="";
}//com
}//apagar
</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>
<%
// existem erros
if(erreurs!=null){
// exibição dos erros
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 limita-se a apresentar as informações que lhe são transmitidas pelo servlet principal da aplicação:
// recuperam-se os atributos passados pelo servlet principal
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 dos botões de opção «sim» e «não» têm como valores possíveis "checked" ou "", para ativar ou desativar o botão de opção correspondente | |
o número de filhos do contribuinte | |
o seu salário anual | |
o montante do imposto a pagar | |
uma eventual lista de erros, se erros!=null |
A página enviada ao cliente contém um script JavaScript com uma função effacer associada ao botão «Effacer», cuja função é repor o formulário no seu estado inicial: botão não marcado, campos de preenchimento vazios. Note-se que este resultado não poderia ser obtido com um botão HTML do tipo «reset». Com efeito, quando este tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. Ora, 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{
// variáveis de instância
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{
// A inicialização decorreu corretamente?
if(msgErreur!=null){
// passa-se o controlo para a página de erro
request.setAttribute("msgErreur",msgErreur);
getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
}
// atributos da solicitação
String chkoui=null;
String chknon=null;
String txtImpots=null;
// recuperam-se os parâmetros da solicitação
String optMarie=request.getParameter("optMarie"); // estado civil
String txtEnfants=request.getParameter("txtEnfants"); // número de filhos
if(txtEnfants==null) txtEnfants="";
String txtSalaire=request.getParameter("txtSalaire"); // salário anual
if(txtSalaire==null) txtSalaire="";
// estão todos os parâmetros esperados?
if(optMarie==null || txtEnfants==null || txtSalaire==null){
// faltam parâmetros
request.setAttribute("chkoui","");
request.setAttribute("chknon","checked");
request.setAttribute("txtEnfants","");
request.setAttribute("txtSalaire","");
request.setAttribute("txtImpots","");
// passamos o controlo para a URL de visualização do imposto
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
// temos todos os parâmetros — verificamo-los
ArrayList erreurs=new ArrayList();
// estado civil
if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
// erro
erreurs.add("Etat marital incorrect");
optMarie="non";
}
// número de filhos
txtEnfants=txtEnfants.trim();
if(! Pattern.matches("^\\d+$",txtEnfants)){
// erro
erreurs.add("Nombre d'enfants incorrect");
}
// salário
txtSalaire=txtSalaire.trim();
if(! Pattern.matches("^\\d+$",txtSalaire)){
// erro
erreurs.add("Salaire incorrect");
}
// se houver erros, estes são passados como atributos da consulta
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
txtImpots="";
}else{
// é possível calcular o imposto a pagar
try{
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
}catch(Exception ex){}
}
// os restantes atributos da consulta
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);
// passa-se o controlo para a URL de visualização do imposto
getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// recuperam-se os parâmetros de inicialização
ServletConfig config=getServletConfig();
urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parâmetros corretos?
if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
msgErreur="Configuration incorrecte";
return;
}
// criamos uma instância de 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 das diferentes faixas de impostos (DSNimpots, admimpots, mdpimpots) e os URL 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 possíveis erros são tratados e a mensagem de erro é colocada na variável msgErreur.
- O método doGET
- verifica, em primeiro lugar, se o servlet foi inicializado corretamente. Caso contrário, é apresentada a página de erro
- recupera os parâmetros esperados do formulário fiscal: optMarie, txtEnfants, txtSalaire. Se algum deles faltar (==null), é enviado um formulário de impostos vazio. Poder-se-ia pensar que a verificação da validade do parâmetro optMarie é desnecessária. Este parâmetro corresponde ao valor de um botão de opção e, neste contexto, só pode assumir um dos valores «oui» ou «non». Isso é esquecer que nada impede um programa de interrogar diretamente o servlet, enviando-lhe os parâmetros que desejar. Nunca se pode ter a certeza de que se está realmente ligado a um navegador. Esquecer este ponto pode levar a falhas de segurança na aplicação e, aliás, é frequente encontrá-las mesmo em aplicações comerciais.
- A validade de cada um dos três parâmetros recuperados é verificada. Cada erro detetado é adicionado a uma lista de erros (ArrayList erros). Se não houver erros, o montante do imposto é calculado; caso contrário, não é calculado.
- As informações necessárias para a exibição da página são colocadas nos atributos da solicitação e, em seguida, o formulário de impostos é exibido
A página de erro JSP é a seguinte:
<%
// jspService
// ocorreu um erro
String msgErreur= (String)request.getAttribute("msgErreur");
if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- início da página 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:
A pasta da aplicação contém as seguintes pastas e ficheiros:




O ficheiro de configuração da aplicação «impôts», web.xml, é 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 e do http://localhost:8080/impots/main.
Eis alguns exemplos de aplicação:
Para se inicializar corretamente, a servlet deve ter acesso à base de dados mysql-dbimpots. Eis a página obtida se, por exemplo, o servidor MySQL não estiver em execução e a base de dados mysql-dbimpots estiver, consequentemente, inacessível:

A página apresentada em caso de introdução incorreta de dados é a seguinte:

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

5.3. Versão 2
No exemplo anterior, a validade dos parâmetros txtEnfants e txtSalaire do formulário é verificada pelo servidor. Propõe-se aqui verificá-la através de um script JavaScript incluído na página do formulário. É então o navegador que efetua a verificação dos parâmetros. O servidor só é solicitado se estes forem válidos. Poupa-se assim «largura de banda». A página de visualização JSP passa a ter o seguinte aspeto:
<%@ page import="java.util.*" %>
............
<html>
<head>
<title>impots</title>
<script language="JavaScript" type="text/javascript">
function effacer(){
.......
}//apagar
function calculer(){
// verificação dos parâmetros antes de os enviar para o servidor
with(document.frmImpots){
//número de filhos
champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
if(champs==null){
// o modelo não foi verificado
alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
nbEnfants.focus();
return;
}//if
//salário
champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
if(champs==null){
// o modelo não foi verificado
alert("Le salaire n'a pas été donné ou est incorrect");
salaire.focus();
return;
}//se
// Está tudo bem — enviamos o formulário para o servidor
submit();
}//com
}//calcular
</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>
De notar as seguintes alterações:
- o botão Calculer já não é do tipo submit, mas sim do tipo button, associado a uma função denominada calculer. É esta função que irá analisar os campos txtEnfants e txTsalaire. Se estiverem corretos, os valores do formulário serão enviados para o servidor (submit); caso contrário, será exibida uma mensagem de erro.
Eis um exemplo de exibição em caso de erro:

5.4. Versão 3
Alteramos ligeiramente a aplicação para introduzir o conceito de sessão. Consideramos agora que a aplicação é uma simulação de cálculo de impostos. Um utilizador pode, assim, simular diferentes «configurações» de contribuinte e ver qual seria, para cada uma delas, o imposto a pagar. A página WEb abaixo apresenta um exemplo do que poderia ser obtido:

O servlet principal foi alterado e passa a chamar-se «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>
A servlet principal chama-se «simulations» e baseia-se no ficheiro de classe simulations.class. Tem o alias /simulations, o que a torna acessível através do URL http://localhost:8080/impots/simulations. Possui os mesmos parâmetros de inicialização que a servlet principal analisada anteriormente no que diz respeito ao acesso à base de dados. Surge um novo parâmetro, urlSimulationsImpots, que corresponde à página de simulação JSP (a que acabou de ser apresentada um pouco mais acima).
A servlet simulations.java é semelhante à servlet main.java. Difere desta nos seguintes pontos principais:
- a servlet principal calcula um valor txtImpots a partir dos parâmetros optmarie, txtEnfants e txtSalaire e passa esse valor para a página de visualização JSP
- o servlet «simulações» calcula, da mesma forma, o valor txtImpots, grava os parâmetros (optMarie, txtEnfants, txtsalaire, txtImpots) numa lista denominada «simulações». É esta lista que é passada como parâmetro para a página de visualização JSP. Para que esta lista contenha todas as simulações realizadas pelo utilizador, é guardada como atributo da sessão atual.
O servlet «simulações» é o seguinte (foram mantidas apenas as linhas de código que diferem das da aplicação anterior):
import java.io.*;
.......
public class simulations extends HttpServlet{
// variáveis de instância
String msgErreur=null;
String urlSimulationImpots=null;
String urlErreur=null;
...........
//-------- GET
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
...........
// recuperam-se as simulações anteriores da sessão
HttpSession session=request.getSession();
ArrayList simulations=(ArrayList)session.getAttribute("simulations");
if(simulations==null) simulations=new ArrayList();
// coloca-se as simulações na consulta atual
request.setAttribute("simulations",simulations);
// outros atributos da consulta
...........
// estão todos os parâmetros esperados?
if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
// passa-se o controlo para o URL de visualização das simulações de cálculo de impostos
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
// temos todos os parâmetros — verificamo-los
...........
// se houver erros, estes são passados para os atributos da consulta
if(erreurs.size()!=0){
request.setAttribute("erreurs",erreurs);
}else{
try{
// é possível calcular o imposto a pagar
int nbEnfants=Integer.parseInt(txtEnfants);
int salaire=Integer.parseInt(txtSalaire);
txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
// adicionamos o resultado atual às simulações anteriores
String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
simulations.add(simulation);
// o novo valor das simulações é guardado na sessão
session.setAttribute("simulations",simulations);
}catch(Exception ex){}
}
// outros atributos da consulta
..........
// passa-se o controlo para o URL de visualização das simulações
getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
}
//-------- POST
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException{
doGet(request,response);
}
//-------- INIT
public void init(){
// recuperam-se os parâmetros de inicialização
ServletConfig config=getServletConfig();
urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
urlErreur=config.getInitParameter("urlErreur");
DSNimpots=config.getInitParameter("DSNimpots");
admimpots=config.getInitParameter("admimpots");
mdpimpots=config.getInitParameter("mdpimpots");
// parâmetros corretos?
.........................
}
}
A página de visualização JSP, que correspondia à página simulationsImpots.jsp, passou a ter o seguinte aspeto (apenas foi mantido o código que difere da página de visualização JSP da aplicação anterior).
<%@ page import="java.util.*" %>
<%
// recuperam-se os atributos passados pelo servlet principal
...........
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>
<%
// Existem erros?
if(erreurs!=null){
..................
}else if(simulations.size()!=0){
// resultados das simulações
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 será um cliente web da aplicação /impots/simulations anterior. Esta aplicação terá a seguinte interface gráfica:
![]() |
n.º | tipo | nome | função |
1 | JTextField | txtUrlServiceImpots | URL do serviço de simulação do cálculo de impostos |
2 | JRadioButton | rdOui | assinalar se for casado |
3 | JRadioButton | rdNon | marcar se solteiro |
4 | JSpinner | spinEnfants | número de filhos do contribuinte (mínimo=0, máximo=20, incremento=1) |
5 | JTextField | txtSalaire | salário anual do contribuinte em F |
6 | JList em JScrollPane | lstSimulations | lista de simulações |
O menu «Impostos» é composto pelas seguintes opções:
opção principal | opção secundária | nome | função |
Impostos | |||
Calcular | mnuCalculer | calcula o imposto a pagar quando todos os dados necessários para o cálculo estão presentes e corretos | |
Apagar | mnuEffacer | repor o formulário ao seu estado inicial | |
Sair | mnuQuitter | encerra a aplicação |
Regras de funcionamento
- a opção de menu Calculer permanece desativada se um dos campos 1 ou 5 estiver vazio
- é detetada uma sintaxe incorreta de URL no campo 1

- for detetado um salário incorreto

- qualquer erro de ligação ao servidor é sinalizado (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 a funcionar)



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

Ao programar um cliente web, é necessário saber exatamente o que o servidor envia em resposta às diferentes solicitações possíveis de um cliente. O servidor envia um conjunto de linhas HTML que inclui informações úteis e outras que servem apenas para a formatação HTML. As expressões regulares em Java podem ajudar-nos a identificar as informações úteis no fluxo de linhas enviadas pelo servidor. Para tal, precisamos de conhecer o formato exato das diferentes respostas do servidor. Aqui, vamos utilizar o cliente web já apresentado, que permite exibir no ecrã a resposta de um servidor a uma solicitação de um URL. O URL solicitado será o do nosso serviço de simulações de cálculos de impostos http://localhost:8080/impots/simulations, ao qual poderemos passar parâmetros na forma http://localhost:8080/impots/simulations?param1=vam1¶m2=val2&...
Solicitemos o URL, uma vez que a base MySQL, que permite criar um objeto do tipo impots, não está em execução:
Dos>java clientweb http://localhost:8080/impostos/simulações 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 da página 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 identificar o erro, o cliente web terá de procurar na resposta do servidor web a linha que contenha o texto «Aplicação indisponível». Agora, vamos iniciar a base MySQL e solicitar a mesma URL, passando-lhe os valores que ela espera (ela aceita-os indistintamente na forma de um GET ou de um POST — ver 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(){
...................................................
}//apagar
function calculer(){
...................................................
}//calcular
</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 encontra-se o documento completo HTML enviado pelo servidor. O resultado das diferentes simulações encontra-se na única tabela presente neste documento. A expressão regular que nos permite recuperar as informações relevantes do documento poderá ser a seguinte:
onde as quatro expressões entre parênteses representam as quatro informações a recuperar.
Temos agora as orientações sobre o que fazer quando o utilizador solicitar o cálculo do imposto a partir da interface gráfica anterior:
- verificar se todos os dados da interface são válidos e, se necessário, sinalizar os erros.
- ligar-se ao URL indicado no campo 1. Para tal, seguiremos o modelo do cliente web genérico já apresentado e analisado
- no fluxo da resposta do servidor, utilizar expressões regulares para:
- encontrar a mensagem de erro, caso exista
- encontrar os resultados das simulações, caso não haja erros
O código associado ao menu calculer é o seguinte:
void mnuCalculer_actionPerformed(ActionEvent e) {
// cálculo do imposto
// verificação URL serviço
URL urlImpots=null;
try{
urlImpots=new URL(txtURLServiceImpots.getText().trim());
String query=urlImpots.getQuery();
if(query!=null) throw new Exception();
}catch (Exception ex){
// mensagem de erro
JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// foco no campo com erro
txtURLServiceImpots.requestFocus();
// regresso à interface
return;
}
// verificação do salário
int salaire=0;
try{
salaire=Integer.parseInt(txtSalaire.getText().trim());
if(salaire<0) throw new Exception();
}catch (Exception ex){
// mensagem de erro
JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
// foco no campo com erro
txtSalaire.requestFocus();
// voltar à interface
return;
}
// número de filhos
Integer nbEnfants=(Integer)spinEnfants.getValue();
try{
// calcula-se o imposto
calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
}catch (Exception ex){
// é apresentado o erro
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{
// cálculo do imposto
// urlImpots: URL da administração fiscal
// casado: true se for casado, false caso contrário
// nbEnfants: número de filhos
// salário: salário anual
// a partir de urlImpots extraem-se as informações necessárias para a ligação ao servidor da administração fiscal
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();
// dados locais
Socket client=null; // o cliente
BufferedReader IN=null; // o fluxo de leitura do cliente
PrintWriter OUT=null; // o fluxo de escrita do cliente
String réponse=null; // resposta do servidor
// o modelo procurado nos cabeçalhos HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// o modelo de uma resposta correta
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
// o resultado da comparação com o modelo
Matcher résultat=null;
try{
// estabelece-se a ligação ao servidor
client=new Socket(host,port);
// criam-se os fluxos de entrada e saída do cliente TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// solicita-se o URL - envio dos cabeçalhos 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("");
// lê-se a primeira linha da resposta
réponse=IN.readLine();
// compara-se a linha HTTP com o modelo da resposta correta
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// há um problema com URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(resultado)
// lê-se a resposta até ao fim dos cabeçalhos, procurando um eventual cookie
while((réponse=IN.readLine())!=null){
// linha vazia?
if(réponse.equals("")) break;
// linha HTTP não vazia
// se não tivermos o token da sessão, procuramo-lo
if (JSESSIONID.equals("")){
// comparamos a linha HTTP com o modelo do cookie
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// encontrou-se o cookie do token
JSESSIONID=résultat.group(1);
}//if(resultado)
}//se (JSESSIONID)
}//enquanto
// acabou para os cabeçalhos HTTP — passamos para o código HTML
// para recuperar as simulações
ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
simulations.clear();
for (int i=0;i<listeSimulations.size();i++){
simulations.addElement(listeSimulations.get(i));
}
// acabou
client.close();
}catch (Exception ex){
throw new Exception(ex.getMessage());
}
}//calculerImpots
private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{
// o modelo de uma linha da tabela de simulações
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// o modelo de uma linha da lista de erros
Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
// o resultado da comparação com o modelo
Matcher résultat=null;
// as simulações
ArrayList listeSimulations=new ArrayList();
// leem-se todas as linhas até ao fim
String ligne=null;
boolean simulationRéussie=false;
while((ligne=IN.readLine())!=null){
// acompanhamento
// compara-se a linha com o modelo de erro, caso a parte das simulações ainda não tenha sido encontrada
if(! simulationRéussie){
résultat=ptnErreur.matcher(ligne);
if(résultat.find()){
// mensagem de erro
JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
// concluído
return listeSimulations;
}//se
}//se
// comparamos a linha com o modelo de simulação
résultat=ptnSimulation.matcher(ligne);
if(résultat.find()){
// foi encontrada uma linha da tabela
listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
":"+résultat.group(4));
// a simulação foi bem-sucedida
simulationRéussie=true;
}//if
}//enquanto
// fim
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, é emitida uma mensagem de erro e o procedimento é encerrado. Se forem, o procedimento calculerImpots é executado.
- O procedimento calculerImpots começa por criar o URL que tem de solicitar
// retiram-se de urlImpots as informações necessárias para a ligação ao servidor fiscal
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();
........................
- e, em seguida, estabelece ligação a este URL, enviando os cabeçalhos HTTP adequados:
// e estabelece-se a ligação ao servidor
client=new Socket(host,port);
// criam-se os fluxos de entrada e saída do cliente TCP
IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
OUT=new PrintWriter(client.getOutputStream(),true);
// solicita-se o URL - envio dos cabeçalhos 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("");
- Depois de efetuada a solicitação, o nosso cliente web aguarda a resposta. Primeiro, obtém os cabeçalhos HTTP da resposta do servidor. Analisa esses cabeçalhos para encontrar o token da sessão. Com efeito, terá de reenviar esse token ao servidor para que este possa manter um registo das diferentes simulações realizadas. Note-se aqui que teria sido mais simples que o cliente memorizasse ele próprio as diferentes simulações realizadas pelo 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. Com efeito, deve ter o formato HTTP/versão 200 OK para indicar que o URL solicitado existe efetivamente. Portanto, se não tiver este formato, conclui-se que o utilizador solicitou um URL incorreto e informa-se-o disso.
// o modelo procurado nos cabeçalhos HTTP
Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
// o modelo de uma resposta correta
Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........
// lê-se a primeira linha da resposta
réponse=IN.readLine();
// compara-se a linha HTTP com o modelo da resposta correta
résultat=réponseOK.matcher(réponse);
if(! résultat.find()){
// há um problema com URL
throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
}//if(resultado)
// lê-se a resposta até ao fim dos cabeçalhos, procurando um eventual cookie
while((réponse=IN.readLine())!=null){
// linha vazia?
if(réponse.equals("")) break;
// linha HTTP não vazia
// se não tivermos o token da sessão, procuramo-lo
if (JSESSIONID.equals("")){
// comparamos a linha HTTP com o modelo do cookie
résultat=modèleCookie.matcher(réponse);
if(résultat.find()){
// encontrou-se o cookie do token
JSESSIONID=résultat.group(1);
}//if(resultado)
}//if(JSESSIONID)
}//enquanto
- Depois de processados os cabeçalhos HTTP, passa-se à parte HTML da resposta
// acabou para os cabeçalhos HTTP — passamos para o código HTML
// para recuperar as simulações
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 existam; essa lista ficará vazia se o servidor devolver uma mensagem de erro. Nesse caso, a mensagem é apresentada numa caixa de mensagem. Se a lista não estiver vazia, é apresentada na lista suspensa da interface gráfica.
- 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 for encontrada a mensagem de erro, esta é apresentada e o procedimento é encerrado. 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{
// o modelo de uma linha da tabela de simulações
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
// o modelo de uma linha da lista de erros
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 introduzia ele próprio o URL do serviço de simulação de cálculos fiscais e, em seguida, a aplicação ligava-se 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. O URL do serviço de simulação estará, portanto, no mesmo servidor que o documento HTML que contém o applet. No nosso exemplo, será um parâmetro de inicialização do applet que será inserido no campo txtUrlServiceImpots, campo esse que não poderá ser editado pelo utilizador. Teremos, assim, o 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 tem um parâmetro urlServiceImpots, que corresponde ao URL do serviço de simulações de cálculo de impostos. Este URL é relativo e medido em relação ao URL do documento HTML simulations.htm. Assim, se um navegador obtiver este documento com o URL http://localhost:8080/impots/simulations.htm, o URL do serviço de simulação será http://localhost:8080/impots/simulations.. Se este URL tivesse sido http://stahe:8080/impostos/simulations.htm, o URL do serviço de simulação teria sido http://stahe:8080/impots/simulations.
O applet appletImpots.java retoma na íntegra o código da aplicação gráfica autónoma anterior, respeitando as regras de transformação de uma aplicação gráfica num applet.
public class appletImpots extends JApplet {
// os componentes da janela
JPanel contentPane;
JMenuBar jMenuBar1 = new JMenuBar();
JMenu jMenu1 = new JMenu();
JMenuItem mnuCalculer = new JMenuItem();
.............
//Construir o quadro
public void init() {
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
// outras inicializações
moreInit();
}
// inicialização do formulário
private void moreInit(){
// recupera-se o parâmetro urlServiceImpots
String urlServiceImpots=getParameter("urlServiceImpots");
if(urlServiceImpots==null){
// parâmetro em falta
JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
// fim
return;
}
// insere-se o URL no respetivo campo
String codeBase=""+getCodeBase();
if(codeBase.endsWith("/"))
txtURLServiceImpots.setText(codeBase+urlServiceImpots);
else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
// menu «Calcular» desativado
mnuCalculer.setEnabled(false);
// seletor «Crianças» — entre 0 e 20 crianças
spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
spinEnfants.setBounds(new Rectangle(130,140,50,27));
contentPane.add(spinEnfants);
}//moreInit
//Inicializar o componente
private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(null);
...............
}
Quando um navegador carrega um applet, executa primeiro o seu procedimento init. Nesse procedimento, recuperámos o valor do parâmetro urlServiceImpots do applet, calculámos o nome completo do URL do serviço de simulação e colocámos esse valor no campo txtURLServiceImpots, como se tivesse sido o utilizador a digitá-lo. Feito isto, já não há diferença entre as duas aplicações. Em particular, o código associado ao menu Calculer é idêntico. Eis um exemplo de execução:

5.7. Conclusão
Mostrámos diferentes versões da nossa aplicação cliente-servidor de cálculo de impostos:
- versão 1: o serviço é prestado por um conjunto de servlets e páginas JSP; o cliente é um navegador. Este efetua uma única simulação e não guarda um registo das anteriores.
- versão 2: adicionam-se algumas funcionalidades do lado do navegador, inserindo scripts JavaScript no documento HTML carregado pelo mesmo. Este controla a validade dos parâmetros do formulário.
- versão 3: permite-se que o serviço se lembre das diferentes simulações realizadas por um cliente através da gestão de uma sessão. A interface HTML é alterada em conformidade para as apresentar.
- versão 4: o cliente é agora uma aplicação gráfica autónoma. Isto permite-nos regressar à programação de clientes web.
- versão 5: o cliente transforma-se num applet Java. Passamos assim a ter uma aplicação cliente-servidor inteiramente programada em Java, tanto do lado do servidor como do lado do cliente.
Nesta altura, podemos fazer algumas observações:
- as versões 1 a 3 permitem a utilização de navegadores sem outras capacidades além da de executar scripts JavaScript. Note-se que um utilizador tem sempre a possibilidade de inibir a execução destes. A aplicação funcionará, então, apenas parcialmente na sua versão 1 (a opção «Apagar» não funcionará) e de todo nas suas versões 2 e 3 (as opções «Apagar» e «Calcular» não funcionarão). Poderia ser interessante prever uma versão do serviço que não utilize scripts JavaScript.
- A versão 4 requer que o computador do utilizador disponha de 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 escrever um serviço Web, é necessário questionar-se sobre os tipos de clientes a que se destina. Se se pretender atingir o maior número possível de clientes, deve-se escrever uma aplicação que envie aos navegadores apenas HTML (sem JavaScript nem applets). Se estivermos a trabalhar numa intranet e tivermos controlo sobre a configuração dos computadores da mesma, podemos então permitir-nos ser mais exigentes em relação ao cliente, e a versão 5 anterior pode, nesse caso, ser aceitável.
As versões 4 e 5 são clientes web que obtêm a informação de que necessitam a partir do fluxo HTML enviado pelo servidor. Muitas vezes, não temos controlo sobre esse fluxo. É o que acontece quando se desenvolve um cliente para um serviço web existente na rede e gerido por outra entidade. Vejamos um exemplo. Suponhamos que o nosso serviço de simulações de cálculo de impostos tenha sido desenvolvido pela empresa X. Atualmente, o serviço envia as simulações numa tabela HTML e o nosso cliente aproveita esse facto para as recuperar. Assim, compara cada linha da resposta do servidor com a expressão regular:
// o modelo de uma linha da tabela de simulações
Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
Suponhamos agora que o programador da aplicação altere a apresentação 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 permanente que paira sobre os clientes web de aplicações que não controlamos diretamente. O XML pode oferecer uma solução para este problema:
- em vez de gerar HTML, o serviço de simulações irá gerar XML. No nosso exemplo, 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>
- Poderá ser associada a esta resposta uma folha de estilo que indique aos navegadores a forma visual a atribuir a esta resposta XML
- os clientes web programados ignorariam esta folha de estilo e recuperariam a informação diretamente do fluxo XML da resposta
Se o programador do serviço pretender alterar a apresentação visual dos resultados que fornece, deverá modificar a folha de estilo e não o XML. Graças à folha de estilo, os navegadores apresentarão o novo aspeto visual e os clientes web programados não terão de ser alterados. Assim, poderão ser desenvolvidas novas versões do nosso serviço de simulações:
- 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 utiliza a resposta XML do servidor
- versão 8: o cliente é um applet Java que utiliza a resposta XML do servidor
