Generar archivo ZIP partiendo de múltiples ByteArray

Estos días he tenido que implementar una funcionalidad no excesivamente compleja, pero que implicaba lidiar con múltiples ByteArrayOutputStream. Como no encontré nada parecido a lo que me hacía falta, dejo aquí la solución por si a alguien le puede servir de ayuda.

El problema

Tenemos varios ByteArrayOutputStream (en mi caso venían devueltos por una herramienta de generación de informes) independientes, sobre los que queremos iterar (supongamos que llamamos a un servicio externo en cada iteración que nos devuelve el ByteArray) y construir un zip con un fichero que represente cada uno de esos ByteArray.

El planteamiento

Muchas de las soluciones que encontré partían de la base de crear un objeto File sobre un archivo ya existente, o crear un FileOutputStream que le pasaríamos al constructor del ByteArrayOutputStream, etc, etc. En cualquier caso, si se trataba de más de un fichero, iteraban sobre los FileInputstream que representaban el fichero y listo: para cada uno creabamos un ZipEntry que se añadía al ZipOutputStream, y hala, ZIP creado. Pero… ¿y si los ficheros sobre los que iteramos no son ficheros todavía? La cosa cambia. Y si hablamos de servlets, esto es más importante si cabe, ya que los permisos de lectura y escritura en el servidor no son cosa de risa.

Necesitabamos, pues, usar un stream en memoria, y cada uno de ellos guardarlo como una entrada ZIP en un ZipOutputStream, para luego devolver ese objeto al cliente (este último paso complicó más las cosas si cabe, ya que en un entorno que usa ZK no es tan simple recoger el objeto response. Del ServletResponse ya ni hablemos).

La solución

Necesitamos dos ByteArrayOutputStream. Si, dos. Uno para recoger cada uno de los objetos devueltos en la iteración que compone el ZIP, y el otro… pues una instancia inicialmente vacía que se pasa al constructor del ZipOutputStream. El código:

final ByteArrayOutputStream baosOut = new ByteArrayOutputStream();
final ZipOutputStream zos = new ZipOutputStream(baosOut);
try {
	for (final Map.Entry<String, ByteArrayOutputStream> m : mapBouts.entrySet()) {
		final ByteArrayOutputStream baosRpt = m.getValue();
		final ZipEntry entry = new ZipEntry(m.getKey());
		entry.setSize(baosRpt.toByteArray().length);
		zos.putNextEntry(entry);
		zos.write(baosRpt.toByteArray());
		zos.closeEntry();
	}
	if (zos != null) {
		zos.finish();
		zos.close();
		final Calendar cal = Calendar.getInstance();
		final DateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
		sb = new StringBuilder(df.format(cal.getTime())).append("-").append(aName).append(".zip");
		// ZK
		Filedownload.save(baosOut.toByteArray(), "application/octet-stream", sb.toString());
	}
} catch (final IOException e) {
	// Exception Handler
} finally {
	if (zos != null) {
		try {
			zos.close();
		} catch (final IOException ex) {
			// Otro Exception Handler
		}
	}
}

Dejando de lado la parafernalia, el mapa mantiene una clave que será el nombre de cada uno de los archivos zip, y su valor es el array de bytes que representa el archivo en memoria. Para cada entrada del mapa, creamos el correspondiente archivo y lo añadimos al zip con ZipEntry. Una vez hemos recorrido el mapa, si el ZipOutputStream realmente existe, lo cerramos y le asignamos un nombre. Después lo enviamos al cliente, en este caso usando ZK.

La clave aquí es la segunda línea: al crear un ZipOutputStream y pasarle un ByteArrayOutputStream “vacío”, el primero irá “rellenando” este array de bytes a medida que vayamos incluyendo ZipEntry‘s en la iteración. Al finalizar, ese array de bytes contiene la representación en memoria del archivo zip, con lo cual solo queda enviarlo a un output para el cliente.

¿Y ahora que?

Hay una pequeña pega, que iré investigando cuando tenga tiempo. Las entradas del archivo zip, es decir, los ficheros que componen dicho archivo, no aceptan nombres en formato UTF-8. Cualquier carácter que exceda del 127 en la tabla ASCII se verá como un carácter extraño, así que tildes, eñes, cedillas y demás sufrirán este engorroso inconveniente. A los efectos no es un problema, ya que el zip se descomprime y los archivos abren perfectamente, pero visualmente queda horrible. Y si el archivo contiene muchos caracteres de este tipo… pues peor.

Advertisements