Skip to content

5. A aplicação de cálculo de impostos

5.1. Introdução

Aqui, revisitamos a aplicação IMPOTS, que é utilizada repetidamente no manual de Java do mesmo autor. Vamos rever o problema. O objetivo é escrever uma aplicação que calcule a obrigação fiscal de um contribuinte. Vamos considerar o caso simplificado de um contribuinte que tem apenas um único salário a declarar:

  • calculamos o número de escalões de imposto do empregado como nbParts = nbEnfants / 2 + 1 se for solteiro, e nbEnfants / 2 + 2 se for casado, onde nbEnfants é o número de filhos.
  • se tiver pelo menos três filhos, recebe uma meia quota adicional
  • Calculamos o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
  • Calculamos o seu coeficiente familiar QF = R / nbParts
  • calculamos o seu imposto I. Considere a seguinte tabela:
12620,0
0
0
13 190
0,05
631
15 640
0,1
1.290,5
24.740
0,15
2.072,5
31 810
0,2
3.309,5
39 970
0,25
4.900
48 360
0,3
6.898,5
55 790
0,35
9.316,5
92 970
0,4
12 106
127 860
0,45
16 754,5
151 250
0,50
23 147,5
172 040
0,55
30 710
195 000
0,60
39 312
0
0,65
49062

Cada linha tem 3 campos. Para calcular o imposto I, encontre a primeira linha em que QF <= campo1. Por exemplo, se QF = 23 000, a linha encontrada será

    24740        0.15        2072.5

O Imposto I é então igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a condição QF<=field1 nunca seja satisfeita, então são utilizados os coeficientes da última linha. Aqui:

    0                0.65        49062

o que dá o imposto I = 0,65*R - 49062*nbParts.

Os dados que definem os diferentes escalões de imposto estão armazenados numa base de dados ODBC-MySQL. O MySQL é um SGBD de domínio público que pode ser utilizado em várias plataformas, incluindo Windows e Linux. Utilizando este SGBD, foi criada uma base de dados denominada dbimpots, contendo uma única tabela chamada impots. O acesso à base de dados é controlado por um nome de utilizador/palavra-passe, neste caso admimpots/mdpimpots. A captura de ecrã seguinte mostra como utilizar a base de dados dbimpots com o MySQL:


C:\Program Files\EasyPHP\mysql\bin>mysql -u admimpots -p
Enter password: *********
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 18 to server version: 3.23.49-max-nt
 
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
 
mysql> use dbimpots;
Database changed
 
mysql> show tables;
+--------------------+
| Tables_in_dbimpots |
+--------------------+
| impots             |
+--------------------+
1 row in set (0.00 sec)
 
mysql> describe impots;
+---------+--------+------+-----+---------+-------+
| Field   | Type   | Null | Key | Default | Extra |
+---------+--------+------+-----+---------+-------+
| limites | double | YES  |     | NULL    |       |
| coeffR  | double | YES  |     | NULL    |       |
| coeffN  | double | YES  |     | NULL    |       |
+---------+--------+------+-----+---------+-------+
3 rows in set (0.02 sec)
 
mysql> select * from impots;
+---------+--------+---------+
| limites | coeffR | coeffN  |
+---------+--------+---------+
|   12620 |      0 |       0 |
|   13190 |   0.05 |     631 |
|   15640 |    0.1 |  1290.5 |
|   24740 |   0.15 |  2072.5 |
|   31810 |    0.2 |  3309.5 |
|   39970 |   0.25 |    4900 |
|   48360 |    0.3 |    6898 |
|   55790 |   0.35 |  9316.5 |
|   92970 |    0.4 |   12106 |
|  127860 |   0.45 |   16754 |
|  151250 |    0.5 | 23147.5 |
|  172040 |   0.55 |   30710 |
|  195000 |    0.6 |   39312 |
|       0 |   0.65 |   49062 |
+---------+--------+---------+
14 rows in set (0.00 sec)
 
mysql>quit

A base de dados dbimpots é convertida numa fonte de dados ODBC da seguinte forma:

  • Inicie o Administrador de Fontes de Dados ODBC de 32 bits

Image

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

Image

  • Selecione o controlador MySQL e clique em [Concluir]

54321

Image

  • O controlador MySQL solicita algumas informações:
1
o nome do DSN a atribuir à fonte de dados ODBC — pode ser qualquer nome
2
a máquina na qual o SGBD MySQL está a ser executado — neste caso, localhost. Vale a pena notar que a base de dados poderia ser uma base de dados remota. As aplicações locais que utilizam a fonte de dados ODBC não teriam conhecimento disso. Este seria o caso, em particular, da nossa aplicação Java.
3
a base de dados MySQL a utilizar. O MySQL é um SGBD que gere bases de dados relacionais, que são conjuntos de tabelas ligadas entre si por relações. Aqui, especificamos o nome da base de dados que está a ser gerida.
4
O nome de um utilizador com direitos de acesso a esta base de dados
5
a sua palavra-passe

Foram definidas duas classes para calcular o imposto: impots e impotsJDBC. Uma instância da classe impots é criada utilizando os dados das faixas de imposto passados como parâmetros em matrizes:

// creation of an impots class

public class impots{

  // data required for tax calculation
   // come from an external source

  protected double[] limites=null;
  protected double[] coeffR=null;
  protected double[] coeffN=null;

   // empty builder
  protected impots(){}

   // manufacturer
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // check that the 3 arrays have the same size
    boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
    if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
      LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
    // it's good
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//manufacturer

   // tAX CALCULATION
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // calculating the number of shares
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

A classe impotsJDBC deriva da classe impots anterior. Uma instância da classe impotsJDBC é construída utilizando dados de escalões fiscais armazenados numa base de dados. A informação necessária para aceder a esta base de dados é passada como parâmetros ao construtor:

// imported packages
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // addition of a constructor for building
   // limit tables, coeffr, coeffn from table
   // database taxes
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: DSN database name
     // userIMPOTS, mdpIMPOTS: database login/password

     // data tables
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // connection to base
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creation of a Statement object
    Statement S=connect.createStatement();
    // select request
    String select="select limites, coeffr, coeffn from impots";
    // query execution
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // running line operation
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// next line
     // closing resources
    RS.close();
    S.close();
    connect.close();
     // data transfer to bounded arrays
    int n=aLimites.size();
    limites=new double[n];
    coeffR=new double[n];
    coeffN=new double[n];
    for(int i=0;i<n;i++){
      limites[i]=Double.parseDouble((String)aLimites.get(i));
      coeffR[i]=Double.parseDouble((String)aCoeffR.get(i));
      coeffN[i]=Double.parseDouble((String)aCoeffN.get(i));
    }//for
  }//manufacturer
}//class

Depois de criada uma instância da classe impotsJDBC, o seu método calculate pode ser chamado repetidamente para calcular o imposto:

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

Os três dados necessários podem ser obtidos de várias formas. A vantagem da classe impotsJDBC é que só precisamos de nos preocupar em obter esses dados. Assim que as três informações (estado civil, número de filhos, salário anual) forem obtidas, chamar o método calculer da classe impotsJDBC dá-nos o valor do imposto a pagar.

5.2. Versão 1

Estamos no contexto de uma aplicação web que apresentaria uma interface HTML a um utilizador para obter os três parâmetros necessários para calcular o imposto:

  • estado civil (casado ou não)
  • número de filhos
  • rendimento anual

Image

O formulário é apresentado pela seguinte página JSP:

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

<%
    // on récupère les attributs passés par la servlet principale
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
          // raz du formulaire
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//with
      }//effacer
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/main" method="POST">
          <table>
            <tr>
              <td>Etes-vous marié(e)</td>
            <td>
                    <input type="radio" name="optMarie" value="oui" <%= chkoui %>>oui
              <input type="radio" name="optMarie" value="non" <%= chknon %>>non
            </td>
          </tr>
          <tr>
              <td>Nombre d'enfants</td>
            <td><input type="text" size="5" name="txtEnfants" value="<%= txtEnfants %>"></td>
          </tr>
          <tr>
              <td>Salaire annuel</td>
            <td><input type="text" size="10" name="txtSalaire" value="<%= txtSalaire %>"></td>
          </tr>
          <tr>
              <td><font color="green">Impôt</font></td>
            <td><input type="text" size="10" name="txtImpots" value="<%= txtImpots %>" readonly></td>
          </tr>
          <tr></tr>
          <tr>
              <td><input type="submit" value="Calculer"></td>
            <td><input type="button" value="Effacer" onclick="effacer()"></td>
          </tr>
        </table>
      </form>
    </center>
    <%
        // y-a-t-il des erreurs
      if(erreurs!=null){
          // affichage des erreurs
        out.println("<hr>");
        out.println("<font color=\"red\">");
        out.println("Les erreurs suivantes se sont produites<br>");
        out.println("<ul>");
        for(int i=0;i<erreurs.size();i++){
            out.println("<li>"+(String)erreurs.get(i));
        }
        out.println("</ul>");
        out.println("</font>");
      } 
   %>
 </body>
</html>

A página JSP simplesmente apresenta as informações que lhe são passadas pelo servlet principal da aplicação:

     // retrieve attributes passed by the main servlet
  String chkoui=(String)request.getAttribute("chkoui");
  String chknon=(String)request.getAttribute("chknon");
  String txtEnfants=(String)request.getAttribute("txtEnfants");
  String txtSalaire=(String)request.getAttribute("txtSalaire");
  String txtImpots=(String)request.getAttribute("txtImpots");
  ArrayList erreurs=(ArrayList)request.getAttribute("erreurs");
chkoui, chknon
Os atributos "checked" e "unchecked" dos botões de opção podem ter os valores "checked" ou "unchecked" para indicar se o botão de opção correspondente está selecionado ou não
txtChildren
o número de filhos do contribuinte
txtSalary
o seu salário anual
txtTaxes
o montante de impostos a pagar
erros
uma lista de erros, se houver, se erros!=null

A página enviada ao cliente contém um script JavaScript com uma função «clear» associada ao botão «Limpar», cujo objetivo é repor o formulário ao seu estado inicial: botões desmarcados, campos de entrada vazios. Note que este resultado não poderia ser alcançado com um botão «reset» em HTML. Com efeito, quando este tipo de botão é utilizado, o navegador repõe o formulário no estado em que o recebeu. No entanto, na nossa aplicação, o navegador recebe formulários que podem não estar vazios.

O servlet principal da aplicação chama-se main.java, e o seu código é o seguinte:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.regex.*;
import java.util.*;

public class main extends HttpServlet{

    // instance variables
    String msgErreur=null;
    String urlAffichageImpots=null;
    String urlErreur=null;
    String DSNimpots=null;
    String admimpots=null;
    String mdpimpots=null;
    impotsJDBC impots=null;

    //-------- GET
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{

        // was the initialization successful?
        if(msgErreur!=null){
             // we hand over to the error page
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // query attributes
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // retrieve query parameters
        String optMarie=request.getParameter("optMarie");              // marital status
        String txtEnfants=request.getParameter("txtEnfants");       // no. of children
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // annual salary
        if(txtSalaire==null) txtSalaire="";

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // missing parameters
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // we hand over to the tax display url
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // we have all the parameters - we check them
        ArrayList erreurs=new ArrayList();
         // marital status
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // error
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // number of children
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // error
            erreurs.add("Nombre d'enfants incorrect");
        }
         // salary
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // error
            erreurs.add("Salaire incorrect");
        }

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // you can calculate the tax payable
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // other query attributes
        if(optMarie.equals("oui")){
            request.setAttribute("chkoui","checked");
            request.setAttribute("chknon","");
        }else{
            request.setAttribute("chknon","checked");
            request.setAttribute("chkoui","");
        }
        request.setAttribute("txtEnfants",txtEnfants);
        request.setAttribute("txtSalaire",txtSalaire);
        request.setAttribute("txtImpots",txtImpots);

         // we hand over to the tax display url
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

     //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlAffichageImpots=config.getInitParameter("urlAffichageImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parameters ok?
        if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         // create an instance of impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • O método init do servlet faz duas coisas:
    • Recupera os seus parâmetros de inicialização. Estes permitem-lhe ligar-se à base de dados ODBC que contém os dados relativos aos vários escalões de imposto (DSNimpots, admimpots, mdpimpots) e os URLs das páginas associadas à aplicação: urlAffichageImpots para o formulário, urlErreur para a página de erro.
    • Cria uma instância da classe impotsJDBC

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

