Skip to content

5. La aplicación IMPOTS

5.1. Introducción

Retomamos aquí la aplicación IMPOTS, que se utiliza en numerosas ocasiones en el manual de Java del mismo autor. Recordemos el problema. Se trata de escribir una aplicación que permita calcular el impuesto de un contribuyente. Nos situamos en el caso simplificado de un contribuyente que solo tiene que declarar su salario:

  • se calcula el número de partes del empleado nbParts = nbEnfants/2 + 1 si no está casado, nbEnfants/2 + 2 si está casado, donde nbEnfants es su número de hijos.
  • si tiene al menos tres hijos, cuenta con media parte adicional
  • se calcula su ingreso imponible R = 0,72 * S, donde S es su salario anual
  • se calcula su coeficiente familiar QF = R / nbParts
  • Se calcula su impuesto I. Consideremos la siguiente tabla:
12620.0
0
0
13 190
0,05
631
15640
0,1
1290,5
24 740
0,15
2072,5
31 810
0,2
3309,5
39 970
0,25
4900
48 360
0,3
6898,5
55 790
0,35
9316,5
92 970
0,4
12106
127 860
0,45
16 754,5
151 250
0,50
23147,5
172040
0,55
30710
195 000
0,60
39312
0
0,65
49 062

Cada línea tiene 3 campos. Para calcular el impuesto I, se busca la primera línea en la que QF sea menor o igual que el campo 1. Por ejemplo, si QF = 23000, se encontrará la línea

    24740        0.15        2072.5

El impuesto I es entonces igual a 0,15*R - 2072,5*nbParts. Si QF es tal que la relación QF <= campo1 nunca se cumple, entonces se utilizan los coeficientes de la última línea. En este caso:

    0                0.65        49062

lo que da como resultado el impuesto I = 0,65 * R - 49062 * nbParts.

Los datos que definen los distintos tramos impositivos se encuentran en una base de datos ODBC-MySQL. MySQL es un SGBD de dominio público que se puede utilizar en diferentes plataformas, entre ellas Windows y Linux. Con este SGBD, se ha creado una base de datos llamada dbimpots que contiene una única tabla denominada impots. El acceso a la base de datos se controla mediante un nombre de usuario y contraseña, en este caso admimpots/mdpimpots. La siguiente captura de pantalla muestra cómo utilizar la base de datos dbimpots con 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

La base de datos dbimpots se transforma en la fuente de datos ODBC de la siguiente manera:

  • se inicia el administrador de fuentes de datos ODBC de 32 bits

Image

  • se utiliza el botón [Add] para agregar una nueva fuente de datos ODBC

Image

  • se selecciona el controlador MySQL y se ejecuta [Terminer]

54321

Image

  • El controlador MySQL solicita cierta información:
1
el nombre DSN que se le dará a la fuente de datos ODBC —puede ser cualquiera—
2
la máquina en la que se ejecuta SGBD MySQL —en este caso, localhost—. Cabe destacar que la base de datos podría ser una base de datos remota. Las aplicaciones locales que utilizan la fuente de datos ODBC no notarían la diferencia. Este sería el caso, en particular, de nuestra aplicación Java.
3
la base de datos MySQL que se utilizará. MySQL es un SGBD que administra bases de datos relacionales, las cuales son conjuntos de tablas relacionadas entre sí mediante relaciones. Aquí se indica el nombre de la base de datos administrada.
4
el nombre de un usuario con derecho de acceso a esta base de datos
5
su contraseña

Se han definido dos clases para calcular el impuesto: impots y impotsJDBC. Se crea una instancia de la clase impots con los datos de los tramos impositivos pasados como parámetros en tablas:

// creación de una clase de impuestos

public class impots{

  // los datos necesarios para el cálculo del impuesto
   // provienen de una fuente externa

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

   // constructor vacío
  protected impots(){}

   // constructor
  public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
     // se verifica que las tres tablas tengan el mismo tamaño
    boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
    if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
      LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
    // está bien
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//generador

   // cálculo del impuesto
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // cálculo del número de cuotas
    double nbParts;
    if (marié) nbParts=(double)nbEnfants/2+2;
    else nbParts=(double)nbEnfants/2+1;
    if (nbEnfants>=3) nbParts+=0.5;
         // cálculo de la renta imponible y el coeficiente familiar
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // cálculo del impuesto
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // resultado
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calcular
}//categoría

La clase impotsJDBC deriva de la clase impots anterior. Se crea una instancia de la clase impotsJDBC con los datos de los tramos impositivos almacenados en una base de datos. La información necesaria para acceder a esta base de datos se pasa como parámetros al constructor:

// paquetes importados
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // agregar un constructor que permita construir
   // las tablas de límites, coeffr y coeffn a partir de la tabla
   // impuestos de una base de datos
  public impotsJDBC(String dsnIMPOTS, String userIMPOTS, String mdpIMPOTS)
      throws SQLException,ClassNotFoundException{

    // dsnIMPOTS: nombre DSN de la base de datos
     // userIMPOTS, mdpIMPOTS: nombre de usuario/contraseña de acceso a la base de datos

     // las tablas de datos
    ArrayList aLimites=new ArrayList();
    ArrayList aCoeffR=new ArrayList();
    ArrayList aCoeffN=new ArrayList();

    // conexión a la base de datos
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Connection connect=DriverManager.getConnection("jdbc:odbc:"+dsnIMPOTS,userIMPOTS,mdpIMPOTS);
    // creación de un objeto Statement
    Statement S=connect.createStatement();
    // consulta SELECT
    String select="select limites, coeffr, coeffn from impots";
    // ejecución de la consulta
    ResultSet RS=S.executeQuery(select);
    while(RS.next()){
      // procesamiento de la fila actual
      aLimites.add(RS.getString("limites"));
      aCoeffR.add(RS.getString("coeffr"));
      aCoeffN.add(RS.getString("coeffn"));
    }// línea siguiente
     // cierre de recursos
    RS.close();
    S.close();
    connect.close();
     // transferencia de datos a tablas 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
  }//constructor
}//clase

Una vez que se ha creado una instancia de la clase impotsJDBC, se puede llamar repetidamente a su método calculer para calcular el impuesto:

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

La obtención de los tres datos necesarios puede realizarse de múltiples maneras. La ventaja de la clase impotsJDBC es que solo hay que ocuparse de dicha obtención. Una vez obtenidos los tres datos (estado civil, número de hijos, salario anual), al llamar al método calculer de la clase impotsJDBC se obtiene el impuesto a pagar.

5.2. Versión 1

Nos situamos en el contexto de una aplicación web que presentaría una interfaz HTML a un usuario con el fin de obtener los tres parámetros necesarios para el cálculo del impuesto:

  • el estado civil (casado o soltero)
  • el número de hijos
  • el salario anual

Image

La visualización del formulario se realiza mediante la siguiente página JSP:

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

<%
     // se recuperan los atributos pasados por el 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(){
           // borra el formulario
        with(document.frmImpots){
            optMarie[0].checked=false;
          optMarie[1].checked=true;
          txtEnfants.value="";
          txtSalaire.value="";
          txtImpots.value="";
        }//con
      }//borrar
        </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>
    <%
         // ¿hay errores?
      if(erreurs!=null){
           // visualización de errores
        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>

La página JSP simplemente muestra la información que le envía el servlet principal de la aplicación:

     // se recuperan los atributos pasados por el 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
Los atributos de los botones de opción «sí» y «no» pueden tener los valores "checked" o "" para activar o desactivar el botón de opción correspondiente
txtEnfants
el número de hijos del contribuyente
txtSalaire
su salario anual
txtImpots
el monto del impuesto a pagar
erreurs
una posible lista de errores si errores!=null

La página enviada al cliente contiene un script de JavaScript con una función effacer asociada al botón «Effacer», cuya función es restablecer el formulario a su estado inicial: botón sin marcar, campos de entrada vacíos. Cabe señalar que este resultado no se podría haber obtenido con un botón HTML de tipo «reset». De hecho, cuando se utiliza este tipo de botón, el navegador restablece el formulario al estado en el que lo recibió. Sin embargo, en nuestra aplicación, el navegador recibe formularios que pueden no estar vacíos.

El servlet principal de la aplicación se llama main.java y su código es el siguiente:

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

public class main extends HttpServlet{

    // variables de instancia
    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{

        // ¿Se ha realizado correctamente la inicialización?
        if(msgErreur!=null){
             // se pasa el control a la página de error
            request.setAttribute("msgErreur",msgErreur);
            getServletContext().getRequestDispatcher(urlErreur).forward(request,response);
        }

         // atributos de la solicitud
        String chkoui=null;
        String chknon=null;
        String txtImpots=null;

        // se recuperan los parámetros de la solicitud
        String optMarie=request.getParameter("optMarie");              // estado civil
        String txtEnfants=request.getParameter("txtEnfants");       // número de hijos
        if(txtEnfants==null) txtEnfants="";
        String txtSalaire=request.getParameter("txtSalaire");       // salario anual
        if(txtSalaire==null) txtSalaire="";

         // ¿Se tienen todos los parámetros esperados?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
             // faltan parámetros
            request.setAttribute("chkoui","");
            request.setAttribute("chknon","checked");
            request.setAttribute("txtEnfants","");
            request.setAttribute("txtSalaire","");
            request.setAttribute("txtImpots","");
             // se pasa el control a la URL de visualización del impuesto
            getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
        }

         // tenemos todos los parámetros; los verificamos
        ArrayList erreurs=new ArrayList();
         // estado civil
        if( ! optMarie.equals("oui") && ! optMarie.equals("non")){
             // error
            erreurs.add("Etat marital incorrect");
            optMarie="non";
        }
         // número de hijos
        txtEnfants=txtEnfants.trim();
        if(! Pattern.matches("^\\d+$",txtEnfants)){
            // error
            erreurs.add("Nombre d'enfants incorrect");
        }
         // salario
        txtSalaire=txtSalaire.trim();
        if(! Pattern.matches("^\\d+$",txtSalaire)){
            // error
            erreurs.add("Salaire incorrect");
        }

         // si hay errores, se pasan como atributos de la consulta
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
            txtImpots="";
        }else{
             // se puede calcular el impuesto a pagar
            try{
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
            }catch(Exception ex){}
        }

         // los demás atributos de la 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);

         // se pasa el control a la URL de visualización del impuesto
        getServletContext().getRequestDispatcher(urlAffichageImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // se recuperan los parámetros de inicialización
        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 correctos?
        if(urlAffichageImpots==null || DSNimpots==null || admimpots==null || mdpimpots==null){
            msgErreur="Configuration incorrecte";
            return;
        }

         // se crea una instancia de impotsJDBC
        try{
            impots=new impotsJDBC(DSNimpots,admimpots,mdpimpots);
        }catch(Exception ex){
            msgErreur=ex.getMessage();
        }
    }
}
  • El método init del servlet hace dos cosas:
    • recupera sus parámetros de inicialización. Estos le permiten conectarse a la base de datos ODBC, que contiene los datos de los distintos tramos de impuestos (DSNimpots, admimpots, mdpimpots) y los URL de las páginas asociadas a la aplicación: urlAffichageImpots para el formulario, urlErreur para la página de error.
    • crea una instancia de la clase impotsJDBC

En ambos casos, se manejan los posibles errores y el mensaje de error se almacena en la variable msgErreur.

  • El método doGET
    • verifica primero que el servlet se haya inicializado correctamente. Si no es así, se muestra la página de error
    • recoge los parámetros esperados del formulario de impuestos: optMarie, txtEnfants, txtSalaire. Si falta alguno de ellos (==null), se envía un formulario de impuestos vacío. Se podría pensar que verificar la validez del parámetro optMarie es innecesario. Este es el valor de un botón de radio y aquí solo puede tener uno de los valores «oui» o «non». Pero esto pasa por alto que nada impide que un programa consulte directamente al servlet enviándole los parámetros que desee. Nunca se puede estar seguro de estar realmente conectado a un navegador. Olvidar este punto puede dar lugar a brechas de seguridad en la aplicación y, de hecho, es frecuente encontrarlas incluso en aplicaciones comerciales.
    • Se verifica la validez de cada uno de los tres parámetros recuperados. Cada error detectado se agrega a una lista de errores (ArrayList errores). Si no hay errores, se calcula el monto del impuesto; de lo contrario, no se calcula.
    • La información necesaria para mostrar la página se incluye en los atributos de la solicitud y, a continuación, se muestra el formulario de impuestos

La página de error JSP es la siguiente:

<%
     // jspService
   // Se produjo un error
  String msgErreur= (String)request.getAttribute("msgErreur");
  if(msgErreur==null) msgErreur="Erreur non identifiée";
%>
<!-- Inicio de la página HTML -->
<html>
  <head>
      <title>impots</title>
  </head>
  <body>
      <h3>calcul d'impots</h3>
      <hr>
    Application indisponible(<%= msgErreur %>)
  </body>
</html>

La aplicación web se llama «impots» y está configurada en el archivo server.xml de Tomcat de la siguiente manera:

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

La carpeta de la aplicación contiene las siguientes carpetas y archivos:

Image

Image

Image

Image

El archivo de configuración de la aplicación impots, web.xml, es el siguiente:

<?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>

El servlet principal se llama main y tiene el alias /main. Por lo tanto, se puede acceder a él a través de URL y http://localhost:8080/impots/main.

A continuación se muestran algunos ejemplos de aplicación:

Para inicializarse correctamente, el servlet debe tener acceso a la base de datos mysql-dbimpots. A continuación se muestra la página que se obtiene si, por ejemplo, el servidor MySQL no está en ejecución y, por lo tanto, no se puede acceder a la base de datos mysql-dbimpots:

Image

La página que aparece en caso de una entrada incorrecta es la siguiente:

Image

Si los datos ingresados son correctos, se calcula el impuesto:

Image

5.3. Versión 2

En el ejemplo anterior, el servidor verifica la validez de los parámetros txtEnfants y txtSalaire del formulario. Aquí se propone verificarla mediante un script de JavaScript incluido en la página del formulario. De esta manera, es el navegador el que verifica los parámetros. El servidor solo se consulta si estos son válidos. Así se ahorra «ancho de banda». La página de visualización JSP queda así:

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

<html>

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

      function calculer(){
           // verificación de los parámetros antes de enviarlos al servidor
        with(document.frmImpots){
          //número de hijos
          champs=/^\s*(\d+)\s*$/.exec(txtEnfants.value);
          if(champs==null){
            // el modelo no se ha verificado
            alert("Le nombre d'enfants n'a pas été donné ou est incorrect");
            nbEnfants.focus();
            return;
          }//si
          //salario
          champs=/^\s*(\d+)\s*$/.exec(txtSalaire.value);
          if(champs==null){
            // el modelo no se ha verificado
            alert("Le salaire n'a pas été donné ou est incorrect");
            salaire.focus();
            return;
          }//si
          // Todo está bien: se envía el formulario al servidor
          submit();
        }//con
      }//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>

Cabe destacar los siguientes cambios:

  • el botón Calculer ya no es del tipo submit, sino del tipo button, asociado a una función llamada calculer. Es esta función la que analizará los campos txtEnfants y txTsalaire. Si son correctos, los valores del formulario se enviarán al servidor (submit); de lo contrario, se mostrará un mensaje de error.

A continuación se muestra un ejemplo de lo que se vería en caso de error:

Image

5.4. Versión 3

Modificamos ligeramente la aplicación para introducir el concepto de sesión. Ahora consideramos que la aplicación es una simulación de cálculo de impuestos. Un usuario puede simular diferentes «configuraciones» de contribuyente y ver cuál sería el impuesto a pagar en cada caso. La página WEb que se muestra a continuación ofrece un ejemplo de lo que se podría obtener:

Image

El servlet principal se modifica y ahora se llama «simulaciones». Se configura de la siguiente manera dentro de la aplicación «impuestos»:

<?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>

El servlet principal se llama «simulations» y se basa en el archivo de clase simulations.class. Tiene el alias /simulations, lo que permite acceder a ella a través de URL y http://localhost:8080/impots/simulations. Cuenta con los mismos parámetros de inicialización que el servlet principal analizado anteriormente en lo que respecta al acceso a la base de datos. Aparece un nuevo parámetro, urlSimulationsImpots, que corresponde a la página de simulación JSP (la que se acaba de presentar un poco más arriba).

El servlet simulations.java es similar al servlet main.java. Se diferencia de este en los siguientes puntos principales:

  • la servlet principal calcula un valor txtImpots a partir de los parámetros optmarie, txtEnfants y txtSalaire y pasa este valor a la página de visualización JSP
  • la servlet de simulaciones calcula de la misma manera el valor txtImpots, registra los parámetros (optMarie, txtEnfants, txtsalaire, txtImpots) en una lista llamada «simulaciones». Esta lista es la que se pasa como parámetro a la página de visualización JSP. Para que esta lista contenga todas las simulaciones realizadas por el usuario, se guarda como atributo de la sesión actual.

El servlet «simulaciones» es el siguiente (solo se han conservado las líneas de código que difieren de las de la aplicación anterior):

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

public class simulations extends HttpServlet{

    // variables de instancia
    String msgErreur=null;
    String urlSimulationImpots=null;
    String urlErreur=null;
...........

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

...........

         // se recuperan las simulaciones anteriores de la sesión
        HttpSession session=request.getSession();
        ArrayList simulations=(ArrayList)session.getAttribute("simulations");
        if(simulations==null) simulations=new ArrayList();
        // se incluyen las simulaciones en la consulta actual
        request.setAttribute("simulations",simulations);

         // otros atributos de la consulta
...........

         // ¿Se cuentan todos los parámetros esperados?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // se pasa el control a la URL de visualización de las simulaciones de cálculo de impuestos
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // se tienen todos los parámetros; se verifican
...........

         // si hay errores, se pasan como atributos de la consulta
        if(erreurs.size()!=0){
            request.setAttribute("erreurs",erreurs);
        }else{
            try{
                 // se puede calcular el impuesto a pagar
                int nbEnfants=Integer.parseInt(txtEnfants);
                int salaire=Integer.parseInt(txtSalaire);
                txtImpots=""+impots.calculer(optMarie.equals("oui"),nbEnfants,salaire);
                 // se agrega el resultado actual a las simulaciones anteriores
                String[] simulation={optMarie.equals("oui") ? "oui" : "non",txtEnfants, txtSalaire, txtImpots};
                simulations.add(simulation);
                 // el nuevo valor de las simulaciones se vuelve a guardar en la sesión
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

         // otros atributos de la consulta
..........

         // se pasa el control a la URL de visualización de las simulaciones
        getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
    }

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

     //-------- INIT
    public void init(){
         // se recuperan los parámetros de inicialización
        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 correctos?
.........................
    }
}

La página de visualización JSP de simulationsImpots.jsp quedó así (solo se conservó el código que difiere de la página de visualización JSP de la aplicación anterior).

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

<%
     // se recuperan los atributos pasados por el 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>
    <%
         // ¿Hay algún error?
      if(erreurs!=null){
..................
      }else if(simulations.size()!=0){
          // resultados de las simulaciones
        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. Versión 4

Ahora crearemos una aplicación independiente que servirá como cliente web de la aplicación anterior /impots/simulations. Esta aplicación tendrá la siguiente interfaz gráfica:

n.º
tipo
nombre
función
1
JTextField
txtUrlServiceImpots
URL del servicio de simulación del cálculo de impuestos
2
JRadioButton
rdOui
marcar si está casado
3
JRadioButton
rdNon
marcar si no está casado
4
JSpinner
spinEnfants
número de hijos del contribuyente (mínimo = 0, máximo = 20, incremento = 1)
5
JTextField
txtSalaire
salario anual en F del contribuyente
6
JList en JScrollPane
lstSimulations
Lista de simulaciones

El menú Impuestos cuenta con las siguientes opciones:

Opción
principal
Opción
secundaria
nombre
función
Impuestos
   
 
Calcular
mnuCalculer
calcula el impuesto a pagar cuando todos los datos necesarios para el cálculo están presentes y son correctos
 
Borrar
mnuEffacer
restablece el formulario a su estado inicial
 
Salir
mnuQuitter
cierra la aplicación

Reglas de funcionamiento

  • La opción de menú Calculer permanece desactivada si alguno de los campos 1 o 5 está vacío
  • se detecta una URL con sintaxis incorrecta en el campo 1

Image

  • se detecta un salario incorrecto

Image

  • se reporta cualquier error de conexión al servidor (en el ejemplo 1 a continuación, el puerto es incorrecto; en el ejemplo 2, el URL solicitado no existe; en el ejemplo 3, la base de datos MySQL no estaba en ejecución)

Image

Image

Image

  • Si todo está correcto, se muestran las simulaciones

Image

Al desarrollar un cliente web programado, es necesario saber exactamente qué envía el servidor en respuesta a las diferentes solicitudes posibles de un cliente. El servidor envía un conjunto de líneas HTML que incluyen información útil y otra que solo sirve para el diseño de la página HTML. Las expresiones regulares de Java pueden ayudarnos a encontrar la información útil en el flujo de líneas enviadas por el servidor. Para ello, necesitamos conocer el formato exacto de las diferentes respuestas del servidor. Aquí utilizaremos el cliente web que ya hemos visto, el cual permite mostrar en pantalla la respuesta de un servidor a la solicitud de un URL. El URL solicitado será el de nuestro servicio de simulaciones de cálculos de impuestos http://localhost:8080/impots/simulations, al cual podremos pasar parámetros en el formato http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

Solicitemos el URL mientras la base MySQL, que permite crear un objeto de tipo impots, no se haya iniciado:


Dos>java clientweb http://localhost:8080/impuestos/simulaciones 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 de la 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 recuperar el error, el cliente web deberá buscar en la respuesta del servidor web la línea que contenga el texto «Aplicación no disponible». Ahora iniciemos la base MySQL y solicitemos la misma URL pasándole los valores que espera (los acepta indistintamente en forma de GET o de POST —véase el código Java del 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(){
...................................................
      }//borrar

      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>

Aquí se encuentra el documento completo HTML enviado por el servidor. El resultado de las diferentes simulaciones se encuentra en la única tabla presente en este documento. La expresión regular que nos permite recuperar la información relevante del documento podría ser la siguiente:

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

donde las cuatro expresiones entre paréntesis representan los cuatro datos que se deben extraer.

Ahora contamos con las pautas sobre qué hacer cuando el usuario solicita el cálculo del impuesto desde la interfaz gráfica anterior:

  • verificar que todos los datos de la interfaz sean válidos y, si es necesario, señalar los errores.
  • conectarse al URL indicado en el campo 1. Para ello, seguiremos el modelo del cliente web genérico ya presentado y analizado
  • en el flujo de la respuesta del servidor, utilizar expresiones regulares para:
    • encontrar el mensaje de error, si lo hay
    • encontrar los resultados de las simulaciones si no hay ningún error

El código relacionado con el menú calculer es el siguiente:

  void mnuCalculer_actionPerformed(ActionEvent e) {
    // cálculo del impuesto
         // verificación URL servicio
        URL urlImpots=null;
        try{
            urlImpots=new URL(txtURLServiceImpots.getText().trim());
            String query=urlImpots.getQuery();
            if(query!=null) throw new Exception();
        }catch (Exception ex){
             // mensaje de error
            JOptionPane.showMessageDialog(this,"URL incorrecte. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
             // enfoque en campo erróneo
            txtURLServiceImpots.requestFocus();
             // regreso a la interfaz
            return;
        }
     // verificación de salario
    int salaire=0;
    try{
      salaire=Integer.parseInt(txtSalaire.getText().trim());
      if(salaire<0) throw new Exception();
    }catch (Exception ex){
       // mensaje de error
            JOptionPane.showMessageDialog(this,"Salaire incorrect. Recommencez","Erreur",JOptionPane.ERROR_MESSAGE);
       // foco en el campo con error
      txtSalaire.requestFocus();
       // regreso a la interfaz
      return;
    }
     // número de hijos
    Integer nbEnfants=(Integer)spinEnfants.getValue();

    try{
             // se calcula el impuesto
            calculerImpots(urlImpots,rdOui.isSelected(),nbEnfants.intValue(),salaire);
    }catch (Exception ex){
             // se muestra el error
            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 del impuesto
         // urlImpots: URL de la autoridad fiscal
         // casado: verdadero si está casado, falso en caso contrario
         // nbEnfants: número de hijos
         // salario: salario anual

         // se extrae de urlImpots la información necesaria para conectarse al servidor de impuestos
        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();

         // datos locales
        Socket  client=null;                        // el cliente
        BufferedReader IN=null;                    // el flujo de lectura del cliente
        PrintWriter OUT=null;                        // el flujo de escritura del cliente
        String réponse=null;                        // respuesta del servidor
         // el patrón buscado en los encabezados HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // el patrón de una respuesta correcta
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
         // el resultado de la comparación con el patrón
        Matcher résultat=null;

        try{
             // se establece la conexión con el servidor
            client=new Socket(host,port);

            // se crean los flujos de entrada y salida del cliente TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // se solicita el URL - envío de los encabezados 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 lee la primera línea de la respuesta
            réponse=IN.readLine();
             // se compara la línea HTTP con el patrón de la respuesta correcta
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // hay un problema con URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//si(resultado)

             // leemos la respuesta hasta el final de los encabezados buscando una posible cookie
            while((réponse=IN.readLine())!=null){
                 // ¿línea vacía?
                if(réponse.equals("")) break;
                // línea HTTP no vacía
                 // si no se tiene el token de la sesión, se busca
                if (JSESSIONID.equals("")){
                    // comparamos la línea HTTP con el patrón de la cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // se ha encontrado la cookie del token
                        JSESSIONID=résultat.group(1);
                    }//si(resultado)
                }//si(JSESSIONID)
            }//mientras

             // se acabaron los encabezados HTTP  pasamos al código HTML
             // para recuperar las simulaciones
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }

            // ya está
            client.close();
        }catch (Exception ex){
            throw new Exception(ex.getMessage());
        }
    }//calculerImpots

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

        // el modelo de una fila de la tabla de simulaciones
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // el modelo de una fila de la lista de errores
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");
         // el resultado de la comparación con el modelo
        Matcher résultat=null;
         // las simulaciones
        ArrayList listeSimulations=new ArrayList();

         // se leen todas las líneas hasta el final
        String ligne=null;
        boolean simulationRéussie=false;
        while((ligne=IN.readLine())!=null){
             // seguimiento
             // se compara la línea con el modelo de error si aún no se ha llegado a la sección de simulaciones
            if(! simulationRéussie){
                résultat=ptnErreur.matcher(ligne);
                if(résultat.find()){
                    // mensaje de error
                    JOptionPane.showMessageDialog(this,résultat.group(1),"Erreur",JOptionPane.ERROR_MESSAGE);
                    // Se ha terminado
                    return listeSimulations;
                }//si
            }//si
             // se compara la fila con el modelo de simulación
            résultat=ptnSimulation.matcher(ligne);
            if(résultat.find()){
                // se ha encontrado una fila de la tabla
                listeSimulations.add(résultat.group(1)+":"+résultat.group(2)+":"+résultat.group(3)+
                                                ":"+résultat.group(4));
                 // la simulación se realizó con éxito
                simulationRéussie=true;
            }//si
        }//mientras
        // fin
        return listeSimulations;
    }

Explicaremos un poco este código:

  • el procedimiento mnuCalculer_actionPerformed verifica que los datos de la interfaz sean válidos. Si no lo son, se emite un mensaje de error y el procedimiento finaliza. Si lo son, se ejecuta el procedimiento calculerImpots.
  • El procedimiento calculerImpots comienza por generar el URL que debe solicitar
         // se extrae de urlImpots la información necesaria para conectarse al servidor de impuestos
        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();
........................
  • y luego se conecta a este URL enviando los encabezados HTTP adecuados:
             // se conecta al servidor
            client=new Socket(host,port);

            // se crean los flujos de entrada y salida del cliente TCP
            IN=new BufferedReader(new InputStreamReader(client.getInputStream()));
            OUT=new PrintWriter(client.getOutputStream(),true);

            // se solicita el URL - envío de los encabezados 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("");
  • Una vez realizada la solicitud, nuestro cliente web espera la respuesta. Primero obtiene los encabezados HTTP de la respuesta del servidor. Analiza estos encabezados para encontrar el token de la sesión. De hecho, deberá reenviarlo al servidor para que este último pueda llevar un registro de las diferentes simulaciones realizadas. Cabe señalar aquí que hubiera sido más sencillo que el cliente memorizara por sí mismo las diferentes simulaciones realizadas por el usuario. Sin embargo, mantenemos la idea de reenviar el token para ofrecer un nuevo ejemplo de gestión de sesión. La primera línea de la respuesta se procesa por separado. De hecho, debe tener el formato HTTP/versión 200 OK para indicar que el URL solicitado existe. Por lo tanto, si no tiene este formato, se concluye que el usuario solicitó un URL incorrecto y se le informa de ello.
         // se busca el patrón en los encabezados HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // el patrón de una respuesta correcta
        Pattern réponseOK=Pattern.compile("^.*? 200 OK");
..........

             // se lee la primera línea de la respuesta
            réponse=IN.readLine();
             // se compara la línea HTTP con el modelo de la respuesta correcta
            résultat=réponseOK.matcher(réponse);
            if(! résultat.find()){
                // hay un problema con URL
                throw new Exception("Le serveur a répondu : URL ["+ txtURLServiceImpots.getText().trim() + "] inconnue");
            }//si(resultado)

             // leemos la respuesta hasta el final de los encabezados buscando una posible cookie
            while((réponse=IN.readLine())!=null){
                 // ¿línea vacía?
                if(réponse.equals("")) break;
                // línea HTTP no vacía
                 // si no se tiene el token de la sesión, se busca
                if (JSESSIONID.equals("")){
                    // comparamos la línea HTTP con el patrón de la cookie
                    résultat=modèleCookie.matcher(réponse);
                    if(résultat.find()){
                        // se ha encontrado la cookie del token
                        JSESSIONID=résultat.group(1);
                    }//si(resultado)
                }//si(JSESSIONID)
            }//mientras
  • Una vez procesados los encabezados HTTP, pasamos a la parte HTML de la respuesta
             // se acabaron los encabezados HTTP  pasamos al código HTML
             // para recuperar las simulaciones
            ArrayList listeSimulations=getSimulations(IN,OUT,simulations);
            simulations.clear();
            for (int i=0;i<listeSimulations.size();i++){
                simulations.addElement(listeSimulations.get(i));
            }
  • El procedimiento getSimulations devuelve la lista de simulaciones, si existen; dicha lista estará vacía si el servidor devuelve un mensaje de error. En ese caso, el mensaje se muestra en un cuadro de mensaje. Si la lista no está vacía, se muestra en la lista desplegable de la interfaz gráfica.
  • El procedimiento getSimulations comparará cada línea de la respuesta HTML con la expresión regular que representa el mensaje de error (Aplicación no disponible...) y con la expresión regular que representa una simulación. Si se encuentra el mensaje de error, se muestra y el procedimiento finaliza. Si se encuentra el resultado de una simulación, se agrega a la lista de simulaciones. Al final del procedimiento, esta lista se devuelve como resultado.
    private ArrayList getSimulations(BufferedReader IN, PrintWriter OUT, DefaultListModel simulations) throws Exception{

        //: la plantilla de una fila de la tabla de simulaciones
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");
        // el modelo de una fila de la lista de errores
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. Versión 5

Aquí transformamos la aplicación gráfica independiente anterior en un applet de Java. La interfaz gráfica es ligeramente diferente. Con la aplicación independiente, el usuario ingresaba él mismo el URL del servicio de simulación de cálculos de impuestos y, a continuación, la aplicación se conectaba a ese URL. Aquí, la aplicación cliente es un navegador y el usuario solicitará el URL del documento HTML que contiene el applet. Ahora hay que recordar que un applet de Java solo puede establecer una conexión de red con el servidor desde el que se descargó. Por lo tanto, el URL del servicio de simulación estará en el mismo servidor que el documento HTML que contiene el applet. En nuestro ejemplo, se colocará un parámetro de inicialización del applet en el campo txtUrlServiceImpots, campo que no podrá ser editado por el usuario. De esta manera, tendremos el siguiente cliente:

Image

El documento HTML que contiene el applet se llama simulations.htm y es el siguiente:

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

El applet tiene un parámetro urlServiceImpots, que corresponde al URL del servicio de simulaciones del cálculo de impuestos. Este URL es relativo y se mide en relación con el URL del documento HTML simulations.htm. Así, si un navegador obtiene este documento con el URL http://localhost:8080/impots/simulations.htm, el URL del servicio de simulación será http://localhost:8080/impots/simulations.. Si este URL hubiera sido http://stahe:8080/impots/simulations.htm, el URL del servicio de simulación habría sido http://stahe:8080/impots/simulations.

El applet appletImpots.java retoma íntegramente el código de la aplicación gráfica independiente anterior, respetando las reglas de transformación de una aplicación gráfica en un applet.

public class appletImpots extends JApplet {
  // los componentes de la ventana
  JPanel contentPane;
  JMenuBar jMenuBar1 = new JMenuBar();
  JMenu jMenu1 = new JMenu();
  JMenuItem mnuCalculer = new JMenuItem();
.............


   //Construir el marco
  public void init() {
    try {
      jbInit();
    }
    catch(Exception e) {
      e.printStackTrace();
    }
     // otras inicializaciones
    moreInit();
  }

   // inicialización del formulario
  private void moreInit(){
         // se recupera el parámetro urlServiceImpots
        String urlServiceImpots=getParameter("urlServiceImpots");
        if(urlServiceImpots==null){
            // parámetro faltante
            JOptionPane.showMessageDialog(this,"Le paramètre urlServiceImpots de l'applet n'a pas été défini","Erreur",JOptionPane.ERROR_MESSAGE);
             // fin
            return;
        }
         // se coloca el URL en su campo
        String codeBase=""+getCodeBase();
        if(codeBase.endsWith("/"))
           txtURLServiceImpots.setText(codeBase+urlServiceImpots);
      else txtURLServiceImpots.setText(codeBase+"/"+urlServiceImpots);
    // menú «Calcular» desactivado
    mnuCalculer.setEnabled(false);
     // selector «Niños»: entre 0 y 20 niños
    spinEnfants=new JSpinner(new SpinnerNumberModel(0,0,20,1));
    spinEnfants.setBounds(new Rectangle(130,140,50,27));
    contentPane.add(spinEnfants);
  }//moreInit

   //Inicializar el componente
  private void jbInit() throws Exception  {
    contentPane = (JPanel) this.getContentPane();
    contentPane.setLayout(null);
...............
  }

Cuando un navegador carga un applet, primero ejecuta su procedimiento init. En este, recuperamos el valor del parámetro urlServiceImpots del applet, calculamos el nombre completo del URL del servicio de simulación y colocamos este valor en el campo txtURLServiceImpots como si lo hubiera escrito el usuario. Una vez hecho esto, ya no hay diferencias entre las dos aplicaciones. En particular, el código asociado al menú Calculer es idéntico. A continuación se muestra un ejemplo de ejecución:

Image

5.7. Conclusión

Hemos mostrado diferentes versiones de nuestra aplicación cliente-servidor para el cálculo de impuestos:

  • versión 1: el servicio lo proporciona un conjunto de servlets y páginas JSP; el cliente es un navegador. Realiza una sola simulación y no guarda un historial de las anteriores.
  • versión 2: se agregan algunas capacidades del lado del navegador al incluir scripts de JavaScript en el documento HTML cargado por este. El navegador verifica la validez de los parámetros del formulario.
  • Versión 3: se permite que el servicio recuerde las diferentes simulaciones realizadas por un cliente mediante la gestión de una sesión. La interfaz HTML se modifica en consecuencia para mostrarlas.
  • Versión 4: el cliente es ahora una aplicación gráfica independiente. Esto nos permite volver a la programación de clientes web.
  • versión 5: el cliente se convierte en un applet de Java. De este modo, contamos con una aplicación cliente-servidor programada íntegramente en Java, tanto del lado del servidor como del lado del cliente.

En este punto, cabe señalar lo siguiente:

  • Las versiones 1 a 3 admiten navegadores que solo tienen la capacidad de ejecutar scripts de JavaScript. Cabe señalar que un usuario siempre tiene la posibilidad de desactivar la ejecución de estos scripts. En ese caso, la aplicación solo funcionará parcialmente en su versión 1 (la opción «Borrar» no funcionará) y no funcionará en absoluto en sus versiones 2 y 3 (las opciones «Borrar» y «Calcular» no funcionarán). Podría ser interesante desarrollar una versión del servicio que no utilice scripts de JavaScript.
  • La versión 4 requiere que la computadora del usuario cuente con una máquina virtual Java 2.
  • La versión 5 requiere que el equipo cliente cuente con un navegador que tenga una máquina virtual Java 2.

Al desarrollar un servicio web, hay que preguntarse a qué tipo de clientes nos dirigimos. Si queremos llegar al mayor número posible de clientes, escribiremos una aplicación que solo envíe a los navegadores HTML (sin JavaScript ni applets). Si se trabaja en una intranet y se tiene control sobre la configuración de las computadoras de la misma, entonces se puede ser más exigente con el cliente y la versión 5 anterior puede ser aceptable.

Las versiones 4 y 5 son clientes web que obtienen la información que necesitan del flujo HTML enviado por el servidor. Muy a menudo no tenemos control sobre este flujo. Este es el caso cuando hemos escrito un cliente para un servicio web existente en la red y administrado por otra persona. Veamos un ejemplo. Supongamos que nuestro servicio de simulaciones de cálculo de impuestos fue desarrollado por una empresa X. Actualmente, el servicio envía las simulaciones en una tabla HTML y nuestro cliente aprovecha esto para recuperarlas. De esta manera, compara cada línea de la respuesta del servidor con la expresión regular:

         // la plantilla de una fila de la tabla de simulaciones
        Pattern ptnSimulation=Pattern.compile("<tr>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*<td>(.*?)</td>\\s*</tr>");

Supongamos ahora que el desarrollador de la aplicación cambia el aspecto visual de la respuesta, colocando las simulaciones no en una tabla, sino en una lista con el siguiente formato:

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

En este caso, tendremos que reescribir nuestro cliente web. Esa es la amenaza constante que se cierne sobre los clientes web de aplicaciones que no controlamos nosotros mismos. XML puede ofrecer una solución a este problema:

  • en lugar de generar HTML, el servicio de simulaciones generará XML. En nuestro ejemplo, esto podría 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>
  • Se podría asociar una hoja de estilo a esta respuesta para indicar a los navegadores el aspecto visual que debe tener esta respuesta XML
  • Los clientes web programados ignorarían esta hoja de estilo y obtendrían la información directamente del flujo XML de la respuesta

Si el desarrollador del servicio desea modificar la presentación visual de los resultados que proporciona, modificará la hoja de estilo y no el XML. Gracias a la hoja de estilo, los navegadores mostrarán el nuevo diseño visual y no será necesario modificar los clientes web programados. Por lo tanto, se podrían desarrollar nuevas versiones de nuestro servicio de simulaciones:

  • versión 6: el servicio proporciona una respuesta XML acompañada de una hoja de estilo destinada a los navegadores
  • versión 7: el cliente es una aplicación gráfica independiente que utiliza la respuesta XML del servidor
  • versión 8: el cliente es un applet de Java que utiliza la respuesta XML del servidor