Publica tu how-to

Dinos como hacer lo que sabes hacer, mándanos un email a wdonet@gmail.com y lo publicamos (dos días máximo) o si te interesa unirte al equipo de redactores, también háznoslo saber por correo.

Configurar POI en Spring 3 para generar archivos Excel

He decidido registrar el proceso de como generar archivos Excel con POI mediante Spring 3 para que no se me olvide (aunque es bastante sencillo).  Me sorprendí de terminarlo en tan sólo 3 pasos!

1) Generar un controller que obtiene los valores que se pretenden mostrar en la vista, los agrega al modelmap de MVC que maneja Spring y luego devuelva el nombre de la vista encargada de generar el excel.

@Controller
@RequestMapping("/excel")
public class ExcelController {

    @Autowired
    private Servicio servicio;

    @RequestMapping(value = "/{parametro}", method = RequestMethod.GET)
    public String generarExcelByParametro(
            ModelMap modelMap, HttpServletResponse response,
            @PathVariable("parametro") String nombreVista) {
        modelMap.put("datos", servicio.obtenerDatos(parametro));

        response.setHeader("Content-type", "application/vnd.ms-excel");
        response.setHeader("Content-Disposition","attachment; filename=\"excel.xls\"");
        return "reporteExcel";
    }

}

Observa que el método generarExcelByParametro() del controller responde a una petición http://{servidor:puerto}/{contexto}/excel/{parametro} y realiza algo en un servicio que devuelve cierta información que se espera administrar de lado de la vista (más adelante lo vemos) y lo guarda dentro del ModelMap de spring con el identificador "datos".

Los headers que se agregan al response, son solo para asegurar que cuando se genere el archivo .xls, se identifique como tal y se obligue al usuario a descargarlo.

Finalmente la cadena ("reporteExcel") que se devuelve determina el nombre de la vista que interpretara los datos para generar el documento.

2) Configurar la vista
Es necesario generar un archivo de nombre views.properties o agregar al que ya exista en el classpath del proyecto, algo como lo siguiente:

reporteExcel.(class)=mx.com.empresa.miproyecto.web.view.ExcelView

La linea comienza con el nombre de la vista, que es el nombre de la cadena que se devuelve en el controller; no olviden encerrar el .class entre paréntesis.  La clase (junto con el paquete) que se asigna como valor indica la ubicacion de la View (o vista), misma que interpretará los datos y los plasmará sobre el Excel.

3) Generar la View (vista) que interpreta los datos del controller.  Se debe generar una clase que herede de org.springframework.web.servlet.view.document.AbstractExcelView e implemente el metodo buildExcelDocument(). En dicho método es donde se debe recuperar los datos que se involucraron en el ModelMap desde el controller.