  • O método doGET
    • verifica primeiro se o servlet foi inicializado corretamente. Se não for esse o caso, a página de erro é apresentada
    • recupera os parâmetros esperados do formulário fiscal: optMarie, txtEnfants, txtSalaire. Se algum deles estiver em falta (==null), é enviado um formulário fiscal vazio. Poder-se-ia pensar que a validação do parâmetro optMarie é desnecessária. Este parâmetro corresponde ao valor de um botão de opção e, neste caso, só pode assumir um dos valores «sim» ou «não». No entanto, isto ignora o facto de que nada impede um programa de consultar diretamente o servlet, enviando-lhe os parâmetros que deseja. Nunca se pode ter a certeza de que se está realmente ligado a um navegador. Ignorar este ponto pode levar a vulnerabilidades de segurança na aplicação e, na verdade, é comum encontrar tais problemas mesmo em aplicações comerciais.
    • A validade de cada um dos três parâmetros recuperados é verificada. Quaisquer erros encontrados são adicionados a uma lista de erros (ArrayList errors). Se não houver erros, o valor do imposto é calculado; caso contrário, não é.
    • As informações necessárias para apresentar a página são definidas como atributos de pedido, sendo então apresentado o formulário de impostos

A página JSP de erro é a seguinte:

<%
    // jspService
  // une erreur s'est produite
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- top of page HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

A aplicação web chama-se impots e está configurada no ficheiro server.xml do Tomcat da seguinte forma:

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

O diretório da aplicação contém os seguintes diretórios e ficheiros:

Image

Image

Image

Image

O ficheiro de configuração web.xml para a aplicação impots é o seguinte:

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

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

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

O servlet principal chama-se main e tem o alias /main. Por conseguinte, é acessível através do URL http://localhost:8080/impots/main.

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

Para ser inicializado corretamente, o servlet deve ter acesso à base de dados mysql-dbimpots. Aqui está a página apresentada se, por exemplo, o servidor MySQL não estiver a funcionar e a base de dados mysql-dbimpots estiver, por isso, inacessível:

Image

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

Image

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

Image

5.3. Versão 2

No exemplo anterior, o servidor valida os parâmetros txtEnfants e txtSalaire no formulário. Aqui, propomos validá-los utilizando um script JavaScript incluído na página do formulário. O navegador realiza então a validação dos parâmetros. O servidor só é contactado se os parâmetros forem válidos. Isto poupa «largura de banda». A página de apresentação JSP fica da seguinte forma:

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

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
.......
      }//effacer

      function calculer(){
          // vérification des paramètres avant de les envoyer au serveur
        with(document.frmImpots){
          //nbre d'enfants
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//if
          //salaire
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // le modéle n'est pas vérifié
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//if
          // c'est bon - on envoie le formulaire au serveur
          submit();
        }//with
      }//calculer        
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
........
              <td><input type="button" value="Calculer" onclick="calculer()"></td>
          <td><input type="button" value="Effacer" onclick="effacer()"></td>
.....
 </body>
</html>

Observe as seguintes alterações:

  • O botão «Calculate» já não é um botão de envio, mas sim um botão normal associado a uma função chamada «calculate». Esta função irá validar os campos «txtEnfants» e «txtSalaire». Se forem válidos, os dados do formulário serão enviados para o servidor; caso contrário, será apresentada uma mensagem de erro.

Eis um exemplo do que é apresentado em caso de erro:

Image

5.4. Versão 3

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

Image

O servlet principal foi modificado e chama-se agora simulations. Está configurado da seguinte forma na aplicação impots:

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

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

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

O servlet principal chama-se simulations e baseia-se no ficheiro simulations.class. Tem o alias /simulations, o que o torna acessível através do URL http://localhost:8080/impots/simulations. Tem os mesmos parâmetros de inicialização que o servlet principal discutido anteriormente relativamente ao acesso à base de dados. Surge um novo parâmetro, **urlSimulationsImpots,** que é o URL da página JSP de simulação (a que acabou de ser apresentada acima).

O servlet simulations.java é semelhante ao servlet main.java. Difere deste nas seguintes aspetos principais:

  • O servlet principal calcula um valor para `txtImpots` com base nos parâmetros `optmarie`, `txtEnfants` e `txtSalaire`, e passa esse valor para a página JSP de exibição
  • O servlet simulations calcula o valor de txtImpots da mesma forma e armazena os parâmetros (optMarie, txtEnfants, txtsalaire, txtImpots) numa lista chamada simulations. Esta lista é passada como parâmetro para a página JSP de visualização. Para garantir que esta lista contém todas as simulações realizadas pelo utilizador, é armazenada como um atributo da sessão atual.

O servlet de simulações é o seguinte (foram incluídas apenas as linhas de código que diferem das da aplicação anterior):

import java.io.*;
.......

public class simulations extends HttpServlet{

    // instance variables
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

    //-------- GET
    public void doGet(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{

...........

         // retrieve previous simulations from the session
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // put the simulations in the current query
        request.setAttribute("simulations",simulations);

         // other query attributes
...........

         // do we have all the expected parameters
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // we hand over to the url for displaying tax calculation simulations
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // we have all the parameters - we check them
...........

         // if there are errors, they are passed as query attributes
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // you can calculate the tax payable
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // the current result is added to the previous simulations
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // the new simulations value is added to the session
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // other query attributes
..........

         // we hand over to the simulations display url
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

     //-------- POST
    public void doPost(HttpServletRequest request, HttpServletResponse response)
    throws IOException, ServletException{
        doGet(request,response);
    }

     //-------- INIT
    public void init(){
         // retrieve initialization parameters
        ServletConfig config=getServletConfig();
        urlSimulationImpots=config.getInitParameter("urlSimulationImpots");
        urlErreur=config.getInitParameter("urlErreur");
        DSNimpots=config.getInitParameter("DSNimpots");
        admimpots=config.getInitParameter("admimpots");
        mdpimpots=config.getInitParameter("mdpimpots");

         // parameters ok?
.........................
    }
}

A página JSP de visualização simulationsImpots.jsp tem agora o seguinte aspeto (apenas foi mantido o código que difere da página JSP de visualização da aplicação anterior).

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

<%
    // on récupère les attributs passés par la servlet principale
...........
  ArrayList simulations=(ArrayList)request.getAttribute("simulations");  
%>

<html>

    <head>
      <title>impots</title>
    <script language="JavaScript" type="text/javascript">
........
        </script>
  </head>

  <body background="/impots/images/standard.jpg">
      <center>
        Calcul d'impôts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
.......................
           </form> 
</center>
    <hr>
    <%
        // y-a-t-il des erreurs ?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // résultats des simulations
        out.println("<h3>Résultats des simulations<h3>");
        out.println("<table \"border=\"1\">");
        out.println("<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>");
        for(int i=0;i<simulations.size();i++){
            String[] simulation=(String[])simulations.get(i);
            out.println("<tr><td>"+simulation[0]+"</td><td>"+simulation[1]+"</td><td>"+simulation[2]+"</td><td>"+simulation[3]+"</td></tr>");
        }
        out.println("</table>");
      }
   %>
 </body>
</html>

5.5. Versão 4

Vamos agora criar uma aplicação autónoma que servirá como cliente web para a aplicação /impots/simulations anterior. Esta aplicação terá a seguinte interface gráfica:

N.º
tipo
nome
função
1
JTextField
txtTaxServiceUrl
URL do serviço de simulação de cálculo de impostos
2
JRadioButton
rdYes
Marcado se for casado
3
JRadioButton
rdNo
Marcado se solteiro
4
JSpinner
spinChildren
número de filhos do contribuinte (mínimo=0, máximo=20, incremento=1)
5
JTextField
txtSalário
salário anual do contribuinte em F
6
JList em JScrollPane
lstSimulations
lista de simulações

O menu «Tax» (Impostos) é composto pelas seguintes opções:

principal
principal
opção
secundário
nome
função
Impostos
   
 
Calcular
mnuCalculate
Calcula o imposto devido quando todos os dados necessários para o cálculo estão presentes e corretos
 
Limpar
mnuLimpar
redefine o formulário para o seu estado inicial
 
Sair
mnuExit
fecha a aplicação

Regras de funcionamento

  • A opção de menu «Calcular» permanece desativada se o campo 1 ou 5 estiver vazio
  • É detetada uma URL com erros sintáticos no campo 1

Image

