Lo que no se sabe expresar, es que no se sabe.
Friedrich Engels

Struts 1, Fin del viaje

strutsAcabo de enterarme de que Struts 1 ha llegado al final de su camino, que arrancó en el año 2000, lo cual me entristece y me llena de morriña.

Para alguien que ha programado en BASIC, luego en Turbo Pascal, y luego con Java Servlets, Struts 1 supuso una gran revolución en cuanto a productividad y mantenimiento en la comunicación entre el servidor y el cliente, tanto por su implementación del patrón de diseño Modelo-Vista-Controlador como por sus frameworks de Validación y Tiles. De hecho la mayor revolución IMHO.

Desde aquí mi más profundo agradecimiento a cada persona que ha dedicado un minuto de su tiempo a este framework, así como el reconocimiento de mi deuda con ellos.

Gracias.

Spring, Struts 1.3.8, JDBC y MySQL

Vaya combinación, ya se va complicando el tema. Ahora que ya tenemos un servidor de base de datos MySQL disponible, queremos utilizarlo en nuestros proyectos web. Para ello partiremos del proyecto que ya hemos desarrollado integrando Spring con Struts 1.3.8, al que añadiremos una capa de persistencia utilizando JDBC (Java DataBase Connectivity).

Diseño

A la hora del diseño, vamos a desarrollar varias capas de persistencia y a la hora de desplegar nuestro proyecto utlizaremos la que más nos convenga.

  • Una primera capa de cartón piedra (es más profesional utilizar los términos mock o fake), es decir con los datos a fuego, que es como estaba en el ejemplo. Se utliza profesionalmente más de lo que parece porque resulta frecuente empezar el desarrollo sin disponer de una base de datos (por problemas de conectividad, licencias…) o desarrollando en paralelo con otro equipo que se encargue de la base de datos.
  • En este ejemplo utlizaremos una para MySQL con JDBC.
  • Más adelante utlizaremos para MySQL el framework iBATIS.
  • Y más adelante aún utlizaremos para MySQL el framework Hibernate.

Creamos los nuevos paquetes :

  • es > lycka > holamundo > persistencia > impl > cartonpiedra
  • es > lycka > holamundo > persistencia > impl > mysql > jdbc
  • es > lycka > holamundo > persistencia > impl > mysql > ibatis
  • es > lycka > holamundo > persistencia > impl > mysql > hibernate

Movemos las clases que ya teníamos (BaseDAOImpl y IdentificarDAOImpl) al paquete cartonpiedra. Creamos dos nuevas clases en el paquete jdbc, BaseDAOImpl y IdentificarDAOImpl (que extienda del nuevo BaseDAOImpl e implemente el interfaz IdentificarDAO). A la izquierda se ve la estructura que quedaría.

Conector MySQL

Descargamos el conector MySQL de la página de sun aquí, e importamos el jar (mysql-connector-java.jar) en nuestro proyecto.

Obtener una conexión

Creamos un nuevo método en nuestro nuevo BaseDAOImpl, establecerConexion.

protected Connection establecerConexion (String URL, String usuario, String contrasenna) throws Exception {

Connection conn = null;

try {

Class.forName(“com.mysql.jdbc.Driver”);

conn = DriverManager.getConnection (URL, usuario, contrasenna);

log.info(“establecerConexion – INFO – Conexión Establecida con\n -> URL = “ + URL + “\n -> USUARIO = “ + usuario);

} catch (Exception e) {

log.error(“establecerConexion – ERROR – Conexión no Establecida con “ + URL);

throw e;

}

return conn;

}

Cerrar una conexión

Creamos un nuevo método también en el nuevo BaseDAOImpl, cerrarConexión.

protected void cerrarConexión (Connection conn, String URL) {

try {

if (conn != null) {

conn.close();

log.info(“cerrarConexión – INFO – Conexión cerrada con “ + URL);

}

} catch (Exception e) {

log.error(“cerrarConexión – WARNING – No se puede cerrar la conexión con “ + URL);

e.printStackTrace();

}

}

Utilizar la conexión

En este ejemplo simple crearemos una conexión en cada petición, ejecutaremos la consulta y cerraremos la conexión. Profesionalmente se debería de utilizar un pool de conexiones, lo dejaré para otra entrada.

package es.lycka.holamundo.persistencia.impl.mysql.jdbc;

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import es.lycka.holamundo.model.Usuario;

import es.lycka.holamundo.persistencia.IdentificarDAO;

public class IdentificarDAOImpl extends BaseDAOImpl implements IdentificarDAO {

public Usuario consultarUsuario (Usuario usuario) throws Exception {

String URLBBDD = “jdbc:mysql://localhost/holamundo”;

String usuarioBBDD = “root”;

String contrasennaBBDD = “superfeo”;

Connection conn = null;

PreparedStatement ps = null;

ResultSet rs = null;

String query = “SELECT IDENTIFICADOR, CONTRASENNA “ +

“FROM USUARIO “ +

“WHERE IDENTIFICADOR = ? AND CONTRASENNA = ?”;

Usuario resultado = null;

try {

conn = establecerConexion (URLBBDD, usuarioBBDD, contrasennaBBDD);

ps = conn.prepareStatement(query);

ps.setString ( 1, usuario.getIdentificador());

ps.setString ( 2, usuario.getContrasenna());

rs = ps.executeQuery();

while ( rs.next() ) {

resultado = new Usuario ();

resultado.setIdentificador(rs.getString(“IDENTIFICADOR”));

resultado.setContrasenna(rs.getString(“CONTRASENNA”));

}

rs.close();

} catch (Exception e) {

throw e;

} finally {

cerrarConexión(conn, URLBBDD);

}

return resultado;

}

}

Acoplar la nueva implementación

Configuramos el fichero para inyectar los nuevos beans applicationContext-persistencia.xml:

<beans>

<bean id=“dao” class=“es.lycka.holamundo.persistencia.impl.mysql.jdbc.BaseDAOImpl” abstract=“true” />

<bean id=“identificarDAO” class=“es.lycka.holamundo.persistencia.impl.mysql.jdbc.IdentificarDAOImpl” parent=“dao”/>

</beans>

Para saber más

iReport sobre Struts – Paso 3 : Generar el informe desde Java

Ahora tenemos la plantilla (.jrxml) y la plantilla compilada (.jasper). La plantilla la podemos compilar también en tiempo de ejecución pero no es lo habitual.

Para entender mejor cómo funciona Jasper Reports puedes consultar la siguiente imagen (sacada de aquí):

 

Nosotros utilizaremos JasperRunManager.runReportToPdf para generar el informe en PDF desde el jasper que hemos generado. Colocamos el informe .jasper en nuestro directorio (yo lo he creado en un paquete llamado "informes"). Creamos una clase que nos genere el pdf es.lycka.holamundoStruts138Jasper.utilidades.GenerarPDF :

package es.lycka.holamundoStruts138Jasper.utilidades;

import java.util.HashMap;

import net.sf.jasperreports.engine.JREmptyDataSource;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperRunManager;
import net.sf.jasperreports.engine.util.JRLoader;
import es.lycka.holamundoStruts138Jasper.model.Informe;

public class GenerarPDF {

    public byte[] generarInforme (Informe informe) {
        byte[] pdf = null;
        String rutaInforme = "/es/lycka/holamundoStruts138Jasper/informes/informe.jasper";
        HashMap<String, Informe> parametros = new HashMap<String, Informe> ();
        parametros.put("informe", informe);

        try {
            JasperReport masterReport = (JasperReport) JRLoader.loadObject(getClass().getResource(rutaInforme));
            pdf = JasperRunManager.runReportToPdf(masterReport, parametros, new JREmptyDataSource());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return pdf;
    }
}

Ahora modificamos el InformeBO para invocar este método :

    public byte[] generarInforme (Informe informe) throws Exception {
        return (new GenerarPDF ()).generarInforme(informe);
    }

Ahora tenermos que hacer que nuestro action nos devuelva el pdf en la response. Para ello hacemos que el método execute nos devuelva null, y ahora implementamos el método que dejamos vacío, mostrarFichero

    private void mostrarPDF (byte[] informePDF, HttpServletResponse response) throws IOException {
        //Incializamos el array
        ByteArrayOutputStream bos    = new ByteArrayOutputStream();
        ServletOutputStream out        = response.getOutputStream();

        bos.write(informePDF);

        response.setContentType("application/pdf");
        //para que el pdf se pueda ver en microsoft explorer
        response.setHeader("Cache-Control", "cache");
        //para que aparezca el diálogo abrir/guardar
        response.setHeader("Content-Disposition", "attachment; filename=informe.pdf");
        response.setHeader("Pragma", "cache");
       
        response.setContentLength(bos.size());
        out.write(bos.toByteArray());
        out.flush();
        bos.close();
        out.close();
        response.flushBuffer();           
    }

Vale, haz la prueba y ya está. He subido el war de este proyecto a hordit, ahí puedes ver la estructura de los ficheros y las fuentes.

Joer ha costado ! Muchas gracias al Espín y a Chema que se curraron todo esto.

iReport sobre Struts – Paso 2 : Construyendo la plantilla

Bueno, pues evidentemente necesitamos descargarnos e instalarnos el software de iReport. Podemos bajarlo aquí. Yo utilizaré la versión 2.0.5 que podéis descargaros aquí.

La instalación es sencilla si os bajáis el ejecutable.

Abrimos el programa y aabrimos un nuevo documento. Nos abre con cuadro de diálogo en el que introducimos el nombre de la plantilla, reportePruebas. Veremos algo como lo siguiente:

 

Primero vemos que el informe tiene varias bandas (title, PageHeader…). Eliminamos las que no vayamos a utilizar dándoles una altura 0 (botón derecho sobre la banda > Propiedades de la banda). Ahora sólo utilizaremos la banda "Detail".

Ahora pondremos un título. Para poner en la plantilla texto estático seguimos la ruta Editar > Insertar elemento… > Texto estático. Centramos el campo y escribimos "INFORME METEOROLÓGICO". En las propiedades del elemento (panel de la derecha) centramos el texto, le damos un tamaño 18, en negrita, centrado y de tipo Helvética.

Ahora insertaremos debajo del título la imagen de la cabecera de este blog. Seguimos la ruta Editar > Insertar elemento… > Imagen.


Añadimos los datos que tienen que aparecer en el informe, primero sus etiquetas como texto estático ("CIUDAD", "TEMPERATURA MÍNIMA", "TEMPERATURA MÁXIMA"). También añadimos un rectángulo que contenga a los datos a modo de formulario, siguiendo Editar > Insertar elemento… > Rectángulo. Dibujamos el rectángulo y en las propiedades le hacemos transparente.

Finalmente añadimos los datos ! Para obtener los datos podemos realizar llamadas a base de datos desde el informe (yo lo desaconsejo totalmente) o pasar un objeto con los datos a mostrar, y en cada campo pintamos el dato que nos interese. Para ello Editar > Insertar elemento… > Campo de Texto.

A continuación le decimos el nombre del Bean y el método para obtener el campo. En nuestro caso pasaremos el bean "Informe" que ya hemos definido en el paso anterior y utilizaremos los métodos getCiudad(), getTemperaturaMinma() y getTemperaturaMaxima() respectivamente :

${informe}.getCiudad()

Ahora deberíamos de tener algo así en la pantalla:


No te engañes, no hemos acabado zZzZzZzZz. Ahora podemos compilar la plantilla (Construir > Compliar) y podemos hacer una previsualización del informe (Construir > Ejecutar informe). Verás que no se muestra exactamente como se ve en la plantilla, así que probablemente tendrás que retocar la plantilla.

Actualización

Ops, falta definir el parámetro "informe". Debemos añadir al classpath del informe nuestra clase "Informe". Vamos a Opciones > Localización de clases (classpath) y añadimos el classpath de nuestro proyecto.

Ahora vamos a Ver > Parámetros del Informe y añadimos un nuevo parámetro de nombre "informe" y nuestra clase como tipo de clase.

Reinicia el iReport y ahora debería de compilar (y generar el .jasper) y podríamos ver la previsualización.

iReport sobre Struts – Paso 1 : Preparando Struts

Sobre el código del ejemplo anterior añadiremos una nueva funcionalidad para permitir que los usuarios identificados en la aplicación puedan introducir unos datos y generar un informe en PDF (invocando un método de negocio de momento vacío).

Haremos un informe meteorológico que constará de tres campos introducidos por el usuario :

