Skip to content

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

    24740        0.15        2072.5

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:

    0                0.65        49062

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

Image

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

Image

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

54321

Image

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

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

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

Image

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");
chkoui, chknon
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
txtEnfants
o número de filhos do contribuinte
txtSalaire
o seu salário anual
txtImpots
o montante do imposto a pagar
erreurs
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:

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

A pasta da aplicação contém as seguintes pastas e ficheiros:

Image

Image

Image

Image

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:

Image

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

Image

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

Image

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:

Image

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:

Image

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

Image

  • for detetado um salário incorreto

Image

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

Image

Image

Image

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

Image

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&param2=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:

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

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:

Image

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:

Image

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:

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

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