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

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

23 Apr 2025 02:53PM UTC coverage: 86.477% (+0.5%) from 85.989%
#368

Pull #247

github

web-flow
Merge 71e7caa5e into 0ab3f3ca9
Pull Request #247: Generalize reading and writing crates

251 of 278 new or added lines in 16 files covered. (90.29%)

21 existing lines in 5 files now uncovered.

1880 of 2174 relevant lines covered (86.48%)

0.86 hits per line

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

93.6
/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java
1
package edu.kit.datamanager.ro_crate;
2

3
import com.fasterxml.jackson.core.TreeNode;
4
import com.fasterxml.jackson.databind.JsonNode;
5
import com.fasterxml.jackson.databind.ObjectMapper;
6
import com.fasterxml.jackson.databind.node.ObjectNode;
7

8
import edu.kit.datamanager.ro_crate.context.CrateMetadataContext;
9
import edu.kit.datamanager.ro_crate.context.RoCrateMetadataContext;
10
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
11
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
12
import edu.kit.datamanager.ro_crate.entities.contextual.JsonDescriptor;
13
import edu.kit.datamanager.ro_crate.entities.contextual.OrganizationEntity;
14
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
15
import edu.kit.datamanager.ro_crate.entities.data.DataEntity.DataEntityBuilder;
16
import edu.kit.datamanager.ro_crate.entities.data.FileEntity;
17
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
18
import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite;
19
import edu.kit.datamanager.ro_crate.externalproviders.organizationprovider.RorProvider;
20
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
21
import edu.kit.datamanager.ro_crate.payload.CratePayload;
22
import edu.kit.datamanager.ro_crate.payload.RoCratePayload;
23
import edu.kit.datamanager.ro_crate.preview.CratePreview;
24
import edu.kit.datamanager.ro_crate.preview.CustomPreview;
25
import edu.kit.datamanager.ro_crate.special.CrateVersion;
26
import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions;
27
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
28
import edu.kit.datamanager.ro_crate.validation.Validator;
29
import edu.kit.datamanager.ro_crate.writer.FolderWriter;
30
import edu.kit.datamanager.ro_crate.writer.RoCrateWriter;
31

32
import java.io.File;
33
import java.net.URI;
34
import java.nio.file.Paths;
35
import java.util.*;
36
import java.util.stream.Collectors;
37
import java.util.stream.StreamSupport;
38

39
/**
40
 * The class that represents a single ROCrate.
41
 * <p>
42
 * To build or modify it, use a instance of {@link RoCrateBuilder}. In the case
43
 * features of RO-Crate DRAFT specifications are needed, refer to
44
 * {@link BuilderWithDraftFeatures} and its documentation.
45
 *
46
 * @author Nikola Tzotchev on 6.2.2022 г.
47
 * @version 1
48
 */
49
public class RoCrate implements Crate {
50

51
    protected final CratePayload roCratePayload;
52
    protected CrateMetadataContext metadataContext;
53
    protected CratePreview roCratePreview;
54
    protected RootDataEntity rootDataEntity;
55
    protected ContextualEntity jsonDescriptor;
56

57
    protected Collection<File> untrackedFiles;
58

59
    @Override
60
    public CratePreview getPreview() {
61
        return this.roCratePreview;
1✔
62
    }
63

64
    public void setRoCratePreview(CratePreview preview) {
65
        this.roCratePreview = preview;
1✔
66
    }
1✔
67

68
    @Override
69
    public void setMetadataContext(CrateMetadataContext metadataContext) {
70
        this.metadataContext = metadataContext;
1✔
71
    }
1✔
72

73
    @Override
74
    public String getMetadataContextValueOf(String key) {
75
        return this.metadataContext.getValueOf(key);
1✔
76
    }
77

78
    @Override
79
    public Set<String> getMetadataContextKeys() {
80
        return this.metadataContext.getKeys();
1✔
81
    }
82

83
    @Override
84
    public Map<String, String> getMetadataContextPairs() {
85
        return this.metadataContext.getPairs();
1✔
86
    }
87

88
    public ContextualEntity getJsonDescriptor() {
89
        return jsonDescriptor;
1✔
90
    }
91

92
    @Override
93
    public void setJsonDescriptor(ContextualEntity jsonDescriptor) {
94
        this.jsonDescriptor = jsonDescriptor;
1✔
95
    }
1✔
96
    
97
    @Override
98
    public RootDataEntity getRootDataEntity() {
99
        return rootDataEntity;
1✔
100
    }
101
    
102
    @Override
103
    public void setRootDataEntity(RootDataEntity rootDataEntity) {
104
        this.rootDataEntity = rootDataEntity;
1✔
105
    }
1✔
106

107
    /**
108
     * Default constructor for creation of an empty crate.
109
     */
110
    public RoCrate() {
1✔
111
        this.roCratePayload = new RoCratePayload();
1✔
112
        this.untrackedFiles = new HashSet<>();
1✔
113
        this.metadataContext = new RoCrateMetadataContext();
1✔
114
        rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
1✔
115
                .build();
1✔
116
        jsonDescriptor = new JsonDescriptor();
1✔
117
    }
1✔
118

119
    /**
120
     * A constructor for creating the crate using a Crate builder for easier
121
     * creation.
122
     *
123
     * @param roCrateBuilder the builder to use.
124
     */
125
    public RoCrate(RoCrateBuilder roCrateBuilder) {
1✔
126
        this.roCratePayload = roCrateBuilder.payload;
1✔
127
        this.metadataContext = roCrateBuilder.metadataContext;
1✔
128
        this.roCratePreview = roCrateBuilder.preview;
1✔
129
        this.rootDataEntity = roCrateBuilder.rootDataEntity;
1✔
130
        this.jsonDescriptor = roCrateBuilder.descriptorBuilder.build();
1✔
131
        this.untrackedFiles = roCrateBuilder.untrackedFiles;
1✔
132
        Validator defaultValidation = new Validator(new JsonSchemaValidation());
1✔
133
        defaultValidation.validate(this);
1✔
134
    }
1✔
135

136
    @Override
137
    public Optional<CrateVersion> getVersion() {
138
        JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
1✔
139
        if (conformsTo.isArray()) {
1✔
140
            return StreamSupport.stream(conformsTo.spliterator(), false)
1✔
141
                    .filter(TreeNode::isObject)
1✔
142
                    .map(obj -> obj.path("@id").asText())
1✔
143
                    .map(CrateVersion::fromSpecUri)
1✔
144
                    .filter(Optional::isPresent)
1✔
145
                    .map(Optional::get)
1✔
146
                    .findFirst();
1✔
147
        } else if (conformsTo.isObject()) {
1✔
148
            return CrateVersion.fromSpecUri(conformsTo.get("@id").asText());
1✔
149
        } else {
UNCOV
150
            return Optional.empty();
×
151
        }
152
    }
153

154
    @Override
155
    public Collection<String> getProfiles() {
156
        JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
1✔
157
        if (conformsTo.isArray()) {
1✔
158
            return StreamSupport.stream(conformsTo.spliterator(), false)
1✔
159
                    .filter(TreeNode::isObject)
1✔
160
                    .map(obj -> obj.path("@id").asText())
1✔
161
                    .collect(Collectors.toSet());
1✔
162
        } else {
163
            return Collections.emptySet();
1✔
164
        }
165
    }
166

167
    @Override
168
    public String getJsonMetadata() {
169
        ObjectMapper objectMapper = MyObjectMapper.getMapper();
1✔
170
        ObjectNode node = objectMapper.createObjectNode();
1✔
171

172
        node.setAll(this.metadataContext.getContextJsonEntity());
1✔
173

174
        var graph = objectMapper.createArrayNode();
1✔
175
        ObjectNode root = objectMapper.convertValue(this.rootDataEntity, ObjectNode.class);
1✔
176
        graph.add(root);
1✔
177

178
        graph.add(objectMapper.convertValue(this.jsonDescriptor, JsonNode.class));
1✔
179
        if (this.roCratePayload != null && this.roCratePayload.getEntitiesMetadata() != null) {
1✔
180
            graph.addAll(this.roCratePayload.getEntitiesMetadata());
1✔
181
        }
182
        node.set("@graph", graph);
1✔
183
        return node.toString();
1✔
184
    }
185

186
    @Override
187
    public DataEntity getDataEntityById(java.lang.String id) {
UNCOV
188
        return this.roCratePayload.getDataEntityById(id);
×
189
    }
190

191
    @Override
192
    public Set<DataEntity> getAllDataEntities() {
193
        return new HashSet<>(this.roCratePayload.getAllDataEntities());
1✔
194
    }
195

196
    @Override
197
    public ContextualEntity getContextualEntityById(String id) {
198
        return this.roCratePayload.getContextualEntityById(id);
1✔
199
    }
200

201
    @Override
202
    public Set<ContextualEntity> getAllContextualEntities() {
203
        return new HashSet<>(this.roCratePayload.getAllContextualEntities());
1✔
204
    }
205

206
    @Override
207
    public AbstractEntity getEntityById(String id) {
UNCOV
208
        return this.roCratePayload.getEntityById(id);
×
209
    }
210

211
    /**
212
     * {@inheritDoc}
213
     * <p>
214
     * Note: This will also link the DataEntity to the root node using the root
215
     * nodes hasPart property.
216
     *
217
     * @param entity the DataEntity to add to this crate.
218
     */
219
    @Override
220
    public void addDataEntity(DataEntity entity) {
221
        this.metadataContext.checkEntity(entity);
1✔
222
        this.roCratePayload.addDataEntity(entity);
1✔
223
        this.rootDataEntity.addToHasPart(entity.getId());
1✔
224
    }
1✔
225

226
    @Override
227
    public void addContextualEntity(ContextualEntity entity) {
228
        this.metadataContext.checkEntity(entity);
1✔
229
        this.roCratePayload.addContextualEntity(entity);
1✔
230
    }
1✔
231

232
    @Override
233
    public void deleteEntityById(String entityId) {
234
        // delete the entity firstly
235
        this.roCratePayload.removeEntityById(entityId);
1✔
236
        // remove from the root data entity hasPart
237
        this.rootDataEntity.removeFromHasPart(entityId);
1✔
238
        // remove from the root entity and the file descriptor
239
        JsonUtilFunctions.removeFieldsWith(entityId, this.rootDataEntity.getProperties());
1✔
240
        JsonUtilFunctions.removeFieldsWith(entityId, this.jsonDescriptor.getProperties());
1✔
241
    }
1✔
242

243
    @Override
244
    public void setUntrackedFiles(Collection<File> files) {
245
        this.untrackedFiles = files;
1✔
246
    }
1✔
247

248
    @Override
249
    public void deleteValuePairFromContext(String key) {
250
        this.metadataContext.deleteValuePairFromContext(key);
1✔
251
    }
1✔
252

253
    @Override
254
    public void deleteUrlFromContext(String key) {
255
        this.metadataContext.deleteUrlFromContext(key);
1✔
256
    }
1✔
257

258
    @Override
259
    public void addFromCollection(Collection<? extends AbstractEntity> entities) {
260
        this.roCratePayload.addEntities(entities);
1✔
261
    }
1✔
262

263
    @Override
264
    public void addItemFromDataCite(String locationUrl) {
UNCOV
265
        ImportFromDataCite.addDataCiteToCrate(locationUrl, this);
×
UNCOV
266
    }
×
267

268
    @Override
269
    public Collection<File> getUntrackedFiles() {
270
        return this.untrackedFiles;
1✔
271
    }
272

273
    /**
274
     * The inner class builder for the easier creation of a ROCrate.
275
     */
276
    public static class RoCrateBuilder {
277

278
        private static final String PROPERTY_DESCRIPTION = "description";
279

280
        CratePayload payload;
281
        CratePreview preview = new CustomPreview();
1✔
282
        CrateMetadataContext metadataContext;
283
        ContextualEntity license;
284
        RootDataEntity rootDataEntity;
285
        Collection<File> untrackedFiles = new HashSet<>();
1✔
286

287
        JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder();
1✔
288

289
        /**
290
         * The default constructor of a builder.
291
         *
292
         * @param name the name of the crate.
293
         * @param description the description of the crate.
294
         * @param datePublished the published date of the crate.
295
         * @param licenseId the license identifier of the crate.
296
         */
297
        public RoCrateBuilder(String name, String description, String datePublished, String licenseId) {
1✔
298
            this.payload = new RoCratePayload();
1✔
299
            this.metadataContext = new RoCrateMetadataContext();
1✔
300
            this.rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
1✔
301
                    .addProperty("name", name)
1✔
302
                    .addProperty(PROPERTY_DESCRIPTION, description)
1✔
303
                    .build();
1✔
304
            this.setLicense(licenseId);
1✔
305
            this.addDatePublishedWithExceptions(datePublished);
1✔
306
        }
1✔
307

308
        /**
309
         * The default constructor of a builder.
310
         *
311
         * @param name the name of the crate.
312
         * @param description the description of the crate.
313
         * @param datePublished the published date of the crate.
314
         * @param license the license entity of the crate.
315
         */
316
        public RoCrateBuilder(String name, String description, String datePublished, ContextualEntity license) {
1✔
317
            this.payload = new RoCratePayload();
1✔
318
            this.metadataContext = new RoCrateMetadataContext();
1✔
319
            this.rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
1✔
320
                    .addProperty("name", name)
1✔
321
                    .addProperty(PROPERTY_DESCRIPTION, description)
1✔
322
                    .build();
1✔
323
            this.setLicense(license);
1✔
324
            this.addDatePublishedWithExceptions(datePublished);
1✔
325
        }
1✔
326

327
        /**
328
         * A default constructor without any params where the root data entity
329
         * will be plain.
330
         */
331
        public RoCrateBuilder() {
1✔
332
            this.payload = new RoCratePayload();
1✔
333
            this.metadataContext = new RoCrateMetadataContext();
1✔
334
            rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
1✔
335
                    .build();
1✔
336
        }
1✔
337

338
        /**
339
         * A constructor with a crate as template.
340
         *
341
         * @param crate the crate to copy.
342
         */
343
        public RoCrateBuilder(RoCrate crate) {
1✔
344
            this.payload = crate.roCratePayload;
1✔
345
            this.preview = crate.roCratePreview;
1✔
346
            this.metadataContext = crate.metadataContext;
1✔
347
            this.rootDataEntity = crate.rootDataEntity;
1✔
348
            this.untrackedFiles = crate.untrackedFiles;
1✔
349
            this.descriptorBuilder = new JsonDescriptor.Builder(crate);
1✔
350
        }
1✔
351

352
        public RoCrateBuilder addName(String name) {
353
            this.rootDataEntity.addProperty("name", name);
1✔
354
            return this;
1✔
355
        }
356

357
        public RoCrateBuilder addDescription(String description) {
358
            this.rootDataEntity.addProperty(PROPERTY_DESCRIPTION, description);
1✔
359
            return this;
1✔
360
        }
361

362
        /**
363
         * Adds a data entity to the crate.
364
         * <p>
365
         * Note: This will also link the DataEntity to the root node using the
366
         * root nodes hasPart property.
367
         *
368
         * @param dataEntity the DataEntity to add to this crate.
369
         * @return returns the builder for further usage.
370
         */
371
        public RoCrateBuilder addDataEntity(DataEntity dataEntity) {
372
            this.payload.addDataEntity(dataEntity);
1✔
373
            this.rootDataEntity.addToHasPart(dataEntity.getId());
1✔
374
            return this;
1✔
375
        }
376

377
        public RoCrateBuilder addContextualEntity(ContextualEntity contextualEntity) {
378
            this.payload.addContextualEntity(contextualEntity);
1✔
379
            return this;
1✔
380
        }
381

382
        /**
383
         * Setting the license of the crate.
384
         *
385
         * @param license the license to set.
386
         * @return this builder.
387
         */
388
        public RoCrateBuilder setLicense(ContextualEntity license) {
389
            this.license = license;
1✔
390
            // From our tests, it seems like if we only have the ID for our license, we do
391
            // not need to add an extra entity.
392
            if (license.getProperties().size() > 1) {
1✔
393
                this.addContextualEntity(license);
1✔
394
            }
395
            this.rootDataEntity.addIdProperty("license", license.getId());
1✔
396
            return this;
1✔
397
        }
398

399
        /**
400
         * Setting the license of the crate using only a license identifier.
401
         *
402
         * @param licenseId the licenses identifier. Should be a resolveable
403
         * URI.
404
         * @return the builder
405
         */
406
        public RoCrateBuilder setLicense(String licenseId) {
407
            ContextualEntity licenseEntity = new ContextualEntity.ContextualEntityBuilder()
1✔
408
                    .setId(licenseId)
1✔
409
                    .build();
1✔
410
            this.setLicense(licenseEntity);
1✔
411
            return this;
1✔
412
        }
413

414
        /**
415
         * Adds a property with date time format. The property should match the
416
         * ISO 8601 date format.
417
         *
418
         * @param dateValue time string in ISO 8601 format
419
         * @return this builder
420
         * @throws IllegalArgumentException if format is not ISO 8601
421
         */
422
        public RoCrateBuilder addDatePublishedWithExceptions(String dateValue) throws IllegalArgumentException {
423
            this.rootDataEntity.addDateTimePropertyWithExceptions("datePublished", dateValue);
1✔
424
            return this;
1✔
425
        }
426

427
        public RoCrateBuilder setContext(CrateMetadataContext context) {
UNCOV
428
            this.metadataContext = context;
×
UNCOV
429
            return this;
×
430
        }
431

432
        public RoCrateBuilder addUrlToContext(java.lang.String url) {
433
            this.metadataContext.addToContextFromUrl(url);
1✔
434
            return this;
1✔
435
        }
436

437
        public RoCrateBuilder addValuePairToContext(java.lang.String key, java.lang.String value) {
438
            this.metadataContext.addToContext(key, value);
1✔
439
            return this;
1✔
440
        }
441

442
        public RoCrateBuilder setPreview(CratePreview preview) {
443
            this.preview = preview;
1✔
444
            return this;
1✔
445
        }
446

447
        public RoCrateBuilder addUntrackedFile(File file) {
448
            this.untrackedFiles.add(file);
1✔
449
            return this;
1✔
450
        }
451

452
        /**
453
         * @return a crate with the information from this builder.
454
         */
455
        public RoCrate build() {
456
            return new RoCrate(this);
1✔
457
        }
458
    }
459

460
    /**
461
     * Builder for Crates, supporting features which are not in a final
462
     * specification yet.
463
     * <p>
464
     * NOTE: This will change the specification version of your crate.
465
     * <p>
466
     * We only add features we expect to be in the new specification in the end.
467
     * In case a feature will not make it into the specification, we will mark
468
     * it as deprecated and remove it in new major versions. If a feature is
469
     * finalized, it will be added to the stable {@link RoCrateBuilder} and
470
     * marked as deprecated in this class.
471
     */
472
    public static class BuilderWithDraftFeatures extends RoCrateBuilder {
473

474
        /**
475
         * @see RoCrateBuilder#RoCrateBuilder()
476
         */
477
        public BuilderWithDraftFeatures() {
478
            super();
1✔
479
        }
1✔
480

481
        /**
482
         * @see RoCrateBuilder#RoCrateBuilder(String, String, String, String)
483
         */
484
        public BuilderWithDraftFeatures(String name, String description, String datePublished, String licenseId) {
UNCOV
485
            super(name, description, datePublished, licenseId);
×
UNCOV
486
        }
×
487

488
        /**
489
         * @see RoCrateBuilder#RoCrateBuilder(String, String, String,
490
         * ContextualEntity)
491
         */
492
        public BuilderWithDraftFeatures(String name, String description, String datePublished, ContextualEntity licenseId) {
UNCOV
493
            super(name, description, datePublished, licenseId);
×
UNCOV
494
        }
×
495

496
        /**
497
         * @see RoCrateBuilder#RoCrateBuilder(RoCrate)
498
         */
499
        public BuilderWithDraftFeatures(RoCrate crate) {
500
            super(crate);
1✔
501
            this.descriptorBuilder = new JsonDescriptor.Builder(crate);
1✔
502
        }
1✔
503

504
        /**
505
         * Indicate this crate also conforms to the given specification, in
506
         * addition to the version this builder adds.
507
         * <p>
508
         * This is helpful for profiles or other specifications the crate
509
         * conforms to. Can be called multiple times to add more specifications.
510
         *
511
         * @param specification a specification or profile this crate conforms
512
         * to.
513
         * @return the builder
514
         */
515
        public BuilderWithDraftFeatures alsoConformsTo(URI specification) {
516
            descriptorBuilder
1✔
517
                    .addConformsTo(specification)
1✔
518
                    // usage of a draft feature results in draft version numbers of the crate
519
                    .setVersion(CrateVersion.LATEST_UNSTABLE);
1✔
520
            return this;
1✔
521
        }
522
    }
523
}
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