public class ExcelView extends AbstractExcelView {
    @Override
    protected void buildExcelDocument(
            Map<String, Object> model, HSSFWorkbook book,
            HttpServletRequest request, HttpServletResponse response)
            throws Exception {
        //recuperar datos
        List reporte = (List<Map<String, Object>>) model.get("datos");
        //crear la hoja
        HSSFSheet hoja = book.createSheet("nombre de la hoja");

        //generar un estilo para la celda
        HSSFCellStyle estiloCelda = obtenerEstiloCelda(book);

        //guardar valores en toda la hoja
        HSSFCell celda;
        int row = 0;
        int col;
        for (Map m : (List<Map<String, Object>>) reporte){
            col = 0;
            // guarda valores en una fila de la hoja
            for (Object o : m.keySet()) {
                celda = getCell(hoja, row, col++);
                celda.setCellStyle(estiloCelda);
                setText(celda, null == m.get(o) ? "[nulo]" : m.get(o).toString());
            }
        row++;
        }
}

La información a utilizar, según el controller que hicimos antes, se obtiene del ModelMap con el identificador "datos".  Para este caso se está obteniendo una lista de registros devueltos comúnmente por la ejecucion de un jdbcTemplate.queryForList("... mi query sql ...") de Spring.

Es necesario crerar una hoja mediante createSheet() y usar el valor devuelto (HSSFSheet) para agregar contenido, diseño, colores de fondo y bordes, agregar registros o contenido en celdas específicas con coordenadas (fila, columna), rotar texto, fuente, tamaño, forma de texto y clasificación de contenido de la celda, entre otros muchos más.

Por ejemplo puedes utilizar la instancia de la hoja (HSSFSheet) y el metodo autoSizeColumn((short) 1) para configurar una columna (este caso la B) como autoajustable a lo ancho, de acuerdo al texto que contenga.  Para modificar el alto de la primera fila, hacer:
        HSSFRow primerRegistro = hoja.createRow(0);
        primerRegistro.setHeight((short) 1200);  //es una altura de 60.00 cuando lo ves desde el Excel

Observa que utilizas dos métodos básicamente: getCell() para obtener una celda (HSSFCell) de una hoja determinada en una ubicación con la combinación fila, columna; y setText() para asignarle un valor de texto a dicha celda.

También puedes crear templates de estilos de celda para luego aplicarlos a un grupo de ellas.  En el código siguiente se esta definiendo un tipo de letra (HSSFFont) de ancho normal, con tamaño 8 y un estilo (HSSFCellStyle) con alineación a la izquierda y borde inferior con pequeños puntos consecutivos(BORDER_DOTTED).

    private static HSSFCellStyle obtenerEstiloCelda(HSSFWorkbook book) {
        HSSFFont fuente = book.createFont();
        fuente.setBoldweight(HSSFFont.BOLDWEIGHT_NORMAL);
        fuente.setFontHeightInPoints((short)8);
        HSSFCellStyle estilo = book.createCellStyle();
        estilo.setFont(fuente);
        //alineacion
        estilo.setAlignment(HSSFCellStyle.ALIGN_LEFT);
        estilo.setVerticalAlignment(HSSFCellStyle.VERTICAL_TOP);
        //border
        estilo.setBorderBottom(HSSFCellStyle.BORDER_DOTTED);
        return estilo;
    }

Existen muchas más alternativas para los estilos pero para no volver a escribir lo que ya está publicado en otros lados, debes revisar las ligas siguientes para abundar más sobre el tema:
Aunque puedes realizar un montón de ajustes en el estilo de tu hoja, básicamente, tu hoja la diseñas en la vista (View) que hereda de la AbstractExcelView y Spring hace todo el resto del trabajo. Sencillo no ?

3 comentarios:

  1. Hola muy bueno el blog!! tengo un inconveniente cuando quise realizar esto y es que cuando lo pruebo con IE me muestra un mensaje de que IE no puede descargar el archivo. Alguna idea??
    Saludos!
    Franco

    ResponderEliminar
  2. Encontre en la red una solucion, el agregar estos headers para evitar problemas con el I.E., pruebalo:

    response.setHeader("Content-type", "application/CSV");
    response.setHeader("Cache-Control: must-revalidate");
    response.setHeader("Pragma: must-revalidate");
    response.setHeader("Content-disposition: attachment; filename=filename.csv");

    o prueba con estos otros:
    response.setHeader("Expires: 0");
    response.setHeader("Cache-Control: must-revalidate, post-check=0, pre-check=0");

    ResponderEliminar
  3. Antes que nada muchísimas gracias por tomarte el tiempo y responder. Te cuento que google bastante por este problema y en muchos sitios encontré lo que planteas como posible solución, en mi caso no funciona, yo tengo la app corriendo en un entorno SSL, encontré en la página de Microsoft la solución y es desactivando la opción "Do not save encrypted pages to disk" ubicada en Tools / Internet Options > Advanced.

    Esto soluciona el problema, pero no es una opción aceptable, pues no puedo estar pidiéndole a los clientes que hagan esto. Voy a seguir investigando, el tiempo me corre :S
    Saludos
    Franco

    ResponderEliminar

Que opinas sobre esta publicación?