Skip to content

5. A aplicação IMPOTS

5.1. Introdução

Retomamos aqui a aplicação IMPOTS, que é utilizada várias vezes no apostila de Java do mesmo autor. Vamos relembrar 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 tenha apenas seu salário para declarar:

  • calcula-se o número de cotas do empregado nbParts = nbEnfants/2 + 1 se ele for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
  • se ele tiver pelo menos três filhos, ele tem mais meia parcela
  • calcula-se sua renda tributável R = 0,72 * S, onde S é seu salário anual
  • calcula-se seu coeficiente familiar QF = R / nbParts
  • calcula-se seu imposto I. Consideremos a tabela a seguir:
12620,0
0
0
13.190
0,05
631
15.640
0,1
1.290,5
24.740
0,15
2.072,5
31.810
0,2
3.309,5
39.970
0,25
4.900
48.360
0,3
6.898,5
55.790
0,35
9.316,5
92.970
0,4
12.106
127.860
0,45
16.754,5
151.250
0,50
23147,5
172.040
0,55
30.710
195.000
0,60
39.312
0
0,65
49.062

Cada linha possui 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF ≤ campo1. Por exemplo, se QF = 23000, será encontrada 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 serão utilizados os coeficientes da última linha. Aqui:

    0                0.65        49062

o que resulta no imposto I = 0,65 * R - 49.062 * nbParts.

Os dados que definem as diferentes faixas de imposto estão em um banco 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 criado um banco de dados dbimpots contendo uma única tabela chamada impots. O acesso ao banco de dados é controlado por um login/senha, neste caso admimpots/mdpimpots. A captura de tela a seguir mostra como utilizar o banco 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

O banco de dados dbimpots é transformado na fonte de dados ODBC da seguinte maneira:

  • é iniciado o gerenciador de fontes de dados ODBC de 32 bits

Image

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

Image

  • escolhe-se o driver MySQL e executa-se [Terminer]

54321

Image

  • o driver MySQL solicita algumas informações:
1
o nome DSN a ser atribuído à 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. Vale ressaltar que o banco de dados pode ser um banco de dados remoto. As aplicações locais que utilizam a fonte de dados ODBC não perceberiam essa mudança. Esse seria o caso, notadamente, da nossa aplicação Java.
3
o banco de dados MySQL a ser utilizado. MySQL é um SGBD que gerencia bancos de dados relacionais, que são conjuntos de tabelas interligadas por relações. Aqui, fornecemos o nome do banco de dados gerenciado.
4
o nome de um usuário com direito de acesso a essa base de dados
5
sua senha

Duas classes foram definidas para calcular o imposto: impots e impotsJDBC. Uma instância da classe impots é criada 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 três 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+")");
    // tudo certo
    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 cotas
    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 de impostos anterior. Uma instância da classe impotsJDBC é criada com os dados das faixas de imposto armazenados em um banco de dados. As informações necessárias para acessar esse banco 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 um banco de dados
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nome DSN do banco de dados
     // userIMPOTS, mdpIMPOTS: login/senha de acesso ao banco de dados

     // as tabelas de dados
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // conexão com o banco 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
     // fechamento 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 que uma instância da classe impotsJDBC for criada, é possível chamar repetidamente 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 maneiras. A vantagem da classe impotsJDBC é que basta se preocupar apenas com essa obtenção. Uma vez obtidas as três informações (estado civil, número de filhos, salário anual), a chamada ao método calculer da classe impotsJDBC nos fornece o imposto a pagar.

5.2. Versão 1

Estamos no contexto de um aplicativo web que apresentaria uma interface HTML a um usuário para obter os três parâmetros necessários ao cálculo do imposto:

  • o estado civil (casado ou solteiro)
  • o número de filhos
  • o salário anual

Image

A exibição do formulário é realizada pela seguinte página JSP:

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

<%
     // recuperamos 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");
%>

<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>
    <%
         // há 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 exibir as informações que lhe são passadas pelo servlet principal do aplicativo:

     // recuperamos 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 "", a fim de ativar ou desativar o botão de opção correspondente
txtEnfants
o número de filhos do contribuinte
txtSalaire
seu salário anual
txtImpots
o valor do imposto a pagar
erreurs
uma eventual lista de erros, caso 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 é restaurar o formulário ao seu estado inicial: botão desmarcado, campos de preenchimento vazios. Observe-se que esse resultado não poderia ser obtido com um botão HTML do tipo “reset”. De fato, quando esse tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. No entanto, em nosso aplicativo, o navegador recebe formulários que podem não estar vazios.

O servlet principal do aplicativo se chama main.java e 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 ocorreu corretamente?
        if(msgErreur!=null){
             // passamos o controle 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;

        // recuperando 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 parâmetro para a URL de exibição do imposto
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // temos todos os parâmetros  vamos verificá-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, eles 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 demais 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);

         // passamos o controle para a URL de exibiçã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(){
         // recuperamos 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;
        }

         // criando uma instância de impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • O método `init` do servlet realiza duas ações:
    • ela recupera seus parâmetros de inicialização. Esses parâmetros permitem que ela se conecte ao banco de dados ODBC, que contém os dados das diferentes faixas de impostos (DSNimpots, admimpots, mdpimpots) e os URL das páginas associadas ao aplicativo: urlAffichageImpots para o formulário, urlErreur para a página de erro.
    • ela cria uma instância da classe impotsJDBC

Em ambos os casos, os possíveis erros são tratados e a mensagem de erro é armazenada na variável msgErreur.

  • O método doGET
    • verifica, em primeiro lugar, se o servlet foi inicializado corretamente. Caso contrário, a página de erro é exibida
    • recupera os parâmetros esperados do formulário de impostos: optMarie, txtEnfants, txtSalaire. Se algum deles estiver faltando (==null), um formulário de impostos vazio é enviado. Pode-se pensar que a verificação da validade do parâmetro optMarie é desnecessária. Esse parâmetro corresponde ao valor de um botão de opção e, neste caso, só pode assumir um dos valores “oui” ou “non”. Isso é esquecer que nada impede que um programa consulte diretamente o servlet, enviando-lhe os parâmetros que desejar. Nunca se pode ter certeza de que se está realmente conectado a um navegador. Esquecer esse ponto pode levar a falhas de segurança na aplicação e, aliás, é comum encontrá-las até mesmo em aplicações comerciais.
    • A validade de cada um dos três parâmetros recuperados é verificada. Cada erro encontrado é adicionado a uma lista de erros (ArrayList erros). Se não houver erros, o valor do imposto é calculado; caso contrário, não é calculado.
    • As informações necessárias para a exibição da página são inseridas 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>

O aplicativo web se chama “impots” e está configurado no arquivo server.xml do Tomcat da seguinte maneira:

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

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

Image

Image

Image

Image

O arquivo de configuração da aplicação impots, 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 se chama main e tem o alias /main. Portanto, ele pode ser acessado por meio de URL e http://localhost:8080/impots/main.

Aqui estão alguns exemplos de aplicação:

Para ser inicializada corretamente, a servlet precisa ter acesso ao banco de dados mysql-dbimpots. Veja a página exibida se, por exemplo, o servidor MySQL não estiver em execução e, consequentemente, o banco de dados mysql-dbimpots estiver inacessível:

Image

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

Image

Se os dados inseridos 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 por meio de um script JavaScript incluído na página do formulário. Assim, é o navegador que realiza a verificação dos parâmetros. O servidor só é acionado se estes forem válidos. Dessa forma, economiza-se “largura de banda”. A página de exibição JSP fica da seguinte forma:

<%@ 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 enviá-los ao 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
          // tudo certo — o formulário é enviado ao 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>

Observe as seguintes alterações:

  • o botão Calculer não é mais do tipo submit, mas sim do tipo button, associado a uma função chamada calculer. É essa função que fará a análise dos campos txtEnfants e txTsalaire. Se estiverem corretos, os valores do formulário serão enviados ao servidor (submit); caso contrário, será exibida uma mensagem de erro.

Veja um exemplo de exibição em caso de erro:

Image

5.4. Versão 3

Modificamos ligeiramente o aplicativo para introduzir o conceito de sessão. Consideramos agora que o aplicativo é um simulador de cálculo de impostos. Um usuário pode, então, simular diferentes “configurações” de contribuinte e verificar 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 modificado e agora se chama simulations. Ele está configurado da seguinte forma na aplicação impots:

<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    ....
</servlet>
    <servlet>
      <servlet-name>simulations</servlet-name>
    <servlet-class>simulations</servlet-class>
    <init-param>
          <param-name>urlSimulationImpots</param-name>
        <param-value>/simulationsImpots.jsp</param-value>
    </init-param>
    <init-param>
          <param-name>DSNimpots</param-name>
        <param-value>mysql-dbimpots</param-value>
    </init-param>
    <init-param>
          <param-name>admimpots</param-name>
        <param-value>admimpots</param-value>
    </init-param>
    <init-param>
          <param-name>mdpimpots</param-name>
        <param-value>mdpimpots</param-value>
    </init-param>    
    <init-param>
          <param-name>urlErreur</param-name>
        <param-value>/erreur.jsp</param-value>
    </init-param>    
  </servlet>
......
  <servlet-mapping>
      <servlet-name>simulations</servlet-name>
    <url-pattern>/simulations</url-pattern>
  </servlet-mapping>
</web-app>

O servlet principal se chama simulations e utiliza o arquivo de classe simulations.class. Ela possui o alias /simulations, o que a torna acessível por meio do URL e do http://localhost:8080/impots/simulations. Ela possui os mesmos parâmetros de inicialização que a servlet principal analisada anteriormente no que diz respeito ao acesso ao banco de dados. Surge um novo parâmetro, urlSimulationsImpots, que corresponde à URL da página de simulação JSP (aquela que acabou de ser apresentada um pouco acima).

A servlet simulations.java é semelhante à servlet main.java. Ela 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 exibição JSP
  • o servlet de simulações calcula, da mesma forma, o valor txtImpots, grava os parâmetros (optMarie, txtEnfants, txtsalaire, txtImpots) em uma lista chamada “simulações”. É essa lista que é passada como parâmetro para a página de exibição JSP. Para que essa lista contenha todas as simulações realizadas pelo usuário, ela é gravada como um 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();
        // insere-se as simulações na consulta atual
        request.setAttribute("simulations",simulations);

         // outros atributos da consulta
...........

         // estamos com todos os parâmetros esperados?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // passamos o controle para a URL de exibição das simulações de cálculo de impostos
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // temos todos os parâmetros  verificamos se estão corretos
...........

         // se houver erros, eles são passados como 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 é inserido na sessão
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // outros atributos da consulta
..........

         // passa-se o controle para a URL de exibiçã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 exibição JSP, que era simulationsImpots.jsp, passou a ter a seguinte aparência (foi mantido apenas o código que difere da página de exibição JSP do aplicativo anterior).

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

<%
     // recuperando 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>
    <%
         // há 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

Agora vamos criar um aplicativo autônomo que servirá como cliente web do aplicativo /impots/simulations anterior. Esse aplicativo terá a seguinte interface gráfica:

n.º
tipo
nome
função
1
JTextField
txtUrlServiceImpots
URL do serviço de simulação de cálculo de impostos
2
JRadioButton
rdOui
marque se for casado
3
JRadioButton
rdNon
marque 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 estiverem presentes e corretos
 
Apagar
mnuEffacer
recupera o formulário ao seu estado inicial
 
Sair
mnuQuitter
encerra o aplicativo

Regras de funcionamento

  • a opção de menu Calculer permanece desativada se um dos campos 1 ou 5 estiver vazio
  • é detectada uma sintaxe incorreta de URL no campo 1

Image

  • for detectado um salário incorreto

Image

  • qualquer erro de conexão com o servidor é sinalizado (no exemplo 1 abaixo, a porta está incorreta — no exemplo 2, o URL solicitado não existe — no exemplo 3, o banco de dados MySQL não estava em execução)

Image

Image

Image

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

Image

Ao desenvolver um cliente web programado, é 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 do Java podem nos ajudar a identificar as informações úteis no fluxo de linhas enviadas pelo servidor. Para isso, precisamos conhecer o formato exato das diferentes respostas do servidor. Aqui, vamos utilizar o cliente web já apresentado, que permite exibir na tela a resposta de um servidor à 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, embora a base MySQL, que permite criar um objeto do tipo impots, não esteja 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 deverá procurar na resposta do servidor web a linha que contenha o texto “Aplicativo indisponível”. Agora, vamos iniciar a base MySQL e solicitar a mesma URL, passando a ela os valores que ela espera (ela os aceita indistintamente na forma de um GET ou de um POST — consulte o código Java do servidor):


Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET

HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots


<html>

        <head>
        <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...................................................
      }//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 está o documento completo HTML enviado pelo servidor. O resultado das diferentes simulações pode ser encontrado na única tabela presente neste documento. A expressão regular que nos permite extrair as informações relevantes do documento pode 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 serem recuperadas.

Agora temos as diretrizes sobre o que fazer quando o usuário 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.
  • conectar-se ao URL indicado no campo 1. Para isso, seguiremos o modelo do cliente web genérico já apresentado e estudado
  • no fluxo da resposta do servidor, usar expressões regulares para:
    • localizar a mensagem de erro, caso haja alguma
    • localizar os resultados das simulações, caso não haja erros

O código relacionado 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();
             // retorno à interface
            return;
        }
     // verificação de 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){
             // exibe 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 Receita Federal
         // casado: true se for casado, false caso contrário
         // nbEnfants: número de filhos
         // salário: salário anual

         // extrai-se de urlImpots as informações necessárias para a conexão com o servidor da Receita Federal
        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 gravação 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{
             // é estabelecida a conexão com o servidor
            client=new Socket(host,port);

            // são criados 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("");

             // -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()){
                //  um problema com URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(resultado)

             // -se a resposta até o final dos cabeçalhos, procurando por um eventual cookie
            while((réponse=IN.readLine())!=null){
                 // linha vazia?
                if(réponse.equals("")) break;
                // linha HTTP não vazia
                 // se não houver o token da sessão, procura-se por ele
                if (JSESSIONID.equals("")){
                    // comparamos a linha HTTP com o modelo do cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // encontramos o cookie do token
                        JSESSIONID=résultat.group(1);
                    }//if(resultado)
                }//if(JSESSIONID)
            }//enquanto

             // fim dos cabeçalhos HTTP  passando 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();

         // são lidas todas as linhas até o final
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // acompanhamento
             // compara-se a linha com o modelo de erro, caso a seção de 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 esse código:

  • o procedimento mnuCalculer_actionPerformed verifica se os dados da interface são válidos. Se não forem, é exibida uma mensagem de erro e o procedimento é encerrado. Se forem, o procedimento calculerImpots é executado.
  • O procedimento calculerImpots começa criando o URL, que ele precisa solicitar
         // são extraídas de urlImpots as informações necessárias para a conexão com o servidor da Receita Federal
        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, se conecta a esse URL enviando os cabeçalhos HTTP adequados:
             // conecta-se 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("");
  • Após a solicitação ser enviada, nosso cliente web aguarda a resposta. Primeiramente, ele recebe os cabeçalhos HTTP da resposta do servidor. Ele analisa esses cabeçalhos para localizar o token da sessão. De fato, ele precisará reenviar esse token ao servidor para que este possa manter um registro das diferentes simulações realizadas. Vale ressaltar aqui que teria sido mais simples se o cliente memorizasse por conta própria as diferentes simulações realizadas pelo usuário. No entanto, mantemos a ideia de reenviar o token para fornecer um novo exemplo de gerenciamento de sessão. A primeira linha da resposta é tratada separadamente. De fato, ela deve ter o formato HTTP/versão 200 OK para indicar que o URL solicitado realmente existe. Portanto, se ela não estiver nesse formato, conclui-se que o usuário solicitou um URL incorreto e isso é informado a ele.
         // 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()){
                //  um problema com URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(resultado)

             // lê-se a resposta até o final dos cabeçalhos, procurando por um eventual cookie
            while((réponse=IN.readLine())!=null){
                 // linha vazia?
                if(réponse.equals("")) break;
                // linha HTTP não vazia
                 // se não houver o token da sessão, procura-se por ele
                if (JSESSIONID.equals("")){
                    // comparamos a linha HTTP com o modelo do cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // encontramos o cookie do token
                        JSESSIONID=résultat.group(1);
                    }//if(resultado)
                }//if(JSESSIONID)
            }//enquanto
  • Após a análise dos cabeçalhos HTTP, passa-se para a 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 retorna a lista de simulações, caso existam; essa lista ficará vazia se o servidor retornar uma mensagem de erro. Nesse caso, a mensagem é exibida em uma caixa de mensagem. Se a lista não estiver vazia, ela é exibida 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 (Aplicativo indisponível...) e com a expressão regular que representa uma simulação. Se a mensagem de erro for encontrada, ela é exibida e o procedimento é encerrado. Se for encontrado o resultado de uma simulação, ele é adicionado à lista de simulações. Ao final do procedimento, essa lista é retornada 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 o aplicativo gráfico independente anterior em um applet Java. A interface gráfica é ligeiramente diferente. No aplicativo independente, o usuário fornecia ele mesmo o URL do serviço de simulação de cálculos de impostos e, em seguida, o aplicativo se conectava a esse URL. Aqui, o aplicativo cliente é um navegador, e o usuário solicitará o URL do documento HTML que contém o applet. É preciso lembrar, agora, que um applet Java só pode estabelecer uma conexão de rede com o servidor do qual foi baixado. 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 usuário. Assim, teremos o seguinte cliente:

Image

O documento HTML que contém o applet é chamado simulations.htm e tem o seguinte conteúdo:

<html>
    <head>
      <title>Simulations de calculs d'impôts</title>
  </head>
  <body background="/impots/images/standard.jpg">
      <center>
          <h3>Simulations de calculs d'impôts</h3>
        <hr>
        <applet code="appletImpots.class" width="400" height="360">
            <param name="urlServiceImpots" value="simulations">
            </applet>
    </center>
  </body>
</html>  

O applet possui um parâmetro urlServiceImpots, que corresponde ao URL do serviço de simulações de cálculo de impostos. Esse URL é relativo e medido em relação ao URL do documento HTML simulations.htm. Assim, se um navegador obtiver esse 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 esse URL tivesse sido http://stahe:8080/impots/simulations.htm, o URL do serviço de simulação teria sido http://stahe:8080/impots/simulations.

O applet appletImpots.java reproduz integralmente o código do aplicativo gráfico autônomo anterior, respeitando as regras de transformação de um aplicativo gráfico em 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 a estrutura
  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 ausente
            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 campo correspondente
        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, ele executa, em primeiro lugar, seu procedimento init. Nesse procedimento, recuperamos o valor do parâmetro urlServiceImpots do applet, calculamos o nome completo do URL do serviço de simulação e inserimos esse valor no campo txtURLServiceImpots como se tivesse sido digitado pelo usuário. Feito isso, não há mais diferença entre as duas aplicações. Notavelmente, o código associado ao menu Calculer é idêntico. Aqui está um exemplo de execução:

Image

5.7. Conclusão

Mostramos diferentes versões de nosso aplicativo cliente-servidor para cálculo de impostos:

  • versão 1: o serviço é prestado por um conjunto de servlets e páginas JSP; o cliente é um navegador. Ele realiza uma única simulação e não mantém registro das anteriores.
  • versão 2: adicionamos algumas funcionalidades no lado do navegador, inserindo scripts JavaScript no documento HTML carregado por ele. O navegador verifica a validade dos parâmetros do formulário.
  • versão 3: permite-se que o serviço lembre-se das diferentes simulações realizadas por um cliente por meio do gerenciamento de uma sessão. A interface HTML é modificada de acordo para exibi-las.
  • versão 4: o cliente passa a ser um aplicativo gráfico autônomo. Isso nos permite voltar a desenvolver clientes web programados.
  • versão 5: o cliente se transforma em um applet Java. Passamos então a ter um aplicativo cliente-servidor inteiramente programado em Java, tanto no lado do servidor quanto no lado do cliente.

Nesse ponto, podemos fazer algumas observações:

  • as versões 1 a 3 permitem navegadores sem nenhuma capacidade além da de executar scripts JavaScript. Vale ressaltar que o usuário sempre tem a possibilidade de desativar a execução desses scripts. O aplicativo funcionará, então, apenas parcialmente na versão 1 (a opção “Apagar” não funcionará) e não funcionará de forma alguma nas versões 2 e 3 (as opções “Apagar” e “Calcular” não funcionarão). Poderia ser interessante desenvolver uma versão do serviço que não utilize scripts JavaScript.
  • A versão 4 requer que o computador do usuário possua uma máquina virtual Java 2.
  • A versão 5 requer que o computador do cliente possua um navegador com uma máquina virtual Java 2.

Ao desenvolver um serviço web, é preciso considerar quais tipos de clientes se deseja atingir. Se o objetivo for atingir o maior número possível de clientes, deve-se desenvolver um aplicativo que envie aos navegadores apenas HTML (sem JavaScript nem applets). Se estivermos trabalhando em uma intranet e tivermos controle sobre a configuração dos computadores dela, podemos então nos permitir 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 extraem as informações de que precisam do fluxo HTML enviado pelo servidor. Muitas vezes, não temos controle sobre esse fluxo. Esse é o caso quando se desenvolve um cliente para um serviço web existente na rede e gerenciado por outra pessoa. Vejamos um exemplo. Suponhamos que 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 em uma tabela HTML e nosso cliente aproveita esse fato para recuperá-las. Assim, ele 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 desenvolvedor do aplicativo altere a aparência visual da resposta, colocando as simulações não em uma tabela, mas em uma lista no formato:

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

Nesse caso, nosso cliente web precisará ser reescrito. Essa é a ameaça constante que paira sobre os clientes web de aplicativos que não controlamos diretamente. O XML pode oferecer uma solução para esse problema:

  • em vez de gerar HTML, o serviço de simulações irá gerar XML. No nosso exemplo, isso poderia ser
<simulations>
    <entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
  <simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
  • uma folha de estilo poderia ser associada a essa resposta, indicando aos navegadores a aparência que devem dar a essa resposta XML
  • os clientes web programados ignorariam essa folha de estilo e recuperariam as informações diretamente do fluxo XML da resposta

Se o desenvolvedor do serviço desejar alterar a apresentação visual dos resultados fornecidos, ele modificará a folha de estilo e não o XML. Graças à folha de estilo, os navegadores exibirão o novo formato visual e os clientes web programados não precisarão ser alterados. Novas versões do nosso serviço de simulações poderiam, portanto, ser desenvolvidas:

  • versão 6: o serviço fornece uma resposta XML acompanhada de uma folha de estilo destinada aos navegadores
  • versão 7: o cliente é um aplicativo gráfico autônomo que utiliza a resposta XML do servidor
  • versão 8: o cliente é um applet Java que utiliza a resposta XML do servidor