martes, 21 de febrero de 2017

Java Poi con Maven y Springboot Restful (Usando imagenes)

Este articulo no es un "tutorial". Es un conjunto de notas de mi experiencia.
Si vas a adentrarte en este articulo. Debes conocer como usar POI. Maven y Springboot Restfull.

 Lo primero que debemos hacer es configurar el POM del maven para usar  todas las características de excel. Claramente algunas no son necesarias. Pero es mejor tenerlas para usar TODO lo que Excel ofrece:


      dom4j
      dom4j
      1.6.1
  
  
   org.apache.poi
   poi
   3.9
  
  
   org.apache.poi
   poi-ooxml
   3.9
  
  
      org.apache.commons
      commons-collections4
      4.1
  
  
      org.apache.xmlbeans
      xmlbeans
      2.3.0
  

Ahora bien para generar reportes POI ofrece varias características principalmente varias vistas de distintos tipos. Para no complicarse vamos a usar lo que SpringBoot ofrece.


No es recomendable... generar los reportes como stream dentro de los HttpServletResponse . Porque se puede cortar el request y el reporte quedar corrupto. Usar siempre las vistas.

//Lo primero debemos escojer un view que se ajuste a las necesidades. Al final hablare de los distintos tipos
public class ExcelBuilder extends AbstractXssfStreamingView {

    @Override
    public void buildExcelDocument(Map model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
        //Aqui ya vienen inicializado el workbook.
        //La unica funcion es implementar. Seria; Crear el sheet, crear filas, celdas, poner estilos, etc
        //No debemos preocuparnos por escribir el workbook en el response. Solo dejemos que el metodo termine.
        //Y Spring sabe que debe tomar el workbook y generar un archivo.
    }

}

//Debemos configurar que la vista sea usada para los endpoint
//Sencillamente podemos usar esto:
@Configuration
public class ExcelBuilderConfig {
    
    @Bean
    public ExcelBuilder ExcelBuilder() {
        return new ExcelBuilder();
    }

}

//Ahora bien solo resta usarlo en algun endpoint
@Api(value = "contest", description = "the contest API")
public interface ContestApi {

    /**
     * contestApplicantExport
     *
     * @param contest
     * @param brandid
     * @return
     */
    @ApiOperation(value = "", notes = "Returns a excel file report according to the information provided (A5:ContestApplicantExport)")
    @ApiResponses(value = {@ApiResponse(code = 200, message = "gallery response"),
            @ApiResponse(code = 200, message = "unexpected error")})
    @RequestMapping(value = "/contest/{contest}/{brandid}", produces = {
            "application/ms-excel"}, method = RequestMethod.GET)
    @org.springframework.web.bind.annotation.GetMapping("/dyo-config")
    ModelAndView contestApplicantExport(
            @ApiParam(value = "Filter based on a specific contest", required = true) @PathVariable("contest") String contest,
            @ApiParam(value = "Filter based on a specific brand", required = true) @PathVariable("brandid") String brandid,
            @ApiParam(value = "HttpServletResponse to setup the excel ouput", required = true) HttpServletResponse response);
}



@Configuration
@RestController
public class ContestApiController implements ContestApi {


    @Override
    public ModelAndView contestApplicantExport(
            @ApiParam(value = "Filter based on a specific contest", required = true) @PathVariable("contest") String contest,
            @ApiParam(value = "Filter based on a specific brand", required = true) @PathVariable("brandid") String brandid,
            @ApiParam(value = "HttpServletResponse to setup the excel ouput", required = true) HttpServletResponse response) {

        Book book = /*Cualquier Objecto*/
        return new ModelAndView("ExcelBuilder", "book", book); //Note que "book" es el dato que se puede obtener de la vista
    }
}

Los diferentes tipos de vistas presentes generan diferentes tipos de workbook.
Cada una con características diferentes:
Podemos mencionar XSSFWorkbook and SXSSFWorkbook.

La clase AbstractXssfStreamingViewes funcional para archivos muy grandes. Sin embargo tiene la tendencia a no funcionar de buena manera con las imágenes. Puede que te tengas que enfrentar a Streaching o a resize ridículos. Después de muchos intentos y de canas verdes. Descubrí que si uno quiere insertar imágenes. La mejor forma es usando XSSFWorkbook.

Para eso escribí mi propia versión de un AbstractXlsxView.

//Simplemente hacemos esto:
public abstract class AbstractXssfStreamingView extends AbstractXlsxView {

    @Override
    protected XSSFWorkbook createWorkbook(Map model, HttpServletRequest request) {
        return new XSSFWorkbook();
    }

}

//Y lo utilizamos:
public class ExcelBuilder extends AbstractXssfStreamingView {

    @Override
    public void buildExcelDocument(Map model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Book book = (Book) model.get("book"); //Usar de referencia al ejemplo de arriba para obtener los datmos del model
        //Tu codigo aqui
    }

}

//En el codigo de ejemplo para agregar imagenes creo un pequeno factory.
//La implementacion es por un conjunto de intefacez de esta manera:

@FunctionalInterface
public interface CellProduceInterface {

    /**
     * update
     * 
     * @param excelCell
     * @param data
     * @param workbook
     * @param sheet
     * @param point
     */
    public void update(org.apache.poi.ss.usermodel.Cell excelCell, Cell data, Workbook workbook, Sheet sheet, Point point);

}

//Y su implementacion
//Notas:
//El excelCell es el cell de POI
//El data es el modelo de la informacion de la celda
//Woorkbook asociado con la vista 
//Sheet actual en que que excelCell esta presente
public class ImageCellProduce implements CellProduceInterface {

    private static final Logger log = LoggerFactory.getLogger(ImageCellProduce.class);

    @Override
    public void update(org.apache.poi.ss.usermodel.Cell excelCell, Cell data, Workbook workbook, Sheet sheet,
            Point point) {
        String path = (String) data.getValue();
        if (path != null) {
            byte[] bytes = ExcelUtil.convertImagePathToBytes(path);
            if (bytes.length > 0) {
                int pictureIdx = workbook.addPicture(bytes, Workbook.PICTURE_TYPE_PNG);
                CreationHelper helper = workbook.getCreationHelper();
                Drawing drawing = sheet.createDrawingPatriarch();
                ClientAnchor anchor = helper.createClientAnchor();
                //Col and Cell pos
                anchor.setCol1(point.y);
                anchor.setRow1(point.x);
                //Height is not possible without stretched the image
                Picture pict = drawing.createPicture(anchor, pictureIdx);
                if (data.getWidth() != -1) {
                    sheet.setColumnWidth(point.y, ExcelUtil.calculatePoiWidthSize(data.getWidth()));
                    data.setWidth(-1);//Asi no se vuelve a aplicar el estilo mas adelante
                }
                if (data.getHeight() != -1) {
                    excelCell.getRow().setHeight((short) ExcelUtil.calculatePoiWidthSize(data.getHeight()));
                    data.setHeight(-1); //Asi no se vuelve a aplicar el estilo mas adelante
                }
                data.setAutoSize(false);
                pict.resize(); //Como es XSSFWorkbook no deberia presentar problemas con el size y mantiene el original
            }
        } else {
            log.info("ImageCellProduce: Ignoring image on " + point);
        }
    }

}

AEM hablemos del arquetipo 11

Cuando creamos un proyecto con AEM. Siempre es importante saber que arquetipo estamos usando. Pues esto me determinara que source, herramien...