Proyecto de Integración
 

Aplicación web con servlets

Introducción

En esta sesión de integración incorporaremos la estructura de clases y los DAOs implementados en las sesiones anteriores, a una aplicación web dinámica que, mediante servlets que utilicen estas clases previas, aportarán todas las funcionalidades de los DAOs a la aplicación web.

Creación del proyecto web

Vamos a crear en primer lugar un nuevo proyecto con Maven, de nombre jbib-web, utilizando para ello el arquetipo webapp-jee5, para tenerlo configurado directamente con la versión de Java EE que vamos a utilizar:

mvn archetype:generate 
           -DgroupId=org.especialistajee.proyint 
           -DartifactId=jbib-web 
           -Dversion=1.0-SNAPSHOT 
           -Dpackage=org.especialistajee.jbib.web

Nos mostrará una lista con todos los posibles arquetipos, y deberemos introducir el número del que queramos utilizar. El arquetipo webapp-jee5 podremos encontrarlo en la posición 208:

208: remote -> webapp-jee5 (JEE 5 web application archetype)

Con esto tendremos configurada la API de Servlets 2.5, de JSP 2.1 y el compilador a nivel 1.5. Ahora pasaremos a preparar Eclipse para poder importar este proyecto. Iremos a Help > About Eclipse > Installation Details para comprobar si tenemos instalado la extensión Maven integration for WTP (optional) del plugin de Maven. Si no es así, procederemos a instalarla como se muestra a continuación:

  • Entramos en Help > Install new software ...
  • Deberemos añadir un nuevo site pulsando sobre el botón Add ...
  • Pondremos como nombre del nuevo site m2eclipse extras y como dirección http://m2eclipse.sonatype.org/sites/m2e-extras
  • Seleccionamos este site en el desplegable y marcamos el plugin Maven integration for WTP (optional)
  • Pulsamos el botón Next y seguimos las instrucciones del proceso de instalación

Ahora importaremos el proyecto de Maven en Eclipse, como Existing Maven Project, igual que hemos hecho en los proyectos anteriores.

Una vez importado el proyecto, configuraremos el plugin Cargo para automatizar el proceso de despliegue con Maven. Para ello deberemos añadir las siguientes líneas al pom.xml:

<plugin>
   <groupId>org.codehaus.cargo</groupId>
   <artifactId>cargo-maven2-plugin</artifactId>
   <configuration>
      <container>
         <containerId>tomcat6x</containerId>
         <home>/opt/apache-tomcat-6.0.29</home>
      </container>
      <configuration>
         <home>${project.build.directory}/tomcat6x</home>
      </configuration>
   </configuration>
</plugin>

Con esto podremos poner en marcha el servidor desde línea de comando mediante:

mvn install
mvn cargo:start

También podemos ejecutar nuestra aplicación directamente desde Eclipse como hemos hecho a lo largo del curso (Run As > Run on server). En este caso tendremos que configurar Tomcat en Eclipse, si no está configurado ya.

Si probamos a ejecutar la aplicación web de cualquiera de las formas anteriores veremos una página de prueba creada automáticamente por Maven, y que estará desplegada en la siguiente dirección:

http://localhost:8080/jbib-web/

Configuración de las dependencias

Añadiremos las siguientes dependencias al proyecto, a parte de las librerías de Servlets y JSPs ya incluidas por el arquetipo:

    <dependency>
      <groupId>httpunit</groupId>
      <artifactId>httpunit</artifactId>
      <version>1.7</version>
    </dependency>
    
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.8.1</version>
      <type>jar</type>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.especialistajee.proyint</groupId>
      <artifactId>jbib-persist-dao</artifactId>
      <version>1.0</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    
    <dependency>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
      <version>1.1.1</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.0.8</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
    
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.14</version>
      <type>jar</type>
      <scope>compile</scope>
    </dependency>
Advertencia
Hay que comprobar que la versión especificada en la dependencia con el proyecto jbib-persist-dao coincida con la versión que habíamos especificado en el POM de ese proyecto. Si no es así, obtendremos un error en las dependencias.

Cabe destacar que no es estrictamente necesario añadir dependencias al driver de MySql y a las librerías de logs, ya que al tener una dependencia con el proyecto jbib-persist-dao, y éste tener dependencias con dichas librerías, automáticamente las tendremos incluidas en el nuevo proyecto. Entonces, ¿conviene indicarlas de forma explícita o no? La respuesta a esta pregunta depende del caso:

  • Si nuestro proyecto no utiliza nunca directamente las librerías, sino que sólo son necesarias para ejecutar el código del proyecto del que dependemos, entonces no deberemos incluir las dependencias. Por ejemplo, se da este caso si en el proyecto web no escribimos logs con log4j, y sólo se escriben cuando ejecutamos métodos del DAO. Entonces la dependencia hacia log4j debería estar sólo en el proyecto jbib-persist-dao.
  • En caso de utilizar directamente esas librerías en el proyecto, si que deberíamos incluir la dependencia de nuevo. Si no la incluimos seguirá funcionando, ya que está incluida en un proyecto del que dependemos. Sin embargo, si en algún momento ese proyecto cambiase y dejase de utilizarlas, y por lo tando de incluirlas, nuestro proyecto fallará por la falta de dichas dependencias.

Dado que nuestra intención es utilizar log4j en nuestros componentes web, y que necesitamos el driver de MySql para que el servidor pueda realizar la autenticación mediante un realm JDBC, será conveniente volver a incluir estas dependencias de forma explícita en nuestro proyecto.

A parte de estas librerías, incluimos junit y httpunit para poder hacer pruebas de unidad de nuestros componentes web.

Sobre el driver de MySql deberemos tener en cuenta que no basta con incluirlo como dependencia del proyecto. Esto bastaría si sólo necesitásemos la base de datos para acceder a ella desde el código de nuestra aplicación (a través del DAO en nuestro caso). Sin embargo, dado que vamos a utilizar seguridad declarativa, el servidor Tomcat también deberá acceder a ella para autenticar a los usuarios. Por lo tanto, no bastará con que el driver esté disponible para nuestra aplicación, sino que deberá estar disponible para Tomcat. En Maven podemos hacer esto añadiendo la dependencia en la configuración del plugin Cargo:

      <plugin>
        <groupId>org.codehaus.cargo</groupId>
        <artifactId>cargo-maven2-plugin</artifactId>
        <configuration>
          <container>
            <containerId>tomcat6x</containerId>
            <home>/opt/apache-tomcat-6.0.29</home>
            <dependencies>
              <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
              </dependency>
            </dependencies>
          </container>
          <configuration>
            <home>${project.build.directory}/tomcat6x</home>
          </configuration>
        </configuration>
      </plugin>

Con esto será suficiente para que la aplicación funcione correctamente cuando arranquemos el servidor desde Maven. Sin embargo, cuando ejecutemos el proyecto desde Eclipse con Run on Server esta configuración no será tenida en cuenta, por lo que Tomcat no será capaz de encontrar el driver adecuado para MySql cuando intente autenticarnos.

Nota
Para que en Eclipse funcione correctamente la autenticación a través de un realm JDBC que utilice nuestra base de datos MySql, deberemos copiar el driver de MySql al directorio de librerías comunes de Tomcat (/opt/apache-tomcat-6.0.29/lib). Si ejecutamos el servidor desde Maven esto no será necesario, ya que en el fichero pom.xml se ha indicado que el servidor depende de dicha librería.

También se puede descargar el fichero pom.xml completo.

Configurar el pooling de conexiones

