[Micronaut] Generating and downloading files as ZIP or JSON
Tags: java, micronaut
Published: 2024-04-07 13:36:24

## Intro
In this time, I will try generating and downloading files as ZIP.
If any error occurs during generation, generate and download a JSON file.

## Generating a ZIP archive
First, I will load a CSV file and split it into multiple CSV files.

#### FileController.java
```java
package jp.masanori;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.server.types.files.SystemFile;

@Controller("/files")
public class FileController {
    private final ResultCsvGenerator csvGen;
    public FileController(ResultCsvGenerator csvGen) {
        this.csvGen = csvGen;
    }    
    @Get(uri="zip")
    public HttpResponse<SystemFile> getZip() {
        SystemFile result = csvGen.generate();
        return HttpResponse.ok(result)
            .contentType("application/zip");
    }
}
```

#### sample.csv
```csv
--12345678
Content-Disposition: mixed; name="sample"; filename="sample.csv";
Content-Type: text/csv
id,名前,メッセージ,タグ
0,サンプル,こんにちは,debug
1,サンプル2,世界,world
2,サンプル3,こんばんは,debug
3,サンプル4,おはよう,morning

--abcdefgh
Content-Disposition: mixed; name="example"; filename="サンプル.csv";
Content-Type: text/csv
id,名前,メッセージ,タグ
4,サンプル,こんにちは,debug
5,サンプル2,世界,world
6,サンプル3,こんばんは,debug
7,サンプル4,おはよう,morning
```

#### ResultCsvGenerator.java
```java
package jp.masanori;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.micronaut.http.server.types.files.SystemFile;
import jakarta.inject.Singleton;
import jp.masanori.dto.ResultCsv;

@Singleton
public class ResultCsvGenerator {
    private final Logger logger;
    private final Pattern cdPattern;
    private final Pattern beiPattern;

    public ResultCsvGenerator() {
        this.logger = LoggerFactory.getLogger(ResultCsvGenerator.class);
        this.cdPattern = Pattern.compile("(Content-Disposition).+(filename=)\"(.+)\"");
    }
    public SystemFile generate() {
        try {
            byte[] fileData = Files
                .readAllBytes(Paths.get("C:/Users/example/sample.csv"));
            List<ResultCsv> csvValues = new ArrayList<>();
            String name = "";
            String value = "";
            String[] splitted = new String(fileData, "SJIS").split("\n");
            for (int i = 0; i < splitted.length; i++) {
                // split the value by "--"
                if (splitted[i].startsWith("--")) {
                    if (name.length() <= 0) {
                        continue;
                    }
                    ResultCsv result = new ResultCsv(name, value);
                    csvValues.add(result);
                    name = "";
                    value = "";
                    continue;
                }
                // get file names from "Content-Disposition" rows
                Matcher cdMatcher = cdPattern.matcher(splitted[i]);
                if (cdMatcher.find()) {
                    name = cdMatcher.group(cdMatcher.groupCount());
                    continue;
                }
                // skip "Content-Type" rows
                if (splitted[i].startsWith("Content-Type")) {
                    continue;
                }
                // add other rows as values
                if (name.length() > 0) {
                    value += splitted[i];
                    value += "\n";
                }
            }
            if(name.length() > 0) {
                ResultCsv result = new ResultCsv(name, value);
                csvValues.add(result);
            }
            return generateZipFile(csvValues);

        } catch(IOException ex) {
            logger.error("Failed generating a ZIP file", ex);
            return null;
        }
    }
    private SystemFile generateZipFile(List<ResultCsv> values) throws IOException {
        File tmpFile = File.createTempFile("result", "zip");
        try (ZipOutputStream zip = new ZipOutputStream(new BufferedOutputStream(
                new FileOutputStream(tmpFile)))) {
            for (ResultCsv csv : values) {
                // add CSV files into ZIP archive
                String fileName = csv.getName();
                if (fileName == null) {
                    continue;
                }
                zip.putNextEntry(new ZipEntry(fileName));
                byte[] value = csv.getValue().getBytes("SJIS");
                zip.write(value, 0, value.length);
            }
        }
        // set ZIP file name
        return new SystemFile(tmpFile).attach("result.zip");
    }
}
```

#### ResultCsv.java
```java
package jp.masanori.dto;

public class ResultCsv {
    private String name;
    private String value;
    public String getName() {
        return name;
    }
    public String getValue() {
        return value;
    }
    public ResultCsv(String name, String value) {
        this.name = name;
        this.value = value;
    }
}
```

* [SystemFile - micronaut 4.4.1 API](https://docs.micronaut.io/latest/api/io/micronaut/http/server/types/files/SystemFile.html)
* [ZipOutputStream - Java Platform SE 8](https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipOutputStream.html)

## Returning a JSON file when ZIP file generation fails

```java
package jp.masanori;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.micronaut.http.server.types.files.SystemFile;

public class ActionResult {
    private boolean succeeded;
    private String errorMessage;
    public boolean isSucceeded() {
        return succeeded;
    }
    public String getErrorMessage() {
        return errorMessage;
    }
    public static ActionResult getSucceeded() {
        ActionResult result = new ActionResult();
        result.succeeded = true;
        return result;
    }
    public static ActionResult getFailed(String errorMessage) {
        ActionResult result = new ActionResult();
        result.errorMessage = errorMessage;
        return result;
    }
    public static String getFailedJson(String errorMessage) throws JsonProcessingException {
        ActionResult result = ActionResult.getFailed(errorMessage);
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(result);
    }
    public static SystemFile getFailedJsonFile(String errorMessage) throws IOException {
        String resultJson = ActionResult.getFailedJson(errorMessage);
        File tmpFile = File.createTempFile("result", "json");
        try(FileOutputStream stream = new FileOutputStream(tmpFile)) {
            stream.write(resultJson.getBytes());
        }
        return new SystemFile(tmpFile).attach("result.json");
    }
}
```

#### FileController.java
```java
...
    @Get(uri="zip")
    public HttpResponse<SystemFile> getZip() {
        SystemFile result = csvGen.generate();
        if(result == null) {
            try {
                return HttpResponse.ok(ActionResult.getFailedJsonFile("Failed generating a Zip file"));
            } catch(IOException ex) {
                return HttpResponse.serverError();
            }
        }
        return HttpResponse.ok(result)
            .contentType("application/zip");
    }
...
```