• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kit-data-manager / ro-crate-java / #429

19 May 2025 12:15PM UTC coverage: 90.5% (+0.3%) from 90.169%
#429

Pull #258

github

web-flow
Merge e4d91d01c into 810d1995c
Pull Request #258: Support .ELN-style crates in all zip readers and writers

224 of 245 new or added lines in 15 files covered. (91.43%)

3 existing lines in 3 files now uncovered.

2010 of 2221 relevant lines covered (90.5%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.04
/src/main/java/edu/kit/datamanager/ro_crate/writer/ZipStreamStrategy.java
1
package edu.kit.datamanager.ro_crate.writer;
2

3
import com.fasterxml.jackson.databind.JsonNode;
4
import com.fasterxml.jackson.databind.ObjectMapper;
5

6
import edu.kit.datamanager.ro_crate.Crate;
7
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
8
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
9

10
import java.io.*;
11
import java.nio.charset.StandardCharsets;
12
import java.nio.file.Path;
13
import java.util.Optional;
14
import java.util.UUID;
15
import java.util.regex.Matcher;
16

17
import edu.kit.datamanager.ro_crate.preview.CratePreview;
18
import edu.kit.datamanager.ro_crate.util.ZipUtil;
19
import net.lingala.zip4j.io.outputstream.ZipOutputStream;
20
import net.lingala.zip4j.model.ZipParameters;
21
import org.apache.commons.io.FileUtils;
22
import org.slf4j.Logger;
23
import org.slf4j.LoggerFactory;
24

25
/**
26
 * Implementation of the writing strategy to provide a way of writing crates to
27
 * a zip archive.
28
 */
29
public class ZipStreamStrategy implements
1✔
30
        GenericWriterStrategy<OutputStream>,
31
        ElnFormatWriter<OutputStream> {
32

33
    private static final Logger logger = LoggerFactory.getLogger(ZipStreamStrategy.class);
1✔
34

35
    /**
36
     * Defines if the zip file will directly contain the crate,
37
     * or if it will contain a subdirectory with the crate.
38
     */
39
    protected boolean createRootSubdir = false;
1✔
40
    protected String rootSubdirName = "content";
1✔
41

42
    @Override
43
    public ElnFormatWriter<OutputStream> usingElnStyle() {
44
        this.createRootSubdir = true;
1✔
45
        return this;
1✔
46
    }
47

48
    /**
49
     * Sets the name of a root subdirectory in the zip file.
50
     * Implicitly also enables the creation of a root subdirectory.
51
     * If used for ELN files, note the subdirectory name should be the same as the zip
52
     * files name.
53
     *
54
     * @param name the name of the subdirectory
55
     * @return this instance of ZipStreamStrategy
56
     */
57
    public ZipStreamStrategy setSubdirectoryName(String name) {
NEW
58
        this.rootSubdirName = name;
×
NEW
59
        this.createRootSubdir = true;
×
NEW
60
        return this;
×
61
    }
62

63
    @Override
64
    public void save(Crate crate, OutputStream destination) throws IOException {
65
        String innerFolderName = "";
1✔
66
        if (this.createRootSubdir) {
1✔
67
            String dot = Matcher.quoteReplacement(".");
1✔
68
            String end = Matcher.quoteReplacement("$");
1✔
69
            innerFolderName = this.rootSubdirName
1✔
70
                    // remove .zip or .eln from the end of the file name
71
                    // (?i) removes case sensitivity
72
                    .replaceFirst("(?i)" + dot + "zip" + end, "")
1✔
73
                    .replaceFirst("(?i)" + dot + "eln" + end, "");
1✔
74
            if (!innerFolderName.endsWith("/")) {
1✔
75
                innerFolderName += "/";
1✔
76
            }
77
        }
78
        try (ZipOutputStream zipFile = new ZipOutputStream(destination)) {
1✔
79
            saveMetadataJson(crate, zipFile, innerFolderName);
1✔
80
            saveDataEntities(crate, zipFile, innerFolderName);
1✔
81
            savePreview(crate, zipFile, innerFolderName);
1✔
82
        }
83
    }
1✔
84

85
    private void saveDataEntities(Crate crate, ZipOutputStream zipStream, String prefix) throws IOException {
86
        for (DataEntity dataEntity : crate.getAllDataEntities()) {
1✔
87
            this.saveToStream(dataEntity, zipStream, prefix);
1✔
88
        }
1✔
89
    }
1✔
90

91
    private void saveMetadataJson(Crate crate, ZipOutputStream zipStream, String prefix) throws IOException {
92
        // write the metadata.json file
93
        ZipParameters zipParameters = new ZipParameters();
1✔
94
        zipParameters.setFileNameInZip(prefix + "ro-crate-metadata.json");
1✔
95
        ObjectMapper objectMapper = MyObjectMapper.getMapper();
1✔
96
        // we create an JsonNode only to have the file written pretty
97
        JsonNode node = objectMapper.readTree(crate.getJsonMetadata());
1✔
98
        String str = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(node);
1✔
99
        // write the ro-crate-metadata
100

101
        byte[] buff = new byte[4096];
1✔
102
        int readLen;
103
        zipStream.putNextEntry(zipParameters);
1✔
104
        try (InputStream inputStream = new ByteArrayInputStream(str.getBytes(StandardCharsets.UTF_8))) {
1✔
105
            while ((readLen = inputStream.read(buff)) != -1) {
1✔
106
                zipStream.write(buff, 0, readLen);
1✔
107
            }
108
        }
109
        zipStream.closeEntry();
1✔
110
    }
1✔
111

112
    private void savePreview(Crate crate, ZipOutputStream zipStream, String prefix) throws IOException {
113
        Optional<CratePreview> preview = Optional.ofNullable(crate.getPreview());
1✔
114
        if (preview.isEmpty()) {
1✔
115
            return;
1✔
116
        }
117
        final String ID = UUID.randomUUID().toString();
1✔
118
        File tmpPreviewFolder = Path.of("./.tmp/ro-crate-java/writer-zipStrategy/")
1✔
119
                .resolve(ID)
1✔
120
                .toFile();
1✔
121
        FileUtils.forceMkdir(tmpPreviewFolder);
1✔
122
        FileUtils.forceDeleteOnExit(tmpPreviewFolder);
1✔
123

124
        preview.get().generate(crate, tmpPreviewFolder);
1✔
125
        String[] paths = tmpPreviewFolder.list();
1✔
126
        if (paths == null) {
1✔
NEW
127
            throw new IOException("No files found in temporary folder");
×
128
        }
129
        for (String path : paths) {
1✔
130
            File file = tmpPreviewFolder.toPath().resolve(path).toFile();
1✔
131
            if (file.isDirectory()) {
1✔
NEW
132
                ZipUtil.addFolderToZipStream(
×
133
                        zipStream,
134
                        file,
135
                        prefix + path);
136
            } else {
137
                ZipUtil.addFileToZipStream(
1✔
138
                        zipStream,
139
                        file,
140
                        prefix + path);
141
            }
142
        }
143
        try {
144
            FileUtils.forceDelete(tmpPreviewFolder);
1✔
UNCOV
145
        } catch (IOException e) {
×
NEW
146
            logger.error("Could not delete temporary preview folder: {}", tmpPreviewFolder);
×
147
        }
1✔
148
    }
1✔
149

150
    private void saveToStream(DataEntity entity, ZipOutputStream zipStream, String prefix) throws IOException {
151
        if (entity == null) {
1✔
NEW
152
            return;
×
153
        }
154

155
        boolean isDirectory = entity.getPath().toFile().isDirectory();
1✔
156
        if (isDirectory) {
1✔
157
            ZipUtil.addFolderToZipStream(
1✔
158
                    zipStream,
159
                    entity.getPath().toAbsolutePath().toString(),
1✔
160
                    prefix + entity.getId());
1✔
161
        } else {
162
            ZipUtil.addFileToZipStream(
1✔
163
                    zipStream,
164
                    entity.getPath().toFile(),
1✔
165
                    prefix + entity.getId());
1✔
166
        }
167
    }
1✔
168
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc