Escenario
Tenemos una aplicación, en este caso un portlet de Liferay, que genera archivos XLS mediante Apache POI y se necesita convertir estos archivos a PDF. Una opción es generar los archivos PDF con iText. Es una buena opción si se tienen conocimientos sobre iText y el formato de los archivos a generar no es demasiado complejo/laborioso. Otra opción, mucho más sencilla y rápida, es utilizar OpenOffice y JODConverter. En este post se detalla como implementar esta segunda opción.
A grandes rasgos, estos son los pasos a seguir:
- Descargar e instalar OpenOffice.
- Publicar OpenOffice como un servicio del SO.
- Añadir a nuestro proyecto las librerías de JODConverter.
- Escribir en nuestra aplicación el código para convertir los archivos XLS a PDF.
A continuación se detallan los pasos citados sobre una máquina Windows. La misma solución se puede aplicar sobre otro SO.
Descargar e instalar OpenOffice
- Descargar el instalable de OpenOffice para el SO. En este caso se ha descargado la versión 3.4.1 para Windows desde la propia página de OpenOffice.
- Instalar el ejecutable descargado siguiendo el wizard de la instalación.
Publicar OpenOffice como un servicio del SO
- Descargar Windows Server 2003 Resource Kit Tools e instalarlo siguiendo los pasos del wizard.
- Seguir los siguientes pasos para publicar el servicio en el SO. Para más información consultar el link: Cómo crear un servicio del SO definido por el usuario
1) En una consola del sistema (ejecutando CMD.EXE), escriba el comando siguiente:
%WINDOWS_RESOURCE_KITS_DIR%\Tools\Instsrv.exe %NOMBRE_SERVICIO% %WINDOWS_RESOURCE_KITS_DIR%\Tools\Srvany.exe
Donde:
%WINDOWS_RESOURCE_KITS_DIR%: Directorio donde se ha instalado Windows Resource Kits
%NOMBRE_SERVICIO%: Nombre con el que identificaremos el servicio de OpenOffice en el SO
Ejemplo:
C:\Program Files (x86)\Windows Resource Kits\Tools\Instsrv.exe OpenOfficeUnoServer C:\ Program Files (x86)\Windows Resource Kits\Tools\Srvany.exe
2) Ejecute el Editor del Registro (Regedt32.exe) y busque la subclave siguiente:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\%NOMBRE_SERVICIO%
en nuestro caso: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\OpenOfficeUnoServer
2.1) En el menú Edición, haga clic en Agregar clave. Escriba lo siguiente y haga clic en Aceptar:
Nombre de clave: Parameters
Clase: <dejar en blanco>
2.2) Seleccione la clave Parameters. En el menú Edición, haga clic en Agregar valor. Escriba lo siguiente y haga clic en Aceptar:
Nombre de valor: Application
Tipo de datos: REG_SZ
Cadena: %OPEN_OFFICE_DIR%
Donde:
%OPEN_OFFICE_DIR%: Directorio donde se ha instalado OpenOffice
Ejemplo:
C:\Program Files\OpenOffice.org 3\program\soffice.exe
2.3) Seleccione la clave Parameters. En el menú Edición, haga clic en Agregar valor. Escriba lo siguiente y haga clic en Aceptar:
Nombre de valor: AppParameters
Tipo de datos: REG_SZ
Cadena: -headless -accept=socket,port=8100;urp;
Una vez realizado los pasos anteriores, comprobar que la creación del servicio ha sido correcta:
Añadir a nuestro proyecto las librerías de JODConverter
En nuestro caso se trata de un portlet de Liferay que tenemos en Maven. De forma que simplemente se deben añadir al POM.xml las siguientes dependencias:
<dependency>
<groupId>com.artofsolving</groupId>
<artifactId>jodconverter</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.4.3</version>
</dependency>
Escribir en nuestra aplicación el código para convertir los archivos XLS a PDF
Finalmente creamos un servlet que se encargará de generar los archivos XLS generados mediante Apache POI a PDF. Simplemente se deben seguir estos pasos:
- Abrir la conexión a OpenOffice.
- Generar el archivo PDF dado el XLS generado mediante Apache POI.
- Cerrar la conexión a OpenOffice.
A continuación se muestra el código del servlet, en negrita se resaltan las partes importantes:
import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; import java.net.ConnectException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.output.ByteArrayOutputStream; import org.apache.log4j.Logger; import org.apache.poi.hssf.usermodel.HSSFCell; import org.apache.poi.hssf.usermodel.HSSFCellStyle; import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.hssf.usermodel.HSSFRow; import org.apache.poi.hssf.usermodel.HSSFSheet; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.hssf.util.HSSFColor; import org.apache.poi.hssf.util.HSSFRegionUtil; import org.apache.poi.ss.util.CellRangeAddress; import org.springframework.core.io.Resource; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.Controller; import com.artofsolving.jodconverter.DocumentConverter; import com.artofsolving.jodconverter.DocumentFamily; import com.artofsolving.jodconverter.DocumentFormat; import com.artofsolving.jodconverter.openoffice.connection.OpenOfficeConnection; import com.artofsolving.jodconverter.openoffice.connection.SocketOpenOfficeConnection; import com.artofsolving.jodconverter.openoffice.converter.OpenOfficeDocumentConverter; public class DonwloadAnualPDFReport implements Controller { private OpenOfficeConnection connection; public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception { // Recogemos parametros para filtrar int anyInici = Integer.parseInt(request.getParameter("year")); String type = (String)request.getParameter("type"); // Preparamos información para generar el archivo XLS GregorianCalendar cal; try { cal = new GregorianCalendar(anyInici, 0, 1); } catch (Exception e) { cal = new GregorianCalendar(); } cal.set(cal.HOUR_OF_DAY,6); cal.set(cal.MINUTE,0);cal.set(cal.SECOND, 0);cal.set(cal.MILLISECOND, 0); cal.setLenient(false); Map<Long, List<HashMap<String, Object>>> infoReserves = salaReservaManager.obteInfoAnual(cal); // Generamos el archivo XLS mediante Apache POI HSSFWorkbook book = exportToExcel(cal, infoReserves, salaManager.getAll(), diaFestiuManager.getAll()); // Gestionamos la conexion con OpenOffice y generamos el PDF OutputStream out = response.getOutputStream(); openOpenOfficeConnection(); convertExcelToPDF(book, out); closeOpenOfficeConnection(); // Preparamos el response SimpleDateFormat format = new SimpleDateFormat("dd_MM_yyyy"); response.setContentType("application/force-download"); response.setHeader("Content-Transfer-Encoding", "binary"); response.setHeader("Content-Disposition","attachment; filename=\"Anual_" + anyInici + "_" +format.format(cal.getTime())+".pdf" + "\";"); response.flushBuffer(); out.close(); return null; } public void openOpenOfficeConnection() { try { if (connection == null || !connection.isConnected()) connection = new SocketOpenOfficeConnection(8100); connection.connect(); } catch (ConnectException e) { e.printStackTrace(); } } public void closeOpenOfficeConnection() { try { if (connection != null && connection.isConnected()) connection.disconnect(); } catch (Exception e) { e.printStackTarce(); } } private void convertExcelToPDF(HSSFWorkbook wb, OutputStream out) throws Exception { DocumentConverter converter = new OpenOfficeDocumentConverter(connection); DocumentFormat inputDocumentFormat = new DocumentFormat("Microsoft Excel", DocumentFamily.SPREADSHEET, "application/vnd.ms-excel", "xls"); inputDocumentFormat.setExportFilter(DocumentFamily.SPREADSHEET, "MS Excel 97"); DocumentFormat outputDocumentFormat = new DocumentFormat("Portable Document Format", DocumentFamily.TEXT,"application/pdf", "pdf"); outputDocumentFormat.setExportFilter(DocumentFamily.SPREADSHEET, "calc_pdf_Export"); converter.convert(new ByteArrayInputStream(generarBytesExcel(wb)), inputDocumentFormat, out, outputDocumentFormat); } public byte[] generarBytesExcel(HSSFWorkbook hssfWorkbook) { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); try { hssfWorkbook.write(outStream); outStream.close(); } catch (IOException e) { e.printStackTrace(); } return outStream.toByteArray(); } }
En este punto ya tenemos todo listo. Como se puede observar con la ayuda de OpenOffice y JODConverter se simplifica mucho la generación de archivos PDF. En el caso concreto de este post, se generan archivos PDF a partir de archivos XLS generados mediante Apache POI, pero está misma solución se puede utilizar para cualquier tipo de archivo que debamos generar en PDF.