  • Ciudad sobre la que se realiza el informe
  • Temperatura mínima del día actual
  • Temperatura máxima del día actual

También añadiremos nosotros automáticamente la fecha del informe, que será la fecha de hoy.

Para ello necesitaremos implementar una nueva "rama" :

  • jsp/generarInforme.jsp – Una jsp de entrada de la que recoger el formulario introducido por el usuario.
  • es.lycka.holamundoStruts138Jasper.model.Informe – Un objeto de modelo que represente el informe.
  • es.lycka.holamundoStruts138Jasper.form.InformeForm – Un actionForm asociado
  • es.lycka.holamundoStruts138Jasper.action.InformeAction – Un action que atienda la petición
  • es.lycka.holamundoStruts138Jasper.negocio.InformeBO – Una clase de negocio que nos devuelva el informe en formato PDF
  • Configurar Struts para que todo esto funcione

Me habré dejado algo en el tintero ? Probemos.

Vista

En el jsp que ven los usuarios identificados (jsp/indexIdentificado.jsp) añadimos un enlace a nuestra nueva funcionalidad: 

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

      <title>Zona Identificada</title>

</head>

<body>

      <h2>Bienvenido a la zona de administración</h2>

      <p>Su identificador de usuario es : <bean:write scope="session" name="usuario" property="identificador" /></p>

      <p>Usted puede realizar las siguientes operaciones:</p>

      <ul>

            <li><html:link href="jsp/generarInforme.jsp">Generar Informe</html:link></li>

      </ul>

</body>

</html>

Se vería así :

 

 

 

Y ahora tenemos que implementar una nueva página JSP (jsp/generarInforme.jsp) que permita al usuario introducir unos datos y enviarlos al servidor para que genere el informe.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"

    pageEncoding="ISO-8859-1"%>

<%@ taglib uri="http://struts.apache.org/tags-html" prefix="html" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

      <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">

      <title>Generar Informe</title>

      <script>

      function enviar (){

            document.informeForm.submit();

      }

      </script>

</head>

<body>

<h2>Generación de Informe</h2>

<html:form action="generarInforme">

<div>

      <span style="text-aling:rigth">Ciudad</span>

      <span>&nbsp;&nbsp;&nbsp;<html:text property="informe.ciudad" /></span>

</div>

<div>

      <span style="text-aling:rigth">Temperatura Máxima Hoy</span>

      <span>&nbsp;&nbsp;&nbsp;<html:text property="informe.temperaturaMaxima" /></span>

</div>

<div>

      <span style="text-aling:rigth">Temperatura Mínima Hoy</span>

      <span>&nbsp;&nbsp;&nbsp;<html:text property="informe.temperaturaMinima" /></span>

</div>

<div>&nbsp;</div>

<div>

      <input type="button" value="Generar Informe" onclick="javascript:enviar();" />

</div>

</html:form>

</body>

</html>

El resultado sería algo parecido a :

 

 

 

Modelo

Necesitamos crear un bean que modele nuestro informe, es.lycka.holamundoStruts138Jasper.model.Informe :

package es.lycka.holamundoStruts138Jasper.model;

public class Informe {

      private String ciudad;

      private String temperaturaMaxima;

      private String temperaturaMinima;

      public String getCiudad() {

            return ciudad;

      }

      public void setCiudad(String ciudad) {

            this.ciudad = ciudad;

      }

      public String getTemperaturaMaxima() {

            return temperaturaMaxima;

      }

      public void setTemperaturaMaxima(String temperaturaMaxima) {

            this.temperaturaMaxima = temperaturaMaxima;

      }

      public String getTemperaturaMinima() {

            return temperaturaMinima;

      }

      public void setTemperaturaMinima(String temperaturaMinima) {

            this.temperaturaMinima = temperaturaMinima;

      }

}

Controlador

El formulario asociado (es.lycka.holamundoStruts138Jasper.form.InformeForm) es muy sencillo, sólo lleva un informe (objeto Informe) :

package es.lycka.holamundoStruts138Jasper.form;

import org.apache.struts.action.ActionForm;

import es.lycka.holamundoStruts138Jasper.model.Informe;

public class InformeForm extends ActionForm {

      private Informe informe = new Informe ();

      public Informe getInforme() {

            return informe;

      }

      public void setInforme(Informe informe) {

            this.informe = informe;

      }

}

La clase action que necesitamos (es.lycka.holamundoStruts138Jasper.action.InformeAction) no es muy complicada, así que vamos a introducir nostros una complicación. Sabemos que por defecto Struts ejecuta el método execute de nuestro action. Pero y si queremos tener varios métodos en el mismo action ? Por ejemplo si queremos asociar varias acciones al mismo tipo de objetos : listar Informes, obtener Informe, insertar Informe, actualizar Informe, borrar Informe…

Una solución sencilla es tener varios métodos en el mismo action y utilizar uno u otro según la configuración de la acción (en el struts-config.xml). Por eso crearemos el método:

package es.lycka.holamundoStruts138Jasper.action;

import java.io.ByteArrayOutputStream;

import java.io.IOException;

import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.Action;

import org.apache.struts.action.ActionForm;

import org.apache.struts.action.ActionForward;

import org.apache.struts.action.ActionMapping;

import es.lycka.holamundoStruts138Jasper.form.InformeForm;

import es.lycka.holamundoStruts138Jasper.negocio.InformeBO;

public class InformeAction extends Action {

      public ActionForward execute (ActionMapping mapping, ActionForm form,

                  HttpServletRequest request, HttpServletResponse response) {

            InformeForm informeForm = (InformeForm) form;

            String destino                           = "ERROR";

            try {

                  byte[] informePDF = InformeBO.generarInforme (informeForm.getInforme ());

                  mostrarInforme (informePDF, response);

            } catch (Exception e) {

                  e.printStackTrace();

                  request.setAttribute("mensaje", "Error interno. Por favor, inténtelo otra vez en unos minutos.");

            }

            return mapping.findForward(destino);

      }

}

Negocio

Creamos un objeto de negocio (es.lycka.holamundoStruts138Jasper.negocio.InformeBO) pero no implementamos todavía el método.

package es.lycka.holamundoStruts138Jasper.negocio;

import es.lycka.holamundoStruts138Jasper.model.Informe;

public class InformeBO {

      public static byte[] generarInforme (Informe informe) throws Exception {

            return null;

      }

}

Configurar todo esto

En la etiqueta <form-beans> declaramos nuestro nuevo formulario :

<form-bean name="informeForm" type="es.lycka.holamundoStruts138Jasper.form.InformeForm" />

Y en la etiqueta <action-mappings> declaramos la nueva acción. Utilizamos parameter para indicar qué método del action se ejecutará.

<action path="/generarInforme" type="es.lycka.holamundoStruts138Jasper.action.InformeAction"

      name="informeForm" scope="request" validate="false" parameter="generar">

      <forward name="SUCCESS" path="/jsp/indexIdentificado.jsp" />

      <forward name="ERROR" path="/jsp/indexIdentificado.jsp" />

</action>

Finalmente la estructura del proyecto quedaría así (pincha en la imagen para verla ampliada) :

 

Comenta