  • for detetado um salário incorreto

Image

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

Image

Image

Image

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

Image

Ao escrever um cliente web programado, é necessário saber exatamente o que o servidor envia em resposta às várias solicitações possíveis de um cliente. O servidor envia um conjunto de linhas HTML contendo informações úteis e outros elementos que existem exclusivamente para o layout HTML. As expressões regulares Java podem ajudar-nos a encontrar as informações úteis no fluxo de linhas enviadas pelo servidor. Para isso, precisamos de conhecer o formato exato das várias respostas do servidor. Aqui, utilizaremos o cliente web que vimos anteriormente, que nos permite exibir no ecrã a resposta de um servidor a um pedido de URL. O URL solicitado será o do nosso serviço de simulação de cálculo de impostos, http://localhost:8080/impots/simulations, para o qual podemos passar parâmetros na forma http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

Vamos solicitar a URL enquanto a base de dados MySQL utilizada para criar um objeto do tipo imposto não estiver em execução:


Dos>java clientweb http://localhost:8080/impots/simulations GET
 
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:31:04 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=9DEC8B27966A1FBE3D4968A7B9DF3331;Path=/impots
 
 
<!-- dÚbut from page HTML -->
<html>
  <head>
        <title>impots</title>
  </head>
  <body>
        <h3>calcul d'impôts</h3>
        <hr>
    Application indisponible([TCX][MyODBC]Can't connect to MySQL server on 'localhost' (10061))
  </body>
</html>

Para recuperar o erro, o cliente web deve procurar na resposta do servidor web a linha que contém o texto «Aplicação indisponível». Agora, vamos iniciar a base de dados MySQL e solicitar o mesmo URL, passando-lhe os valores que espera (aceita-os igualmente bem como um pedido GET ou POST — consulte o código Java do servidor):


Dos>java clientweb "http://localhost:8080/impots/simulations?ptMarie=oui&txtEnfants=2&txtSalaire=200000" GET
 
HTTP/1.1 200 OK
Content-Type: text/html;charset=ISO-8859-1
Date: Fri, 16 Aug 2002 16:42:36 GMT
Connection: close
Server: Apache Tomcat/4.0.3 (HTTP/1.1 Connector)
Set-Cookie: JSESSIONID=C2A707600E98A37A343611D80DD5C8A2;Path=/impots
 
 
<html>
 
        <head>
        <title>impots</title>
    <script language="JavaScript" type="text/javascript">
        function effacer(){
...................................................
      }//effacer
 
      function calculer(){
...................................................
      }//calculer
                </script>
  </head>
 
  <body background="/impots/images/standard.jpg">
        <center>
        Calcul d'imp¶ts
        <hr>
      <form name="frmImpots" action="/impots/simulations" method="POST">
...................................................
      </form>
    </center>
    <hr>
    <h3>Résultats des simulations<h3>
<table "border="1">
<tr><td>Marié</td><td>Enfants</td><td>Salaire annuel (F)</td><td>Impôts à payer (F)</td></tr>
<tr><td>oui</td><td>2</td><td>200000</td><td>22504</td></tr>
</table>
 
 </body>
</html>

Aqui está o documento HTML completo enviado pelo servidor. Os resultados das várias simulações encontram-se na tabela única contida neste documento. A expressão regular que nos permite extrair as informações relevantes do documento poderia ser a seguinte:

"<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 extrair.

Temos agora as orientações sobre o que fazer quando o utilizador solicita um cálculo de impostos a partir da interface gráfica anterior:

  • verificar se todos os dados na interface são válidos e comunicar quaisquer erros.
  • ligar-se ao URL especificado no campo 1. Para tal, seguiremos o modelo do cliente web genérico já apresentado e estudado
  • no fluxo de resposta do servidor, usar expressões regulares para:
    • encontrar a mensagem de erro, se houver
    • encontrar os resultados da simulação, caso não haja erros

O código relacionado com o menu «Calcular» é o seguinte:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // tAX CALCULATION
         // verification URL service
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // error msg
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // focus on wrong field
            txtURLServiceImpots.requestFocus();
             // back to interface
            return;
        }
     // salary verification
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // error msg
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // focus on wrong field
      txtSalaire.requestFocus();
       // back to interface
      return;
    }
     // no. of children
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // tax calculation
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // error is displayed
            JOptionPane.showMessageDialog(this,"L'erreur suivante s'est produite : " + ex.getMessage(),"Erreur",JOptionPane.ERROR_MESSAGE);
        }
  }//mnuCalculer


    public void calculerImpots(URL urlImpots,boolean marié, int nbEnfants, int salaire)
          throws Exception{
         // tAX CALCULATION
         // urlImpots : URL of the tax department
         // married: true if married, false otherwise
         // nbEnfants : number of children
         // salary: annual salary

         // remove from urlImpots the info needed to connect to the tax server
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();

         // local data
        Socket  client=null;                        // the customer
        BufferedReader IN=null;                    // the customer's reading flow
        PrintWriter OUT=null;                        // the customer's writing flow
        String réponse=null;                        // server response
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model for a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // the result of the model comparison
        Matcher résultat=null;

        try{
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request URL - send HTTP headers
            OUT.println("GET " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            if(! JSESSIONID.equals("")){
                OUT.println("Cookie: JSESSIONID="+JSESSIONID);
            }
            OUT.println("Connection: close");
            OUT.println("");

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // we have a URL problem
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while

             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // it's over
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // the result of the model comparison
        Matcher résultat=null;
         // simulations
        ArrayList listeSimulations=new ArrayList();

         // read all the lines to the end
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // follow-up
             // the line is compared with the error model if the simulation part has not yet been encountered
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // error msg
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // it's over
                    return listeSimulations;
                }//if
            }//if
             // the line is compared with the simulation model
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // we found a row in the
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // the simulation was a success
                simulationRéussie=true;
            }//if
        }//while
        // end
        return listeSimulations;
    }

Vamos explicar um pouco este código:

  • O procedimento mnuCalculer_actionPerformed verifica se os dados da interface são válidos. Se não forem, é exibida uma mensagem de erro e o procedimento é encerrado. Se forem válidos, o procedimento calculerImpots é executado.
  • O procedimento calculerImpots começa por construir o URL necessário para efetuar o pedido
        // on retire d'urlImpots les infos nécessaire à la connexion au serveur d'impôts
        String path=urlImpots.getPath();
        if(path.equals("")) path="/";
        String query="?"+"optMarie="+(marié ? "oui":"non")+"&txtEnfants="+nbEnfants+"&txtSalaire="+salaire;
        String host=urlImpots.getHost();
        int port=urlImpots.getPort();
        if(port==-1) port=urlImpots.getDefaultPort();
