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

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

19 May 2025 01:23PM UTC coverage: 90.495% (+0.3%) from 90.169%
#430

Pull #258

github

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

276 of 305 new or added lines in 21 files covered. (90.49%)

1 existing line in 1 file now uncovered.

2009 of 2220 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/WriteZipStreamStrategy.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 WriteZipStreamStrategy implements
1✔
30
        GenericWriterStrategy<OutputStream>,
31
        ElnFormatWriter<OutputStream> {
32

33
    private static final Logger logger = LoggerFactory.getLogger(WriteZipStreamStrategy.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

41
    /**
42
     * In streams, we do not have a file name yet (or do not know it),
43
     * so we need to set a default name for the root subdirectory.
44
     */
45
    protected String rootSubdirName = "content";
1✔
46

47
    @Override
48
    public ElnFormatWriter<OutputStream> usingElnStyle() {
49
        this.createRootSubdir = true;
1✔
50
        return this;
1✔
51
    }
52

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

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

90
    private void saveDataEntities(Crate crate, ZipOutputStream zipStream, String prefix) throws IOException {
91
        for (DataEntity dataEntity : crate.getAllDataEntities()) {
1✔
92
            this.saveToStream(dataEntity, zipStream, prefix);
1✔
93
        }
1✔
94
    }
1✔
95

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

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

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

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

155
    private void saveToStream(DataEntity entity, ZipOutputStream zipStream, String prefix) throws IOException {
156
        if (entity == null) {
1✔
NEW
157
            return;
×
158
        }
159

160
        boolean isDirectory = entity.getPath().toFile().isDirectory();
1✔
161
        if (isDirectory) {
1✔
162
            ZipUtil.addFolderToZipStream(
1✔
163
                    zipStream,
164
                    entity.getPath().toAbsolutePath().toString(),
1✔
165
                    prefix + entity.getId());
1✔
166
        } else {
167
            ZipUtil.addFileToZipStream(
1✔
168
                    zipStream,
169
                    entity.getPath().toFile(),
1✔
170
                    prefix + entity.getId());
1✔
171
        }
172
    }
1✔
173
}
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