From 8f326c1366d82b70f7ef80dd421e0f64ac025d09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Garcia?= Date: Sun, 21 Sep 2014 16:25:28 -0300 Subject: [PATCH 1/5] Support for download files as zip archive --- .../observer/download/DownloadBuilder.java | 33 +++++++- .../observer/download/ZipDownload.java | 50 ++++++++++++ .../observer/download/ZipDownloadTest.java | 78 +++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java create mode 100644 vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ZipDownloadTest.java diff --git a/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/DownloadBuilder.java b/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/DownloadBuilder.java index 3d4650f49..f5c4de286 100644 --- a/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/DownloadBuilder.java +++ b/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/DownloadBuilder.java @@ -6,6 +6,8 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.InputStream; +import java.nio.file.Path; +import java.util.List; import javax.enterprise.inject.Vetoed; @@ -73,6 +75,23 @@ public static ByteArrayDownloadBuilder of(byte[] input) { return new ByteArrayDownloadBuilder(input); } + /** + * Creates an instance for build a {@link ZipDownload}.
+ * + * + * List listOfFiles = [...]; + * Download download = DownloadBuilder.of(listOfFiles) + * .withFileName("resume.zip") + * .build(); + * + * + * @param files List of input files + * @throws NullPointerException If the {@code input} argument is {@code null} + */ + public static ZipDownloadBuilder of(List files) { + return new ZipDownloadBuilder(files); + } + static abstract class AbstractDownloadBuilder { protected String fileName; protected String contentType; @@ -136,4 +155,16 @@ public ByteArrayDownload build() { return new ByteArrayDownload(buff, contentType, fileName, doDownload); } } -} + + public static class ZipDownloadBuilder extends AbstractDownloadBuilder { + private final List files; + + ZipDownloadBuilder(List files) { + this.files = requireNonNull(files, "files can't be null"); + } + + public ZipDownload build() { + return new ZipDownload(fileName, files); + } + } +} \ No newline at end of file diff --git a/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java b/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java new file mode 100644 index 000000000..3e460419a --- /dev/null +++ b/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java @@ -0,0 +1,50 @@ +package br.com.caelum.vraptor.observer.download; + +import static java.nio.file.Files.copy; +import static java.util.Arrays.asList; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.zip.CRC32; +import java.util.zip.CheckedOutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +import javax.servlet.http.HttpServletResponse; + +/** + * Supports multiple files download as a zip file. + * + * @author Otávio Scherer Garcia + * @since REPLACE_ME_ON_NEXT_RELEASE + */ +public class ZipDownload implements Download { + + private final String filename; + private final Iterable files; + + public ZipDownload(String filename, Iterable files) { + this.filename = filename; + this.files = files; + } + + public ZipDownload(String filename, Path... files) { + this(filename, asList(files)); + } + + @Override + public void write(HttpServletResponse response) + throws IOException { + response.setHeader("Content-disposition", "attachment; filename=" + filename); + response.setHeader("Content-type", "application/zip"); + + CheckedOutputStream stream = new CheckedOutputStream(response.getOutputStream(), new CRC32()); + try (ZipOutputStream zip = new ZipOutputStream(stream)) { + for (Path file : files) { + zip.putNextEntry(new ZipEntry(file.getFileName().toString())); + copy(file, zip); + zip.closeEntry(); + } + } + } +} \ No newline at end of file diff --git a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ZipDownloadTest.java b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ZipDownloadTest.java new file mode 100644 index 000000000..7a858c119 --- /dev/null +++ b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ZipDownloadTest.java @@ -0,0 +1,78 @@ +/*** + * Copyright (c) 2009 Caelum - www.caelum.com.br/opensource All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package br.com.caelum.vraptor.observer.download; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ZipDownloadTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + private Path inpuFile0; + private Path inpuFile1; + + private @Mock HttpServletResponse response; + private @Mock ServletOutputStream socketStream; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + inpuFile0 = folder.newFile().toPath(); + inpuFile1 = folder.newFile().toPath(); + + when(response.getOutputStream()).thenReturn(socketStream); + } + + @Test + public void builderShouldThrowsExceptionIfFileDoesntExists() throws Exception { + thrown.expect(NoSuchFileException.class); + + Download download = new ZipDownload("file.zip", Paths.get("/path/that/doesnt/exists/picture.jpg")); + download.write(response); + } + + @Test + public void shouldUseHeadersToHttpResponse() throws IOException { + Download fd = new ZipDownload("download.zip", inpuFile0, inpuFile1); + fd.write(response); + + verify(response, times(1)).setHeader("Content-type", "application/zip"); + verify(response, times(1)).setHeader("Content-disposition", "attachment; filename=download.zip"); + } +} From 659633b75ccc3612450d8b79d53c2a36819a3b9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Garcia?= Date: Sun, 21 Sep 2014 19:58:58 -0300 Subject: [PATCH 2/5] Some small changes in download tests --- .../vraptor/observer/download/ByteArrayDownloadTest.java | 5 ++--- .../vraptor/observer/download/FileDownloadTest.java | 9 +++------ .../observer/download/InputStreamDownloadTest.java | 8 ++++---- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ByteArrayDownloadTest.java b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ByteArrayDownloadTest.java index 4977063d0..e02758eaa 100644 --- a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ByteArrayDownloadTest.java +++ b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/ByteArrayDownloadTest.java @@ -20,7 +20,6 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -50,9 +49,9 @@ public void setUp() throws Exception { MockitoAnnotations.initMocks(this); bytes = new byte[] { (byte) 0x0 }; - this.outputStream = new ByteArrayOutputStream(); + outputStream = new ByteArrayOutputStream(); - this.socketStream = new ServletOutputStream() { + socketStream = new ServletOutputStream() { @Override public void write(int b) throws IOException { outputStream.write(b); diff --git a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/FileDownloadTest.java b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/FileDownloadTest.java index 4edaf11a4..8294a919e 100644 --- a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/FileDownloadTest.java +++ b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/FileDownloadTest.java @@ -23,8 +23,8 @@ import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; +import java.nio.file.Files; import javax.servlet.ServletOutputStream; import javax.servlet.WriteListener; @@ -56,15 +56,12 @@ public class FileDownloadTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - bytes = new byte[] { (byte) 0 }; + bytes = new byte[] { (byte) 0x0 }; outputStream = new ByteArrayOutputStream(); file = folder.newFile(); + Files.write(file.toPath(), bytes); - try (FileOutputStream fileStream = new FileOutputStream(file)) { - fileStream.write(bytes); - } - socketStream = new ServletOutputStream() { @Override public void write(int b) throws IOException { diff --git a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/InputStreamDownloadTest.java b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/InputStreamDownloadTest.java index a61da6da4..087c88822 100644 --- a/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/InputStreamDownloadTest.java +++ b/vraptor-core/src/test/java/br/com/caelum/vraptor/observer/download/InputStreamDownloadTest.java @@ -52,11 +52,11 @@ public class InputStreamDownloadTest { public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - bytes = new byte[] { (byte) 0 }; - this.inputStream = new ByteArrayInputStream(bytes); - this.outputStream = new ByteArrayOutputStream(); + bytes = new byte[] { (byte) 0x0 }; + inputStream = new ByteArrayInputStream(bytes); + outputStream = new ByteArrayOutputStream(); - this.socketStream = new ServletOutputStream() { + socketStream = new ServletOutputStream() { @Override public void write(int b) throws IOException { outputStream.write(b); From 578a7cc987d8f8ce976256c6ec810735ec4c9e1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Garcia?= Date: Tue, 23 Sep 2014 12:01:26 -0300 Subject: [PATCH 3/5] Adding info regarding ZipDownload (EN) --- .../content/pt/docs/download-e-upload.html | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/vraptor-site/content/pt/docs/download-e-upload.html b/vraptor-site/content/pt/docs/download-e-upload.html index 128b536af..9b40912b7 100644 --- a/vraptor-site/content/pt/docs/download-e-upload.html +++ b/vraptor-site/content/pt/docs/download-e-upload.html @@ -27,7 +27,7 @@ @Controller public class PerfilController { public Download foto(Perfil perfil) { - File file = new File("/path/para/a/foto." + perfil.getId()+ ".jpg"); + File file = new File("/caminho/para/a/foto." + perfil.getId()+ ".jpg"); String contentType = "image/jpg"; String filename = perfil.getNome() + ".jpg"; @@ -60,6 +60,19 @@ } ~~~ +Você também pode enviar uma lista de arquivos para download, e desta forma eles serão enviados ao browser +comprimidos em um arquivo Zip. + +~~~ +#!java +public Download fotos() { + Path foto01 = new File("/caminho/para/a/foto01.jpg").toPath(); + Path foto02 = new File("/caminho/para/a/foto02.jpg").toPath(); + return new ZipDownload("fotos.zip", foto01, foto02); +} +~~~ + + ##Using DownloadBuilder `DownloadBuilder` é uma classe útil para ajudar você a criar instâncias da classe `Download`, usando uma interface fluente. Para criar uma instância de um `FileDownload` você pode escrever o seguinte código: From 5d084337c92ddc36380407ef24c548367d5dbd4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Garcia?= Date: Tue, 23 Sep 2014 12:04:14 -0300 Subject: [PATCH 4/5] Adding info about ZipDownload --- .../content/en/docs/download-and-upload.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vraptor-site/content/en/docs/download-and-upload.html b/vraptor-site/content/en/docs/download-and-upload.html index 55e25df8d..863d971a3 100644 --- a/vraptor-site/content/en/docs/download-and-upload.html +++ b/vraptor-site/content/en/docs/download-and-upload.html @@ -59,6 +59,18 @@ } ~~~ +You can also put a list of files to download, so will sent to the browser compressed as Zip archive. + +~~~ +#!java +public Download pictures() { + Path pic01 = new File("/path/to/the/pic01.jpg").toPath(); + Path pic02 = new File("/path/to/the/pic02.jpg").toPath(); + return new ZipDownload("pics.zip", pic01, pic02); +} +~~~ + + ##Using DownloadBuilder `DownloadBuilder` is a class to help you to create instances for `Download`, using a fluent interface. To create a `FileDownload` you can write this code: From 9ff468d864c28bc67562fbac6180a935986d452c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ot=C3=A1vio=20Garcia?= Date: Tue, 23 Sep 2014 18:22:45 -0300 Subject: [PATCH 5/5] Updating release version --- .../br/com/caelum/vraptor/observer/download/ZipDownload.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java b/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java index 3e460419a..ae32d37ef 100644 --- a/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java +++ b/vraptor-core/src/main/java/br/com/caelum/vraptor/observer/download/ZipDownload.java @@ -16,7 +16,7 @@ * Supports multiple files download as a zip file. * * @author Otávio Scherer Garcia - * @since REPLACE_ME_ON_NEXT_RELEASE + * @since 4.1 */ public class ZipDownload implements Download { @@ -47,4 +47,4 @@ public void write(HttpServletResponse response) } } } -} \ No newline at end of file +}