DEV Community

Masui Masanori
Masui Masanori

Posted on

1

[Micronaut] Generating and downloading files as ZIP or JSON

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

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

sample.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
Enter fullscreen mode Exit fullscreen mode

ResultCsvGenerator.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");
    }
}
Enter fullscreen mode Exit fullscreen mode

ResultCsv.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;
    }
}
Enter fullscreen mode Exit fullscreen mode

Returning a JSON file when ZIP file generation fails

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");
    }
}
Enter fullscreen mode Exit fullscreen mode

FileController.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");
    }
...
Enter fullscreen mode Exit fullscreen mode

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs