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 folleto de Java del mismo autor. Recordemos el problema. Se trata de escribir una aplicación que permita calcular los impuestos 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 participaciones 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, tiene media parte más
  • se calcula su renta 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
13190
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
16754,5
151 250
0,50
231 47,5
172 040
0,55
30710
195 000
0,60
39312
0
0,65
49062

Cada línea tiene 3 campos. Para calcular el impuesto I, se busca la primera línea donde QF<=campo1. 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 denominada MySQL. MySQL es un archivo 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 dbimpots que contiene una única tabla llamada impots. El acceso a la base de datos se controla mediante un nombre de usuario y una 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 gestor de fuentes de datos ODBC de 32 bits

Image

  • se utiliza el botón [Add] para añadir 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 el 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 se darían cuenta de ello. Este sería el caso, en particular, de nuestra aplicación Java.
3
la base de datos MySQL que se va a utilizar. MySQL es un SGBD que gestiona bases de datos relacionales, que son conjuntos de tablas relacionadas entre sí mediante relaciones. Aquí se indica el nombre de la base de datos gestionada.
4
el nombre de un usuario con derechos de acceso a esta base
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 matrices:

// 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 comprueba que las 3 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+")");
    // Todo correcto
    this.limites=LIMITES;
    this.coeffR=COEFFR;
    this.coeffN=COEFFN;
  }//constructor

   // cálculo del impuesto
  public long calculer(boolean marié, int nbEnfants, int salaire){
     // cálculo del número de participaciones
    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 del 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
}//Clase

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:

// paquetages importés
import java.sql.*;
import java.util.*;

public class impotsJDBC extends impots{
  // Añadir un generador que permita crear
   // 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

     // 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();
    // requête 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 línea 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 creada 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 formas. 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), la llamada al método calculer de la clase impotsJDBC nos da el impuesto a pagar.

5.2. Version 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(){
           // borrado del 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 se limita a mostrar 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 radio «sí» y «no» pueden tener los valores «checked» o «""» para activar o desactivar el botón de radio correspondiente
txtEnfants
el número de hijos del contribuyente
txtSalaire
su salario anual
txtImpots
el importe del impuesto a pagar
erreurs
una posible lista de errores si errores!=null

La página enviada al cliente contiene un script javascript que incluye 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 devuelve 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 al 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 al 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 ok?
        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:
    • recoge sus parámetros de inicialización. Estos le permiten conectarse a la base de datos ODBC que contiene los datos de los diferentes 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 gestionan los posibles errores y el mensaje de error se coloca en la variable msgErreur.

  • El método doGET
    • comprueba en primer lugar 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 la verificación de la validez del parámetro optMarie es innecesaria. Este es el valor de un botón de radio y aquí solo puede tener uno de los valores «oui» y «non». Pero esto olvida 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 agujeros de seguridad en la aplicación y, de hecho, es frecuente encontrarlos incluso en aplicaciones comerciales.
    • Se comprueba la validez de cada uno de los tres parámetros recuperados. Cada error encontrado se añade a una lista de errores (ArrayList errores). Si no hay errores, se calcula el importe 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 ha producido 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 web.xml de configuración de la aplicación impots 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 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. Esta es la página que se obtiene si, por ejemplo, el servidor MySQL no está en marcha y, por lo tanto, no se puede acceder a la base de datos mysql-dbimpots:

Image

La página que se obtiene en caso de introducción incorrecta es la siguiente:

Image

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

Image

5.3. Version 2

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

<%@ 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 correcto: 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 de tipo submit, sino de tipo button, asociado a una función denominada calculer. Es esta última 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. Version 3

Modificamos ligeramente la aplicación para introducir el concepto de sesión. Consideramos ahora que la aplicación es una aplicación de simulación del cálculo de impuestos. Un usuario puede entonces simular diferentes «configuraciones» de contribuyente y ver cuál sería el impuesto a pagar para cada una de ellas. 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 ha modificado y ahora se llama «simulations». Está configurado de la siguiente manera dentro de la aplicación «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>

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 http://localhost:8080/impots/simulations. Tiene 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 es el URL de 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:

  • el 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
  • el servlet de simulaciones calcula de la misma manera el valor txtImpots, guarda los parámetros (optMarie, txtEnfants, txtsalaire, txtImpots) en una lista denominada «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
...........

         // ¿Tenemos todos los parámetros esperados?
        if(optMarie==null || txtEnfants==null || txtSalaire==null){
........
             // se pasa el control a url para mostrar las simulaciones de cálculos de impuestos
            getServletContext().getRequestDispatcher(urlSimulationImpots).forward(request,response);
        }

         // tenemos todos los parámetros; los verificamos
...........

         // si hay errores, se pasan a los 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 añade 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 introducir en la sesión
                session.setAttribute("simulations",simulations);
            }catch(Exception ex){}
        }

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

         // se pasa el control a url para mostrar 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 ok?
.........................
    }
}

La página de visualización JSP de simulationsImpots.jsp ha quedado como se muestra a continuación (solo se ha conservado 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 errores?
      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. Version 4

Ahora vamos a crear una aplicación independiente que será un cliente web de la aplicación /impots/simulations anterior. 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
marcado 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 se compone de las siguientes opciones:

option
principal
option
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ú option permanece desactivada si alguno de los campos 1 o 5 está vacío
  • se detecta una sintaxis incorrecta en el campo 1

Image

  • se detecta un salario incorrecto

Image

  • se señala 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 se había iniciado)

Image

Image

Image

  • Si todo es correcto, se muestran las simulaciones

Image

Al escribir 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 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í vamos a utilizar el cliente web que ya hemos visto, que permite mostrar en pantalla la respuesta de un servidor a la solicitud de un URL. La URL solicitada será la de nuestro servicio de simulaciones de cálculos de impuestos http://localhost:8080/impots/simulations, a la que podremos pasar parámetros en el formato http://localhost:8080/impots/simulations?param1=vam1&param2=val2&...

Solicitemos el URL mientras que la base MySQL que permite crear un objeto de tipo impots no se ha 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 espera 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 hay que recuperar.

Ahora disponemos de las directrices sobre lo que hay que hacer cuando el usuario solicita el cálculo del impuesto desde la interfaz gráfica anterior:

  • comprobar que todos los datos de la interfaz son válidos y, en su caso, 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 estudiado
  • 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();
             // volver a la interfaz
            return;
        }
     // verificación del 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 campo erróneo
      txtSalaire.requestFocus();
       // volver 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){
             // aparece 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 Agencia Tributaria
         // casado: true si está casado, false en caso contrario
         // nbEnfants: número de hijos
         // salario: salario anual

         // se extrae de urlImpots la información necesaria para conectarse al servidor de Hacienda
        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 conecta al servidor
            client=new Socket(host,port);

            // se crean los flujos de entrada-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 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");
            }//if(resultado)

             // leemos la respuesta hasta el final de los encabezados buscando la 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);
                    }//if(resultado)
                }//if(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));
            }

            // se acabó
            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 línea 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 línea 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 encontrado la parte 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 línea 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 ha realizado con éxito
                simulationRéussie=true;
            }//if
        }//mientras
        // fin
        return listeSimulations;
    }

Explicaremos un poco este código:

  • el procedimiento mnuCalculer_actionPerformed comprueba 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 creando 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. En primer lugar, obtiene los encabezados HTTP de la respuesta del servidor. Explora estos encabezados para encontrar el token de la sesión. De hecho, deberá reenviarlo al servidor para que este pueda llevar un registro de las diferentes simulaciones realizadas. Cabe señalar aquí que habría sido más sencillo que el propio cliente memorizara las diferentes simulaciones realizadas por el usuario. No obstante, 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 trata por separado. De hecho, debe tener el formato HTTP/version 200 OK para indicar que el URL solicitado existe efectivamente. Por lo tanto, si no tiene este formato, se concluye que el usuario ha solicitado un URL incorrecto y se le informa de ello.
         // el patrón buscado en los encabezados HTTP
        Pattern modèleCookie=Pattern.compile("^Set-Cookie: JSESSIONID=(.*?);");
        // el modelo 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 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");
            }//if(resultado)

             // leemos la respuesta hasta el final de los encabezados buscando la 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);
                    }//if(resultado)
                }//if(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 las hay; 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 añade 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{

        // el modelo de una línea 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 línea de la lista de errores
        Pattern ptnErreur=Pattern.compile("(Application indisponible.*?)\\s*$");

5.6. Version 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 introducía é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. Hay que recordar ahora que un applet Java solo puede establecer una conexión de red con el servidor desde el que se ha descargado. 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 introducirá un parámetro de inicialización del applet en el campo txtUrlServiceImpots, campo que no podrá ser editado por el usuario. De este modo, 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 es el 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 autónoma anterior respetando las reglas de transformación de una aplicación gráfica en 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 introduce 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, hemos recuperado el valor del parámetro urlServiceImpots del applet, calculado el nombre completo del URL del servicio de simulación y colocado 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 de cálculo de impuestos:

  • version 1: el servicio lo proporciona un conjunto de servlets y páginas JSP; el cliente es un navegador. Realiza una única simulación y no guarda memoria de las anteriores.
  • version 2: se añaden algunas capacidades del lado del navegador mediante la inclusión de scripts javascript en el documento HTML cargado por este. Controla la validez de los parámetros del formulario.
  • version 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.
  • version 4: el cliente es ahora una aplicación gráfica autónoma. Esto nos permite volver a la escritura de clients web programados.
  • version 5: el cliente se convierte en un applet Java. De este modo, se obtiene una aplicación cliente-servidor totalmente programada en Java, tanto en el lado del servidor como en el del cliente.

Llegados a este punto, cabe hacer algunas observaciones:

  • las versiones 1 a 3 admiten navegadores sin otra capacidad que la de poder ejecutar scripts javascript. Cabe señalar que el usuario siempre tiene la posibilidad de desactivar la ejecución de estos. 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 prever una versión del servicio que no utilice scripts.
  • La version 4 requiere que el equipo cliente disponga de una máquina virtual Java 2.
  • La version 5 requiere que el equipo cliente disponga de un navegador con una máquina virtual Java 2.

Al escribir un servicio web, hay que preguntarse a qué tipos de clients nos dirigimos. Si se quiere llegar al mayor número posible de clients, se escribirá una aplicación que solo envíe a los navegadores HTML (sin javascript ni applets). Si trabajamos en una intranet y controlamos la configuración de los equipos de la misma, podemos permitirnos ser más exigentes en cuanto al cliente, y el version 5 anterior puede ser entonces aceptable.

Las versiones 4 y 5 son clients web que obtienen la información que necesitan del flujo HTML enviado por el servidor. Muy a menudo no se controla este flujo. Es el caso cuando se ha escrito un cliente para un servicio web existente en la red y gestionado por otra persona. Veamos un ejemplo. Supongamos que nuestro servicio de simulaciones de cálculo de impuestos ha sido escrito por una empresa X. Actualmente, el servicio envía las simulaciones en una tabla HTML y nuestro cliente aprovecha este hecho para recuperarlas. Así, compara cada línea de la respuesta del servidor con la expresión regular:

         // la plantilla de una línea 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 formato:

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

En este caso, nuestro cliente web deberá reescribirse. Esta es la amenaza permanente que se cierne sobre las aplicaciones web clients que no controlamos nosotros mismos. XML puede aportar una solución a este problema:

  • en lugar de generar HTML, el servicio de simulaciones generará XML. En nuestro ejemplo, 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 que indicara a los navegadores el aspecto visual que debe tener esta respuesta XML
  • los sitios web programados ignorarían esta hoja de estilo y recuperarían la información directamente del flujo de la respuesta

Si el diseñador 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 la nueva forma visual y no será necesario modificar los clients web programados. Por lo tanto, se podrían escribir nuevas version de nuestro servicio de simulaciones:

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