En las sesiones de integración anteriores obteníamos una Connection simple, a través de un DriverManager, en el método createConnection de la clase org.especialistajee.jbib.dao.FactoriaMySqlDaos. Ahora que vamos a pasar a aplicación web, vamos a mejorar el rendimiento, accediendo al pooling de conexiones que nos ofrece Tomcat. Para ello, introduciremos los siguientes cambios:

  • Creamos un fichero context.xml en la carpeta META-INF de nuestro proyecto web, donde definiremos las características del pooling:
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <Context>
      <Resource
        name="jdbc/biblioteca"
        type="javax.sql.DataSource"
        auth="Container"
        username="root"
        password="especialista"
        driverClassName="com.mysql.jdbc.Driver"
        url=
          "jdbc:mysql://localhost:3306/biblioteca?autoReconnect=true"/>
    
      <ResourceParams name="jdbc/biblioteca">
      
        <parameter>
          <name>maxActive</name>
          <value>20</value>
        </parameter>
        
        <parameter>
          <name>maxIdle</name>
          <value>5</value>
        </parameter>
    
        <parameter>
          <name>maxWait</name>
          <value>10000</value>
        </parameter>
      </ResourceParams>
    
    </Context>
  • Modificar el fichero web.xml de la aplicación para que referencie al recurso creado en el paso anterior. Para eso, añadimos estas líneas:
    <resource-ref>
      <res-ref-name>jdbc/biblioteca</res-ref-name>
      <res-type>javax.sql.DataSource</res-type>
      <res-auth>Container</res-auth>
    </resource-ref>
  • Finalmente, tendremos que hacer una modificación en el proyecto jbib-persist-dao. Deberemos terminar de implementar el método createConnection de la clase org.especialistajee.jbib.dao.FactoriaMySqlDaos. Recordemos que la obtención de las conexiones mediante JNDI había quedado sin implementar. Ahora podremos implementar esta parte haciendo que las obtenga del pool de conexiones que acabamos de configurar en Tomcat. Para ello, en lugar de lanzar una excepción, cuando se especifique como método para obtener conexiones JNDI utilizaremos el siguiente código:
        case JNDI:
          try {
            Context initCtx = new InitialContext();
            Context envCtx = (Context) initCtx.lookup("java:comp/env");
            DataSource ds = 
                (DataSource)envCtx.lookup("jdbc/biblioteca");
            conn = ds.getConnection();
          } catch (Exception sqle) {
            logger.fatal("No se ha podido crear la conexion", sqle);
          }
    Además, especificaremos esta opción como método por defecto para obtener las conexiones:
      static private TipoFuenteDatos tipoFuenteDatos = 
                                                TipoFuenteDatos.JNDI;

    El problema que surge es que ahora las pruebas de unidad definidas en el proyecto jbib-persist-dao dejarán de funcionar, ya que el pool de conexiones sólo estará disponible cuando el código se ejecute desde dentro de Tomcat. Para evitar estos fallos modificaremos las pruebas de forma que antes de ejecutarse cambien el tipo de fuente de datos a DRIVER. Para ello, en primer lugar debemos definir un método en FactoriaMySqlDaos que nos permita hacer esto:

      static public void setTipoFuenteDatos(TipoFuenteDatos tipo) {
        tipoFuenteDatos = tipo;
      }

    Una vez hecho esto, crearemos un método anotado con @BeforeClass en cada clase de junit (LibroDaoTest y UsuarioDaoTest) que establezca el tipo de fuente de datos a DRIVER:

      @BeforeClass
      public static void inizializaTests() {
        FactoriaMySqlDaos.setTipoFuenteDatos(TipoFuenteDatos.DRIVER);
      }

    Lo mismo podemos hacer al comienzo del método main de la clase PruebaDao, si queremos que pueda seguir funcionando de forma independiente.

    Una vez hechos estos cambios, instalaremos de nuevo este proyecto en el repositorio de Maven.

Montando la web

Acciones a realizar

De la sesión anterior, tenemos unos objetos DAO que realizan una serie de acciones sobre usuarios (IUsuarioDao y su implementación UsuarioMySqlDao), sobre libros (ILibroDao y su implementación LibroMySqlDao), sobre operaciones de usuarios con libros (IOperacionDao y su implementación OperacionMySqlDao), y sobre multas (IMultaDao y su implementación MultaMySqlDao). Estos objetos se apoyan para trabajar en sus respectivos objetos de dominio (domain) para ciertas operaciones, como por ejemplo, dar de alta un nuevo usuario, libro u operación, dado su objeto domain correspondiente (por ejemplo, pasamos un objeto LibroDomain para dar de alta un libro).

Lo que vamos a hacer en esta sesión es incorporar estas clases a la estructura de una aplicación web donde, mediante páginas HTML, indicaremos al servidor la operación que queremos realizar (por ejemplo, dar de alta un nuevo usuario, o listar todos los libros). Estas operaciones irán a parar al servlet encargado de atenderlas (veremos luego qué servlets implementar). Dicho servlet utilizará las clases auxiliares anteriores (los DAOs y los objetos de dominio) para realizar la operación, y luego mostrar un resultado (una página HTML generada por él mismo, con el resultado de la operación).

Es importante tener en cuenta que, para que todo esto funcione de la forma más modular posible, necesitamos tener las clases de manejo de DAOs de la sesión anterior, ya que serán las que nos proporcionen el "puente" para acceder desde los servlets a las operaciones que queramos realizar. Por ejemplo, para sacar un listado de todos los libros de la biblioteca, haríamos, desde el servlet, lo siguiente:

ILibroDao il = FactoriaDaos.getInstance().getLibroDao();
List<LibroDomain> lista = il.getLibros();
...// y luego recorrer la lista y mostrarla

Como puede verse, el esquema a seguir es que el servlet obtenga en un objeto el DAO (o DAOs) apropiados, y éstos le proporcionen los interfaces DAO que necesite (ILibroDao, en este caso), y con dicho interfaz, llamar al método o métodos necesarios para completar la operación.

En esta sesión vamos a centrarnos en mostrar un listado de libros, y sobre el mismo, poder realizar reservas de los libros que puedan reservarse.

Listado de libros

En esta sesión todo va a partir de una página principal que generará un servlet SeleccionarLibroServlet, con un listado de libros. Dicho servlet lo tenéis parcialmente implementado aquí. Copiadlo en el paquete correspondiente (org.especialistajee.jbib.web):

Listado básico de libros

Deberéis:

  • Mapear adecuadamente el servlet en el fichero web.xml.
  • Podríais, además de su mapeo, incluir temporalmente el servlet entre las páginas de bienvenida, para que se cargue como página principal "/":
    <welcome-file-list>
      <welcome-file>SeleccionarLibroServlet</welcome-file>
    </welcome-file-list>
  • Modificar el listado de libros para que, además del título, saque también el autor y el número de páginas, antes de la columna "Operaciones".
  • Obtener sólo los libros que estén disponibles para reserva (para hacer esto deberemos obtener el listado de libros a partir de IOperacionDao, en lugar de ILibroDao).
  • Implementar la reserva de libros como se comenta a continuación.

Reserva de libros

Para la reserva de libros, definiremos 2 servlets:

  • Un primer servlet PrepararReservaServlet, que será llamado desde los enlaces de "Reservar" del listado de libros anterior (mapear el servlet adecuadamente). Recogerá como parámetro el ISBN del libro, y mostrará una página con un formulario para introducir el login del usuario a nombre del cual se realizará la reserva. Además, en la página se mostrará el título del libro que estamos reservando. En caso de que no reciba el parámetro isbn, o que el ISBN proporcionado no esté en la base de datos, nos redirigirá a la página principal (listado de libros). Para realizar dicha redirección utilizaremos el método sendRedirect.

  • Un segundo servlet RealizarReservaServlet que será llamado desde el formulario del paso anterior, y tomará como parámetros el ISBN del libro y el login del usuario que reserva. Tras realizar la reserva, mostrará una página con el resultado de la operación, y un enlace para poder volver al listado de libros. Para realizar la reserva podemos copiar al servlet el método realizaReserva de la clase PruebaDao implementada anteriormente. Este método debería estar en un objeto de negocio, pero por el momento podemos ponerlo directamente dentro de nuestro serlvet.

Ayuda
Si aparecen fallos de compilación provenientes del directorio target, normalmente se solucionarán haciendo un mvn clean.

Configurar la seguridad

Como último paso, vamos a configurar seguridad declarativa en nuestra aplicación. Para eso seguiremos estos pasos:

  1. Vamos a utilizar la propia tabla usuario para validar quién entra. En principio dejaremos pasar a cualquiera que esté registrado en esa tabla, y en futuras sesiones de integración veremos cómo, dependiendo de su perfil (rol) le podemos dejar hacer unas u otras cosas.

    Para poder utilizar nuestra base de datos, debemos añadir un bloque Realm en el contexto de nuestra aplicación, que especifique que utilizaremos un JDBCRealm conectado a nuestra base de datos. Para ello, añadimos este bloque dentro de la etiqueta Context de nuestro fichero META-INF/context.xml:

    <Realm  className="org.apache.catalina.realm.JDBCRealm"
      driverName="com.mysql.jdbc.Driver"
      connectionURL=
          "jdbc:mysql://localhost:3306/biblioteca?autoReconnect=true"
      connectionName="root" connectionPassword="especialista"
      userTable="usuario" userNameCol="login" userCredCol="password"
      userRoleTable="usuario" roleNameCol="tipoUsuario" />
  2. En segundo lugar, debemos definir las páginas login.jsp y errorLogin.jsp en nuestra carpeta WebContent, con el formulario de validación y la página de error, como se vio en el módulo de Servidores Web.

    Sugerencia
    Es importante que ambas páginas tengan extensión .jsp, ya que de lo contrario Eclipse no las admitirá como páginas de login y error válidas. Además, en futuras sesiones será mejor tener páginas JSP a las que se pueda incorporar contenido dinámico.
  3. Finalmente, queda añadir las líneas de configuración de seguridad al fichero web.xml.

    El bloque security-constraint:

    <security-constraint>
      <web-resource-collection>
        <web-resource-name>...</web-resource-name>
        <url-pattern>...</url-pattern>
      </web-resource-collection>
      <auth-constraint>
        <role-name>...</role-name>
        <role-name>...</role-name>
        ...
      </auth-constraint>
    </security-constraint>

    Hemos dejado en blanco (...) el contenido de cada etiqueta para que pongáis lo que corresponde. Deberemos especificar por separado los 3 roles para los que damos acceso (ALUMNO, PROFESOR y BIBLIOTECARIO). Para ello podemos añadir varias etiquetas role-name dentro del auth-constraint, como puede verse en el código anterior

    Bajo el bloque anterior, colocamos el bloque login-config:

    <login-config>
      <auth-method>...</auth-method>
      ...
    </login-config>

Pruebas con httpunit

Vamos a utilizar httpunit para probar los componentes web desarrollados. Estas pruebas se harán directamente sobre la clase del servlet, sin necesidad de tener el servidor web en marcha. Introduciremos la clase PreparaReservaServletTest en el paquete org.especialistajee.jbib.web dentro de la carpeta src/test/java.

En la clase hay definidos tres casos de prueba:

  • Invocar el método doGet sin pasar el parámetro isbn. El resultado esperado es un código de redirección 302.
  • Invocar el método doGet pasando como parámetro isbn un ISBN que no está en la base de datos (1234567890). El resultado esperado es un código de redirección 302.
  • Invocar el método doGet pasando como parámetro isbn un ISBN válido (0321180860). El resultado esperado es un código de estado 200.

Hay que destacar que al ejecutar sin poner en marcha el servidor no tendremos disponible la fuente de datos JNDI. Por este motivo, antes de comenzar las pruebas se cambia el tipo de fuente de datos a DRIVER.

Ejecutaremos las pruebas con Maven y comprobaremos que nuestro servlet se comporta según lo especificado.

Resumen

Contenido del proyecto web

En esta sesión deberemos entregar los proyectos jbib-comun, jbib-persist-dao y jbib-web. Deberemos realizar las siguientes tareas:

  • Crear el proyecto Maven, importarlo en Eclipse (instalando las extensiones del plugin de Maven de Eclipse para WTP si es necesario), y configurar el fichero POM con Carbon y las dependencias necesarias (junit, httpunit, mysql, log4j, commons-logging y el proyecto jbib-persist-dao). El driver de MySql debe estar también como dependencia del servidor.
  • Configurar el pool de conexiones. Crear el fichero META-INF/context.xml. Modificar el proyecto jbib-persist-dao para que la clase FactoriaMySqlDaos obtenga conexiones mediante JNDI por defecto, y que los casos de prueba establezcan el tipo de fuente de datos a DRIVER.
  • Modificar el servlet SeleccionarLibroServlet e implementar PrepararReservaServlet y RealizarReservaServlet. Hacer que la página principal de la aplicación sea SeleccionarLibroServlet.
  • Configurar seguridad declarativa mediante base de datos y formularios (login.jsp y errorLogin.jsp).
  • Añadir la clase de pruebas PrepararReservaServletTest y comprobar que los tres tests se ejecutan con éxito.

El plazo final de entrega será el jueves 9 de diciembre. Se realizará una sesión on-line de dudas el martes 7 de 19:00 a 21:00.