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

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

19 May 2025 02:23PM UTC coverage: 90.418% (+0.2%) from 90.169%
#431

Pull #258

github

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

283 of 315 new or added lines in 25 files covered. (89.84%)

1 existing line in 1 file now uncovered.

2010 of 2223 relevant lines covered (90.42%)

0.9 hits per line

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

88.41
/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.Set;
15
import java.util.UUID;
16

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

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

34
    private static final Logger logger = LoggerFactory.getLogger(WriteZipStreamStrategy.class);
1✔
35
    public static final String TMP_DIR = "./.tmp/ro-crate-java/writer-zip-stream-strategy/";
36

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

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

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

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

70
    @Override
71
    public void save(Crate crate, OutputStream destination) throws IOException {
72
        String innerFolderName = "";
1✔
73
        if (this.createRootSubdir) {
1✔
74
            innerFolderName = FileSystemUtil.filterExtensionsFromFileName(
1✔
75
                    this.rootSubdirName,
76
                    Set.of("ELN", "ZIP"));
1✔
77
            innerFolderName = FileSystemUtil.ensureTrailingSlash(innerFolderName);
1✔
78
        }
79
        try (ZipOutputStream zipFile = new ZipOutputStream(destination)) {
1✔
80
            saveMetadataJson(crate, zipFile, innerFolderName);
1✔
81
            saveDataEntities(crate, zipFile, innerFolderName);
1✔
82
            savePreview(crate, zipFile, innerFolderName);
1✔
83
        }
84
    }
1✔
85

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

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

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

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

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

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

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