diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..30cf57e
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,10 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Ignored default folder with query files
+/queries/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..b26bca2
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+benchmark
\ No newline at end of file
diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml
new file mode 100644
index 0000000..644cdf0
--- /dev/null
+++ b/.idea/go.imports.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..b37fcda
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..7dcc043
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
old mode 100644
new mode 100755
index 304e7e2..df545ab
--- a/build.gradle
+++ b/build.gradle
@@ -10,11 +10,18 @@ repositories {
}
dependencies {
+ implementation group: 'commons-io', name: 'commons-io', version: '2.21.0'
+ implementation "org.apache.commons:commons-lang3:3.20.0"
+ implementation "org.slf4j:jul-to-slf4j:$sl4j_version"
+ implementation "org.slf4j:log4j-over-slf4j:$sl4j_version"
+
testImplementation platform('org.junit:junit-bom:6.0.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
+ testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: '3.0'
+
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
test {
useJUnitPlatform()
-}
\ No newline at end of file
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100755
index 0000000..28f4b38
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1 @@
+sl4j_version=1.7.35
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
old mode 100644
new mode 100755
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
old mode 100644
new mode 100755
index 43d9bce..be2bdc2
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,4 +1,4 @@
-#Wed Apr 08 21:42:31 CEST 2026
+#Wed Apr 08 22:08:17 CEST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
diff --git a/gradlew.bat b/gradlew.bat
old mode 100644
new mode 100755
diff --git a/settings.gradle b/settings.gradle
old mode 100644
new mode 100755
index 1a25252..e3abb74
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1 @@
-rootProject.name = 'Benchmark'
\ No newline at end of file
+rootProject.name = 'benchmark'
\ No newline at end of file
diff --git a/src/main/java/ch/illgner/OrderCalculator.java b/src/main/java/ch/illgner/OrderCalculator.java
deleted file mode 100644
index 091ac7a..0000000
--- a/src/main/java/ch/illgner/OrderCalculator.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package ch.illgner;
-
-public class OrderCalculator {
-
- /**
- * Berechnet den Endpreis nach Rabatt und inklusive 19% MwSt.
- *
- * @param basePrice Der Nettopreis
- * @param discountRate Der Rabatt (z.B. 0.1 für 10%)
- * @return Der Endpreis gerundet auf 2 Nachkommastellen
- */
- public double calculateFinalPrice(double basePrice, double discountRate) {
- double discountedPrice = basePrice * discountRate;
-
- double priceWithTax = discountedPrice * 1.19;
-
- return priceWithTax;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/illgner/ch/createzip/PostBoxZipFileWriter.java b/src/main/java/illgner/ch/createzip/PostBoxZipFileWriter.java
new file mode 100755
index 0000000..ef46a0a
--- /dev/null
+++ b/src/main/java/illgner/ch/createzip/PostBoxZipFileWriter.java
@@ -0,0 +1,138 @@
+package illgner.ch.createzip;
+
+import illgner.ch.encryption.IOutputStreamEnhancer;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.CountingOutputStream;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.crypto.NoSuchPaddingException;
+import java.io.BufferedOutputStream;
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.PosixFilePermission;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.EnumSet;
+import java.util.Set;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+import static java.nio.file.attribute.PosixFilePermission.GROUP_READ;
+import static java.nio.file.attribute.PosixFilePermission.OTHERS_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
+import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
+
+public class PostBoxZipFileWriter implements Closeable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(PostBoxZipFileWriter.class);
+
+ private static final Set ZIP_FILE_PERMISSIONS = EnumSet.of(OWNER_READ, OWNER_WRITE, GROUP_READ, OTHERS_READ);
+
+ private final Path destinationDir;
+ private final String baseName;
+ private final int chunkSize;
+ private final int bufferSize;
+ private final ZipResult zipResult;
+ IOutputStreamEnhancer outputStreamEnhancer;
+
+ private ZipOutputStream zipOutputStream;
+
+ private int fileCount = 1;
+ private long bytesWritten = 0;
+ private File actualZipFile;
+ private CountingOutputStream countingOutputStream;
+
+ public PostBoxZipFileWriter(Path destinationDir, String baseName, int chunkSize, int bufferSize, IOutputStreamEnhancer outputStreamEnhancer) {
+ this.destinationDir = destinationDir;
+ this.baseName = baseName;
+ this.chunkSize = chunkSize;
+ this.bufferSize = bufferSize;
+ this.zipResult = new ZipResult();
+ this.outputStreamEnhancer = outputStreamEnhancer;
+ }
+
+ private ZipOutputStream createNewZipOutputStream() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException,
+ NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+
+ String actualZipFilename = createOutputFilename();
+ actualZipFile = destinationDir.resolve(actualZipFilename).toFile();
+
+ LOG.info("Creating zip part {}: {}", fileCount, actualZipFilename);
+
+ countingOutputStream = new CountingOutputStream(outputStreamEnhancer.enhance(new BufferedOutputStream(new FileOutputStream(actualZipFile), bufferSize)));
+ return new ZipOutputStream(countingOutputStream);
+ }
+
+ private String createOutputFilename() {
+ return String.format("%s_%02d.zip", baseName, fileCount);
+ }
+
+ public void write(InputStream inputStream, String path) throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException,
+ NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+
+ if (countingOutputStream == null) {
+ zipOutputStream = createNewZipOutputStream();
+ }
+ if (bytesWritten >= chunkSize) {
+ switchOutputFile();
+ }
+ LOG.info("Writing file {} to zip", path);
+ zipOutputStream.putNextEntry(new ZipEntry(path));
+ bytesWritten += IOUtils.copyLarge(inputStream, zipOutputStream);
+ zipOutputStream.closeEntry();
+ }
+
+ private void switchOutputFile() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ LOG.info("Switching zip");
+ zipOutputStream.flush();
+ zipOutputStream.close();
+ addZipToResult();
+ fileCount++;
+ zipOutputStream = createNewZipOutputStream();
+ bytesWritten = 0;
+ }
+
+ public void close() throws IOException {
+ LOG.info("Closing zip {}", actualZipFile.getName());
+ zipOutputStream.flush();
+ zipOutputStream.close();
+ addZipToResult();
+ }
+
+ public void writeReadMe() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException,
+ NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+
+ if (zipOutputStream != null) {
+ LOG.info("Skip adding Readme");
+ return;
+ }
+
+ try (InputStream readmeStream = this.getClass().getClassLoader().getResourceAsStream("Readme.txt")) {
+ if (readmeStream == null) {
+ throw new IOException("Cannot open Readme");
+ }
+
+ LOG.info("Writing Readme");
+ write(readmeStream, "Readme.txt");
+ } catch (IOException e) {
+ throw new IOException("Could not write Readme", e);
+ }
+ }
+
+ public ZipResult getZipResult() {
+ return this.zipResult;
+ }
+
+ private void addZipToResult() throws IOException {
+ zipResult.add(actualZipFile.getName(), countingOutputStream.getCount());
+ Files.setPosixFilePermissions(actualZipFile.toPath(), ZIP_FILE_PERMISSIONS);
+ }
+}
diff --git a/src/main/java/illgner/ch/createzip/ZipResult.java b/src/main/java/illgner/ch/createzip/ZipResult.java
new file mode 100755
index 0000000..947bf33
--- /dev/null
+++ b/src/main/java/illgner/ch/createzip/ZipResult.java
@@ -0,0 +1,23 @@
+package illgner.ch.createzip;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ZipResult {
+
+ private final List zipFilenames = new ArrayList<>();
+ private final List zipFileLengths = new ArrayList<>();
+
+ void add(String fileName, long bytesWritten) {
+ zipFilenames.add(fileName);
+ zipFileLengths.add(bytesWritten);
+ }
+
+ public List getFileLengths() {
+ return zipFileLengths;
+ }
+
+ public List getFilenames() {
+ return zipFilenames;
+ }
+}
diff --git a/src/main/java/illgner/ch/encryption/IInputStreamEnhancer.java b/src/main/java/illgner/ch/encryption/IInputStreamEnhancer.java
new file mode 100755
index 0000000..94dd475
--- /dev/null
+++ b/src/main/java/illgner/ch/encryption/IInputStreamEnhancer.java
@@ -0,0 +1,15 @@
+package illgner.ch.encryption;
+
+import javax.crypto.NoSuchPaddingException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+public interface IInputStreamEnhancer {
+
+ InputStream enhance(InputStream inputStream) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException;
+
+}
diff --git a/src/main/java/illgner/ch/encryption/IOutputStreamEnhancer.java b/src/main/java/illgner/ch/encryption/IOutputStreamEnhancer.java
new file mode 100755
index 0000000..d17ad88
--- /dev/null
+++ b/src/main/java/illgner/ch/encryption/IOutputStreamEnhancer.java
@@ -0,0 +1,18 @@
+package illgner.ch.encryption;
+
+import javax.crypto.NoSuchPaddingException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+public interface IOutputStreamEnhancer {
+ /**
+ * This method enhances the given input stream {@link OutputStream} wrapping with the additional functionality (in our case with encryption)
+ * @param outputStream the original stream {@link OutputStream} to be enhanced.
+ * @return A new {@link OutputStream} that wraps the functionality to input stream.
+ */
+ OutputStream enhance(OutputStream outputStream) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException;
+}
diff --git a/src/test/java/ch/illgner/OrderCalculatorTest.java b/src/test/java/ch/illgner/OrderCalculatorTest.java
deleted file mode 100644
index f62acda..0000000
--- a/src/test/java/ch/illgner/OrderCalculatorTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package ch.illgner;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class OrderCalculatorTest {
-
- @Test
- public void testCalculateFinalPrice_NormalScenario() {
- OrderCalculator calculator = new OrderCalculator();
-
- // Szenario: 100€ Basis, 10% Rabatt = 90€.
- // 90€ + 19% MwSt (17.10€) = 107.10€
- double result = calculator.calculateFinalPrice(100.0, 0.10);
-
- assertEquals(107.10, result, "Der Endpreis für 100€ mit 10% Rabatt muss 107.10 sein.");
- }
-
- @Test
- public void testCalculateFinalPrice_Rounding() {
- OrderCalculator calculator = new OrderCalculator();
-
- // Szenario: 10.55€ Basis, 5% Rabatt = 10.0225€
- // 10.0225€ + 19% MwSt = 11.926775€ -> Gerundet: 11.93€
- double result = calculator.calculateFinalPrice(10.55, 0.05);
-
- assertEquals(11.93, result, "Der Preis muss korrekt auf 2 Stellen gerundet werden.");
- }
-}
\ No newline at end of file
diff --git a/src/test/java/illgner/ch/jobs/postboxbulk/service/PostBoxZipFileWriterTest.java b/src/test/java/illgner/ch/jobs/postboxbulk/service/PostBoxZipFileWriterTest.java
new file mode 100755
index 0000000..837ddd2
--- /dev/null
+++ b/src/test/java/illgner/ch/jobs/postboxbulk/service/PostBoxZipFileWriterTest.java
@@ -0,0 +1,306 @@
+package illgner.ch.jobs.postboxbulk.service;
+
+import illgner.ch.createzip.PostBoxZipFileWriter;
+import illgner.ch.createzip.ZipResult;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import javax.crypto.NoSuchPaddingException;
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.util.Objects;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.hamcrest.io.FileMatchers.aFileNamed;
+import static org.hamcrest.io.FileMatchers.anExistingFile;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class PostBoxZipFileWriterTest {
+
+ private static final Path DESTINATION_DIR = Paths.get("./destination");
+ private static final int ONE_BYTE = 1024;
+ private static final int HALF_BYTE = ONE_BYTE / 2;
+ private static final int ZIP_BUFFER_SIZE_IN_BYTE = 2 * ONE_BYTE;
+ private static final int ZIP_CHUNK_SIZE_IN_BYTE = 3 * ONE_BYTE;
+
+ @BeforeAll
+ static void setUp() throws IOException {
+ FileUtils.deleteDirectory(DESTINATION_DIR.toFile());
+ boolean directoryCreated = DESTINATION_DIR.toFile().mkdir();
+ assertTrue(directoryCreated);
+ }
+
+ @AfterAll
+ static void cleanUp() throws IOException {
+ FileUtils.deleteDirectory(DESTINATION_DIR.toFile());
+ boolean directoryCreated = DESTINATION_DIR.toFile().mkdir();
+ assertTrue(directoryCreated);
+ }
+
+ @Test
+ void singleFileSmallerThanChunkSizeToOneZip() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ byte[] fileSimpleContent = RandomStringUtils.randomAlphanumeric(2 * ONE_BYTE).getBytes(StandardCharsets.UTF_8);
+ ZipResult zipResult;
+ String outputFileName = RandomStringUtils.randomAlphanumeric(10);
+ String inputFileName = "singleFileSmallerThanChunkSizeToOneZip.txt";
+ PostBoxZipFileWriter postBoxZipFileWriter = new PostBoxZipFileWriter(
+ DESTINATION_DIR, outputFileName, ZIP_CHUNK_SIZE_IN_BYTE, ZIP_BUFFER_SIZE_IN_BYTE, (OutputStream outputStream) -> outputStream);
+ try (postBoxZipFileWriter) {
+ postBoxZipFileWriter.write(new ByteArrayInputStream(fileSimpleContent), inputFileName);
+ }
+ String expectedZippedFileName = outputFileName + "_01.zip";
+ zipResult = postBoxZipFileWriter.getZipResult();
+ File createdZipFile = DESTINATION_DIR.resolve(expectedZippedFileName).toFile();
+
+ assertThat(zipResult.getFilenames(), hasSize(1));
+ assertThat(zipResult.getFileLengths(), hasSize(1));
+ assertThat(zipResult.getFilenames().get(0), is(equalTo(expectedZippedFileName)));
+ assertThat(zipResult.getFileLengths().get(0), is(equalTo(createdZipFile.length())));
+ assertThat(createdZipFile, is(anExistingFile()));
+ assertThat(createdZipFile, is(aFileNamed(equalTo(expectedZippedFileName))));
+
+ // check zip content
+ try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(createdZipFile))) {
+ assertThat(Objects.requireNonNull(zipInputStream.getNextEntry()).getName(), is(equalTo(inputFileName)));
+ assertThat(IOUtils.toByteArray(zipInputStream), is(equalTo(fileSimpleContent)));
+ }
+ }
+
+ @Test
+ void singleFileGreaterThanChunkSizeToOneZip() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ byte[] fileSimpleContent = RandomStringUtils.randomAlphanumeric(4 * ONE_BYTE).getBytes(StandardCharsets.UTF_8);
+ ZipResult zipResult;
+ String outputFileName = RandomStringUtils.randomAlphanumeric(10);
+ String inputFileName = "singleFileGreaterThanChunkSizeToOneZip.txt";
+ PostBoxZipFileWriter postBoxZipFileWriter = new PostBoxZipFileWriter(
+ DESTINATION_DIR, outputFileName, ZIP_CHUNK_SIZE_IN_BYTE, ZIP_BUFFER_SIZE_IN_BYTE, (OutputStream outputStream) -> outputStream);
+ try (postBoxZipFileWriter) {
+ postBoxZipFileWriter.write(new ByteArrayInputStream(fileSimpleContent), inputFileName);
+ }
+ String expectedZippedFileNamePartOne = outputFileName + "_01.zip";
+ zipResult = postBoxZipFileWriter.getZipResult();
+ assertThat(zipResult.getFilenames(), hasSize(1));
+ assertThat(zipResult.getFileLengths(), hasSize(1));
+ assertTrue(zipResult.getFilenames().contains(expectedZippedFileNamePartOne));
+
+ File createdZipOne = DESTINATION_DIR.resolve(expectedZippedFileNamePartOne).toFile();
+
+ assertThat(createdZipOne, is(anExistingFile()));
+ assertThat(createdZipOne, is(aFileNamed(equalTo(expectedZippedFileNamePartOne))));
+
+ // check zip content
+ extractSplitZipFiles(new File[]{createdZipOne});
+ File extractedFile = new File(DESTINATION_DIR + "/" + inputFileName);
+ byte[] extractedFileBytes = IOUtils.toByteArray(new FileInputStream(extractedFile));
+ assertThat(extractedFileBytes, is(equalTo(fileSimpleContent)));
+ }
+
+ @Test
+ void multipleFileSmallerThanChunkSizeToSingleZip() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ byte[] firstFile = RandomStringUtils.randomAlphanumeric(ONE_BYTE).getBytes(StandardCharsets.UTF_8);
+ byte[] secondFile = "uts öäü ÖÄÜ ß".getBytes(StandardCharsets.UTF_8);
+
+ String outputFileName = RandomStringUtils.randomAlphanumeric(10);
+ String inputFileNameOne = "multipleFileSmallerThanChunkSizeToSingleZip1.txt";
+ String inputFileNameTwo = "multipleFileSmallerThanChunkSizeToSingleZip2.txt";
+
+ ZipResult zipResult;
+ PostBoxZipFileWriter postBoxZipFileWriter = new PostBoxZipFileWriter(
+ DESTINATION_DIR, outputFileName, ZIP_CHUNK_SIZE_IN_BYTE, ZIP_BUFFER_SIZE_IN_BYTE, (OutputStream outputStream) -> outputStream);
+ try (postBoxZipFileWriter) {
+ postBoxZipFileWriter.write(new ByteArrayInputStream(firstFile), inputFileNameOne);
+ postBoxZipFileWriter.write(new ByteArrayInputStream(secondFile), inputFileNameTwo);
+ }
+
+ zipResult = postBoxZipFileWriter.getZipResult();
+ String expectedZippedFileNamePartOne = outputFileName + "_01.zip";
+ File createdZip = DESTINATION_DIR.resolve(expectedZippedFileNamePartOne).toFile();
+
+ assertThat(zipResult.getFilenames(), hasSize(1));
+ assertThat(zipResult.getFileLengths(), hasSize(1));
+ assertThat(createdZip, is(anExistingFile()));
+ assertThat(createdZip, is(aFileNamed(equalTo(expectedZippedFileNamePartOne))));
+ assertThat(zipResult.getFilenames().get(0), is(equalTo(expectedZippedFileNamePartOne)));
+ assertThat(zipResult.getFileLengths().get(0), is(equalTo(createdZip.length())));
+
+ // check zip content
+ try (ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(createdZip))) {
+ assertThat(Objects.requireNonNull(zipInputStream.getNextEntry()).getName(), is(equalTo(inputFileNameOne)));
+ assertThat(IOUtils.toByteArray(zipInputStream), is(equalTo(firstFile)));
+ assertThat(zipInputStream.getNextEntry().getName(), is(equalTo(inputFileNameTwo)));
+ assertThat(IOUtils.toByteArray(zipInputStream), is(equalTo(secondFile)));
+ }
+ }
+
+ @Test
+ void multipleFilesTogetherGreaterThanChunkSizeToSingleZip() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ String text1 = RandomStringUtils.randomAlphanumeric(2 * ONE_BYTE + HALF_BYTE);
+ String text2 = RandomStringUtils.randomAlphanumeric(2 * ONE_BYTE + HALF_BYTE);
+ byte[] firstFile = text1.getBytes(StandardCharsets.UTF_8);
+ byte[] secondFile = text2.getBytes(StandardCharsets.UTF_8);
+
+ String outputFileName = RandomStringUtils.randomAlphanumeric(10);
+ String inputFileNameOne = "multipleFilesTogetherGreaterThanChunkSizeToSingleZip1.txt";
+ String inputFileNameTwo = "multipleFilesTogetherGreaterThanChunkSizeToSingleZip2.txt";
+
+ ZipResult zipResult;
+ PostBoxZipFileWriter postBoxZipFileWriter = new PostBoxZipFileWriter(
+ DESTINATION_DIR, outputFileName, ZIP_CHUNK_SIZE_IN_BYTE, ZIP_BUFFER_SIZE_IN_BYTE, (OutputStream outputStream) -> outputStream);
+ try (postBoxZipFileWriter) {
+ postBoxZipFileWriter.write(new ByteArrayInputStream(firstFile), inputFileNameOne);
+ postBoxZipFileWriter.write(new ByteArrayInputStream(secondFile), inputFileNameTwo);
+ } finally {
+ zipResult = postBoxZipFileWriter.getZipResult();
+ }
+
+ String expectedZippedFileNamePartOne = outputFileName + "_01.zip";
+
+ assertThat(zipResult.getFilenames(), hasSize(1));
+ assertThat(zipResult.getFileLengths(), hasSize(1));
+ assertTrue(zipResult.getFilenames().contains(expectedZippedFileNamePartOne));
+
+ File createdZipOne = DESTINATION_DIR.resolve(expectedZippedFileNamePartOne).toFile();
+
+ assertThat(createdZipOne, is(anExistingFile()));
+ assertThat(createdZipOne, is(aFileNamed(equalTo(expectedZippedFileNamePartOne))));
+
+ // check zip content
+ extractSplitZipFiles(new File[]{createdZipOne});
+ File extractedFile = new File(DESTINATION_DIR + "/" + inputFileNameOne);
+ byte[] extractedFileBytes = IOUtils.toByteArray(new FileInputStream(extractedFile));
+ assertThat(extractedFileBytes, is(equalTo(firstFile)));
+
+ extractedFile = new File(DESTINATION_DIR + "/" + inputFileNameTwo);
+ extractedFileBytes = IOUtils.toByteArray(new FileInputStream(extractedFile));
+ assertThat(extractedFileBytes, is(equalTo(secondFile)));
+ }
+
+ @Test
+ void multipleBigFilesToMultipleZip() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ ZipResult zipResult;
+ int bigChuckSize = ONE_BYTE * 50;
+ int bigBufferSize = ONE_BYTE * 10;
+ // First two files will be zipped together and third file will be zipped in the second file.
+ byte[] firstFile = RandomStringUtils.randomAlphanumeric(40 * ONE_BYTE).getBytes(StandardCharsets.UTF_8);
+ byte[] secondFile = RandomStringUtils.randomAlphanumeric(70 * ONE_BYTE).getBytes(StandardCharsets.UTF_8);
+ byte[] thirdFile = RandomStringUtils.randomAlphanumeric(10 * ONE_BYTE).getBytes(StandardCharsets.UTF_8);
+ String[] originalFileNames = {"multipleBigFilesToMultipleZip1.txt", "multipleBigFilesToMultipleZip2.txt", "multipleBigFilesToMultipleZip3.txt"};
+ String outputFileName = RandomStringUtils.randomAlphanumeric(10);
+
+ PostBoxZipFileWriter postBoxZipFileWriter = new PostBoxZipFileWriter(
+ DESTINATION_DIR, outputFileName, bigChuckSize, bigBufferSize, (OutputStream outputStream) -> outputStream);
+ try (postBoxZipFileWriter) {
+ postBoxZipFileWriter.write(new ByteArrayInputStream(firstFile), originalFileNames[0]);
+ postBoxZipFileWriter.write(new ByteArrayInputStream(secondFile), originalFileNames[1]);
+ postBoxZipFileWriter.write(new ByteArrayInputStream(thirdFile), originalFileNames[2]);
+ } finally {
+ zipResult = postBoxZipFileWriter.getZipResult();
+ }
+
+ String expectedZippedFileNamePartOne = outputFileName + "_01.zip";
+ String expectedZippedFileNamePartTwo = outputFileName + "_02.zip";
+
+ assertThat(zipResult.getFilenames(), hasSize(2));
+ assertThat(zipResult.getFileLengths(), hasSize(2));
+ assertTrue(zipResult.getFilenames().contains(expectedZippedFileNamePartOne));
+ assertTrue(zipResult.getFilenames().contains(expectedZippedFileNamePartTwo));
+
+ File part1 = DESTINATION_DIR.resolve(expectedZippedFileNamePartOne).toFile();
+ File part2 = DESTINATION_DIR.resolve(expectedZippedFileNamePartTwo).toFile();
+
+ File[] zippedFiles = {part1, part2};
+
+ assertThat(part1, is(anExistingFile()));
+ assertThat(part1, is(aFileNamed(equalTo(expectedZippedFileNamePartOne))));
+ assertThat(part2, is(anExistingFile()));
+ assertThat(part2, is(aFileNamed(equalTo(expectedZippedFileNamePartTwo))));
+
+ extractSplitZipFiles(zippedFiles);
+
+ // check zip content
+ File extractedFile = new File(DESTINATION_DIR + "/" + originalFileNames[0]);
+ byte[] extractedFileBytes = IOUtils.toByteArray(new FileInputStream(extractedFile));
+ assertThat(extractedFileBytes, is(equalTo(firstFile)));
+
+ extractedFile = new File(DESTINATION_DIR + "/" + originalFileNames[1]);
+ extractedFileBytes = IOUtils.toByteArray(new FileInputStream(extractedFile));
+ assertThat(extractedFileBytes, is(equalTo(secondFile)));
+
+ extractedFile = new File(DESTINATION_DIR + "/" + originalFileNames[2]);
+ extractedFileBytes = IOUtils.toByteArray(new FileInputStream(extractedFile));
+ assertThat(extractedFileBytes, is(equalTo(thirdFile)));
+ }
+
+ @Test
+ void createBigZip() throws IOException, InvalidAlgorithmParameterException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ // first implementation crashed with an OutOfMemoryException while copying an 50MB Stream,
+ // so we added a test for this
+
+ try (PostBoxZipFileWriter zipFileWriter = new PostBoxZipFileWriter(
+ DESTINATION_DIR, "testBigMulti", 100 * 1000 * 1000, ZIP_BUFFER_SIZE_IN_BYTE, (OutputStream outputStream) -> outputStream)) {
+ for (int i = 0; i < 10; i++) {
+ byte[] content = RandomStringUtils.randomAlphanumeric(50 * 1000 * 1000).getBytes();
+ zipFileWriter.write(new ByteArrayInputStream(content), "test" + i);
+ }
+ }
+ }
+
+ @Test
+ void addCompressibleFiles() throws InvalidAlgorithmParameterException, NoSuchPaddingException, IOException, NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException {
+ byte[] content1 = new byte[10000];
+ byte[] content2 = new byte[10000];
+ byte[] content3 = new byte[10000];
+
+ String outputFileName = RandomStringUtils.randomAlphanumeric(10);
+
+ ZipResult zipResult;
+ try (PostBoxZipFileWriter postBoxZipFileWriter = new PostBoxZipFileWriter(DESTINATION_DIR, outputFileName, 1000, 2048, (OutputStream outputStream) -> outputStream)) {
+ postBoxZipFileWriter.write(new ByteArrayInputStream(content1), "file1");
+ postBoxZipFileWriter.write(new ByteArrayInputStream(content2), "file2");
+ postBoxZipFileWriter.write(new ByteArrayInputStream(content3), "file3");
+ zipResult = postBoxZipFileWriter.getZipResult();
+ }
+ assertThat("compressible files only containing zeroes should be compressed into one file", zipResult.getFileLengths().size(), is(1));
+ }
+
+ public static void extractSplitZipFiles(File[] zipFiles) throws IOException {
+ byte[] buffer = new byte[ZIP_BUFFER_SIZE_IN_BYTE];
+ for (File zipFilePath : zipFiles) {
+ try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePath))) {
+ ZipEntry zipEntry = zis.getNextEntry();
+
+ while (zipEntry != null) {
+ File newFile = new File(DESTINATION_DIR + "/" + zipEntry.getName());
+ new File(newFile.getParent()).mkdirs(); // Create directories for nested files
+ try (FileOutputStream fos = new FileOutputStream(newFile, true)) {
+ int len;
+ while ((len = zis.read(buffer)) > 0) {
+ fos.write(buffer, 0, len);
+ }
+ }
+ zipEntry = zis.getNextEntry();
+ }
+ }
+ }
+ }
+}