........................
  • depois liga-se a este URL enviando os cabeçalhos HTTP apropriados:
             // connect to the server
            client=new Socket(host,port);

            // create customer input/output flows TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // request URL - send HTTP headers
            OUT.println("GET " + path + query + " HTTP/1.1");
            OUT.println("Host: " + host + ":" + port);
            if(! JSESSIONID.equals("")){
                OUT.println("Cookie: JSESSIONID="+JSESSIONID);
            }
            OUT.println("Connection: close");
            OUT.println("");
  • Assim que o pedido é feito, o nosso cliente web aguarda a resposta. Primeiro, recebe os cabeçalhos HTTP da resposta do servidor. Analisa esses cabeçalhos para encontrar o token de sessão. De facto, tem de enviar este token de volta ao servidor para que este possa acompanhar as várias simulações realizadas. Note-se aqui que teria sido mais simples para o cliente armazenar as várias simulações realizadas pelo próprio utilizador. No entanto, mantemos a ideia de reenviar o token para fornecer um novo exemplo de gestão de sessão. A primeira linha da resposta é tratada separadamente. Deve estar no formato HTTP/versão 200 OK para indicar que o URL solicitado existe de facto. Se não estiver neste formato, concluímos que o utilizador solicitou um URL incorreto e informamo-lo em conformidade.
         // the model searched in HTTP headers
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // the model of a correct answer
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // read the 1st line of the answer
            réponse=IN.readLine();
             // compare the HTTP line with the model of the correct answer
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // we have a URL problem
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//if(result)

             // we read the response through to the end of the headers, looking for any cookies
            while((réponse=IN.readLine())!=null){
                 // empty line?
                if(réponse.equals("")) break;
                // line HTTP not empty
                 // if you don't have the session token, look for it
                if (JSESSIONID.equals("")){
                    // compare the HTTP line with the cookie template
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // we found the token cookie
                        JSESSIONID=résultat.group(1);
                    }//if(result)
                }//if(JSESSIONID)
            }//while
  • Depois de processados os cabeçalhos HTTP, passamos à parte HTML da resposta
             // that's it for HTTP headers - move on to HTML code
             // to retrieve simulations
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
  • O procedimento getSimulations devolve a lista de simulações, caso estas existam; esta lista estará vazia se o servidor devolver uma mensagem de erro. Neste caso, a mensagem de erro é apresentada numa caixa de mensagem. Se a lista não estiver vazia, é apresentada na lista suspensa da interface gráfica do utilizador.
  • O procedimento getSimulations compara cada linha da resposta HTML com a expressão regular que representa a mensagem de erro (Aplicação indisponível...) e com a expressão regular que representa uma simulação. Se a mensagem de erro for encontrada, esta é apresentada e o procedimento termina. Se for encontrado o resultado de uma simulação, este é adicionado à lista de simulações. No final do procedimento, esta lista é devolvida como resultado.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // the template for a line in the error list
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. Versão 5

Aqui, transformamos a aplicação gráfica autónoma anterior num applet Java. A interface gráfica é ligeiramente diferente. Com a aplicação autónoma, o utilizador fornecia ele próprio o URL do serviço de simulação de cálculo de impostos, e a aplicação ligava-se então a esse URL. Aqui, a aplicação cliente é um navegador, e o utilizador irá solicitar o URL do documento HTML que contém o applet. É importante lembrar que um applet Java só pode estabelecer uma ligação de rede com o servidor a partir do qual foi descarregado. A URL do serviço de simulação estará, portanto, no mesmo servidor que o documento HTML que contém o applet. No nosso exemplo, este será um parâmetro de inicialização do applet colocado no campo txtUrlServiceImpots, que não poderá ser editado pelo utilizador. Isto resulta no seguinte cliente:

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 possui um parâmetro chamado *urlServiceImpots, que é a URL do serviço de simulação de cálculo de impostos. Esta URL é relativa à URL do documento HTML simulations.htm. Assim, se um navegador recuperar este documento através da URL http://localhost:8080/impots/simulations.htm, a URL do serviço de simulação será http://localhost:8080/impots/simulations. Se esta URL fosse http://stahe:8080/impots/simulations.htm, a URL do serviço de simulação seria http://stahe:8080/impots/simulations*.

O applet appletImpots.java incorpora na íntegra o código da aplicação gráfica autónoma anterior, respeitando as regras para a conversão de uma aplicação gráfica num applet.

public class appletImpots extends JApplet {
  // window components
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //Building the frame
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // other initializations
    moreInit();
  }

   // form initialization
  private void moreInit(){
         // retrieve the urlServiceImpots parameter
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // missing parameter
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // end
            return;
        }
         // put the URL in its field
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // calculate menu disabled
    mnuCalculer.setEnabled(false);
     // spinner Children - between 0 and 20 children
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Initialize component
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
...............
  }

Quando um navegador carrega um applet, executa primeiro o seu método init. Neste método, recuperámos o valor do parâmetro urlServiceImpots do applet, calculámos o URL completo do serviço de simulação e colocámos este valor no campo txtURLServiceImpots, como se o utilizador o tivesse digitado. Uma vez feito isto, já não há qualquer diferença entre as duas aplicações. Em particular, o código associado ao menu «Calculate» é idêntico. Aqui está um exemplo de execução:

Image

5.7. Conclusão

Demonstrámos diferentes versões da nossa aplicação cliente-servidor de cálculo de impostos:

  • Versão 1: O serviço é fornecido por um conjunto de servlets e páginas JSP; o cliente é um navegador. Realiza uma única simulação e não mantém qualquer histórico das simulações anteriores.
  • Versão 2: Adicionamos algumas funcionalidades do lado do navegador, incorporando scripts JavaScript no documento HTML carregado pelo navegador. Este valida os parâmetros do formulário.
  • Versão 3: Permitimos que o serviço memorize as várias simulações realizadas por um cliente através da gestão de uma sessão. A interface HTML é modificada em conformidade para apresentar essas simulações.
  • Versão 4: O cliente é agora uma aplicação gráfica autónoma. Isto permite-nos revisitar o desenvolvimento de clientes web programados.
  • Versão 5: O cliente torna-se um applet Java. Temos agora uma aplicação cliente-servidor inteiramente programada em Java, tanto no lado do servidor como no lado do cliente.

Nesta altura, podem ser feitas algumas observações:

  • As versões 1 a 3 suportam navegadores sem outras capacidades além da capacidade de executar scripts JavaScript. Note-se que um utilizador tem sempre a opção de desativar a execução destes scripts. A aplicação funcionará então apenas parcialmente na versão 1 (a opção Limpar não funcionará) e de todo nas versões 2 e 3 (as opções Limpar e Calcular não funcionarão). Pode valer a pena desenvolver uma versão do serviço que não utilize scripts JavaScript.
  • A versão 4 requer que o computador do cliente tenha uma máquina virtual Java 2.
  • A versão 5 requer que o computador do cliente tenha um navegador com uma máquina virtual Java 2.

Ao desenvolver um serviço web, é necessário ter em conta os tipos de clientes a que se destina. Se pretender alcançar o maior número possível de clientes, deve criar uma aplicação que envie apenas HTML para os navegadores (sem JavaScript nem applets). Se estiver a trabalhar numa intranet e tiver controlo sobre a configuração das estações de trabalho, poderá então permitir-se ser mais exigente no que diz respeito ao lado do cliente, e a versão 5 anterior poderá, nesse caso, ser aceitável.

As versões 4 e 5 são clientes web que recuperam as informações de que necessitam a partir do fluxo HTML enviado pelo servidor. Muitas vezes, não temos controlo sobre este fluxo. É o que acontece quando criamos um cliente para um serviço web existente na rede que é gerido por outra entidade. Vejamos um exemplo. Suponhamos que o nosso serviço de simulação de cálculo de impostos tenha sido escrito pela Empresa X. Atualmente, o serviço envia as simulações numa tabela HTML, e o nosso cliente utiliza este facto para as recuperar. Assim, compara cada linha da resposta do servidor com a expressão regular:

         // the model of a line in the simulation table
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");

Suponha agora que o programador da aplicação altere a aparência visual da resposta, colocando as simulações não numa tabela, mas numa lista com o seguinte formato:

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 constante que os clientes web enfrentam em aplicações que não controlamos. O XML pode fornecer uma solução para este problema:

  • em vez de gerar HTML, o serviço de simulação irá gerar XML. No nosso exemplo, isto poderia ser
<simulations>
    <entetes marie="marié" enfants="enfants" salaire="salaire" impot="impôt"/>
  <simulation marie="oui" enfants="2" salaire="200000" impot="22504" />
  <simulation marie="non" enfants="2" salaire="200000" impot="33388" />
</simulations>
  • Uma folha de estilo poderia ser associada a esta resposta, instruindo os navegadores sobre a apresentação visual desta resposta XML
  • Os clientes web programados ignorariam esta folha de estilo e recuperariam a informação diretamente do fluxo XML da resposta

Se o criador do serviço desejar modificar a apresentação visual dos resultados fornecidos, modificará a folha de estilo em vez do XML. Graças à folha de estilo, os navegadores exibirão o novo formato visual e os clientes web programados não precisarão de ser modificados. Novas versões do nosso serviço de simulação poderão, portanto, ser criadas:

  • Versão 6: O serviço fornece uma resposta XML acompanhada de uma folha de estilo destinada aos navegadores
  • Versão 7: O cliente é uma aplicação gráfica autónoma que processa a resposta XML do servidor
  • Versão 8: O cliente é um applet Java que processa a resposta XML do servidor