If you are using Spring 3, you can use a lot of the swagger annotation interfaces to help cleanly build your ResponseEntity while using the StreamingResponseBody to properly prototype the format to be expected by spring.
The body code here is a shortened way of mapping the ZipOutputStream stream into StreamingResponseBody type that the controller expects to return (.body(out -> {...}) does this in my code below).
The [controller] code would look like this:
@GetMapping(value = "/myZip")
@Operation(
summary = "Retrieves a ZIP file from the system given a proper request ID.",
responses = {
@ApiResponse(
description = "Get ZIP file containing data for the ID.",
responseCode = "200",
content = @Content(schema = @Schema(implementation = StreamingResponseBody.class))),
@ApiResponse(
description = "Unauthenticated",
responseCode = "401",
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class))),
@ApiResponse(
description = "Forbidden. Access Denied.",
responseCode = "403",
content = @Content(schema = @Schema(implementation = ApiErrorResponse.class)))
})
public ResponseEntity<StreamingResponseBody> myZipBuilder(@RequestParam String id, HttpServletResponse response)
throws IOException {
final String fileName = "MyRequest_" + id + "_" + new SimpleDateFormat("MMddyyyy").format(new Date());
return ResponseEntity.ok()
.header(CONTENT_DISPOSITION,"attachment;filename=\"" + fileName + ".zip\"")
.contentType(MediaType.valueOf("application/zip"))
.body(out -> myZipService.build(id, response.getOutputStream()));
}
The code for your service build method simply needs to take in the whatever parms you need for the data, plus your ServletOutputStream responseOutputStream parm to allow you to build your ZipOutputStream object seeded by that stream.
In my little example below, you can see I build some CSV data in the buildDataLists method (not shown), which is just a List of List<String[]>.. I then take each of the top level List items and push them into the ZipOutputStream object using my streamWriteCsvToZip. The point is, you build your ZIP stream seeded with the responseOutputStream you were given from the controller. Once you have built your zip fully, make sure to close it up (in my case zos.close()). Then return the zos object to the controller.
/**
* Get ZIP file containing datafiles for a given request id
*
* @param id of the request
* @param responseOutputStream for streaming the zip results
* @return ZipOutputStream a ZIP file stream for the contents
* @throws AccessDeniedException if user does not have access to this function
* @throws UnauthenticatedException if user is not authenticated
*/
public ZipOutputStream build(String id, ServletOutputStream responseOutputStream) throws IOException {
try {
List<List<String[]>> csvFilesContents = buildDataLists(id);
final ZipOutputStream zos = new ZipOutputStream(responseOutputStream);
streamWriteCsvToZip("control", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("roles", id, zos, csvFilesContents.remove(0));
streamWriteCsvToZip("accounts", id, zos, csvFilesContents.remove(0));
zos.close(); // finally closing the ZipOutputStream to mark completion of ZIP file
return zos;
} catch (IOException | ClientException ex) {
throw ex;
}
}
No magic here. Just take your data and put it into the zip stream. In my case, I was pulling list/array data, dropping it into a CSV, then putting that CSV in to the zip (as an entry by using zos.putNextEntry(entry);). Both the CSVs and the ZIP are kept as streams so that nothing is written to the filesystem during this operation, and the final result can be just streamed out by the controller. Make sure to close out the entry each time you write one to the zip output stream (zos.closeEntry()).
private void streamWriteCsvToZip(String csvName, String id, ZipOutputStream zos, List<String[]> csvFileContents)
throws IOException {
String filename = id + "_" + csvName + ".csv";
ZipEntry entry = new ZipEntry(filename); // create a zip entry and add it to ZipOutputStream
zos.putNextEntry(entry);
CSVWriter csvWriter = new CSVWriter(new OutputStreamWriter(zos)); // Directly write bytes to the output stream
csvWriter.writeAll(csvFileContents); // write the contents
csvWriter.flush(); // flush the writer
zos.closeEntry(); // close the entry. Note: not closing the zos just yet as we need to add more files to our ZIP
}