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

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

02 May 2025 11:49AM UTC coverage: 89.417% (+0.06%) from 89.357%
#412

Pull #256

github

web-flow
Merge b167baed2 into f1a1a72a0
Pull Request #256: Make specification examples (from readme) executable

32 of 32 new or added lines in 2 files covered. (100.0%)

14 existing lines in 3 files now uncovered.

1901 of 2126 relevant lines covered (89.42%)

0.89 hits per line

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

93.68
/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.data.DataEntity;
14

15
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
16
import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite;
17
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
18
import edu.kit.datamanager.ro_crate.payload.CratePayload;
19
import edu.kit.datamanager.ro_crate.payload.RoCratePayload;
20
import edu.kit.datamanager.ro_crate.preview.CratePreview;
21
import edu.kit.datamanager.ro_crate.preview.CustomPreview;
22
import edu.kit.datamanager.ro_crate.special.CrateVersion;
23
import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions;
24
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
25
import edu.kit.datamanager.ro_crate.validation.Validator;
26

27
import java.io.File;
28
import java.net.URI;
29
import java.util.*;
30
import java.util.stream.Collectors;
31
import java.util.stream.StreamSupport;
32

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

45
    protected final CratePayload roCratePayload;
46
    protected CrateMetadataContext metadataContext;
47
    protected CratePreview roCratePreview;
48
    protected RootDataEntity rootDataEntity;
49
    protected ContextualEntity jsonDescriptor;
50

51
    protected Collection<File> untrackedFiles;
52

53
    @Override
54
    public CratePreview getPreview() {
55
        return this.roCratePreview;
1✔
56
    }
57

58
    public void setRoCratePreview(CratePreview preview) {
59
        this.roCratePreview = preview;
1✔
60
    }
1✔
61

62
    @Override
63
    public void setMetadataContext(CrateMetadataContext metadataContext) {
64
        this.metadataContext = metadataContext;
1✔
65
    }
1✔
66

67
    @Override
68
    public String getMetadataContextValueOf(String key) {
69
        return this.metadataContext.getValueOf(key);
1✔
70
    }
71

72
    @Override
73
    public Set<String> getMetadataContextKeys() {
74
        return this.metadataContext.getKeys();
1✔
75
    }
76

77
    @Override
78
    public Map<String, String> getMetadataContextPairs() {
79
        return this.metadataContext.getPairs();
1✔
80
    }
81

82
    public ContextualEntity getJsonDescriptor() {
83
        return jsonDescriptor;
1✔
84
    }
85

86
    @Override
87
    public void setJsonDescriptor(ContextualEntity jsonDescriptor) {
88
        this.jsonDescriptor = jsonDescriptor;
1✔
89
    }
1✔
90
    
91
    @Override
92
    public RootDataEntity getRootDataEntity() {
93
        return rootDataEntity;
1✔
94
    }
95
    
96
    @Override
97
    public void setRootDataEntity(RootDataEntity rootDataEntity) {
98
        this.rootDataEntity = rootDataEntity;
1✔
99
    }
1✔
100

101
    /**
102
     * Default constructor for creation of an empty crate.
103
     */
104
    public RoCrate() {
1✔
105
        this.roCratePayload = new RoCratePayload();
1✔
106
        this.untrackedFiles = new HashSet<>();
1✔
107
        this.metadataContext = new RoCrateMetadataContext();
1✔
108
        rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
1✔
109
                .build();
1✔
110
        jsonDescriptor = new JsonDescriptor();
1✔
111
    }
1✔
112

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

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

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

161
    @Override
162
    public String getJsonMetadata() {
163
        ObjectMapper objectMapper = MyObjectMapper.getMapper();
1✔
164
        ObjectNode node = objectMapper.createObjectNode();
1✔
165

166
        node.setAll(this.metadataContext.getContextJsonEntity());
1✔
167

168
        var graph = objectMapper.createArrayNode();
1✔
169
        ObjectNode root = objectMapper.convertValue(this.rootDataEntity, ObjectNode.class);
1✔
170
        graph.add(root);
1✔
171

172
        graph.add(objectMapper.convertValue(this.jsonDescriptor, JsonNode.class));
1✔
173
        if (this.roCratePayload != null && this.roCratePayload.getEntitiesMetadata() != null) {
1✔
174
            graph.addAll(this.roCratePayload.getEntitiesMetadata());
1✔
175
        }
176
        node.set("@graph", graph);
1✔
177
        return node.toString();
1✔
178
    }
179

180
    @Override
181
    public DataEntity getDataEntityById(java.lang.String id) {
182
        return this.roCratePayload.getDataEntityById(id);
×
183
    }
184

185
    @Override
186
    public Set<DataEntity> getAllDataEntities() {
187
        return new HashSet<>(this.roCratePayload.getAllDataEntities());
1✔
188
    }
189

190
    @Override
191
    public ContextualEntity getContextualEntityById(String id) {
192
        return this.roCratePayload.getContextualEntityById(id);
1✔
193
    }
194

195
    @Override
196
    public Set<ContextualEntity> getAllContextualEntities() {
197
        return new HashSet<>(this.roCratePayload.getAllContextualEntities());
1✔
198
    }
199

200
    @Override
201
    public AbstractEntity getEntityById(String id) {
202
        return this.roCratePayload.getEntityById(id);
×
203
    }
204

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

220
    @Override
221
    public void addContextualEntity(ContextualEntity entity) {
222
        this.metadataContext.checkEntity(entity);
1✔
223
        this.roCratePayload.addContextualEntity(entity);
1✔
224
    }
1✔
225

226
    @Override
227
    public void deleteEntityById(String entityId) {
228
        // delete the entity firstly
229
        this.roCratePayload.removeEntityById(entityId);
1✔
230
        // remove from the root data entity hasPart
231
        this.rootDataEntity.removeFromHasPart(entityId);
1✔
232
        // remove from the root entity and the file descriptor
233
        JsonUtilFunctions.removeFieldsWith(entityId, this.rootDataEntity.getProperties());
1✔
234
        JsonUtilFunctions.removeFieldsWith(entityId, this.jsonDescriptor.getProperties());
1✔
235
    }
1✔
236

237
    @Override
238
    public void setUntrackedFiles(Collection<File> files) {
239
        this.untrackedFiles = files;
1✔
240
    }
1✔
241

242
    @Override
243
    public void deleteValuePairFromContext(String key) {
244
        this.metadataContext.deleteValuePairFromContext(key);
1✔
245
    }
1✔
246

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

252
    @Override
253
    public void addFromCollection(Collection<? extends AbstractEntity> entities) {
254
        this.roCratePayload.addEntities(entities);
1✔
255
    }
1✔
256

257
    @Override
258
    public void addItemFromDataCite(String locationUrl) {
259
        ImportFromDataCite.addDataCiteToCrate(locationUrl, this);
×
260
    }
×
261

262
    @Override
263
    public Collection<File> getUntrackedFiles() {
264
        return this.untrackedFiles;
1✔
265
    }
266

267
    /**
268
     * The inner class builder for the easier creation of a ROCrate.
269
     */
270
    public static class RoCrateBuilder {
271

272
        private static final String PROPERTY_DESCRIPTION = "description";
273

274
        CratePayload payload;
275
        CratePreview preview = new CustomPreview();
1✔
276
        CrateMetadataContext metadataContext;
277
        ContextualEntity license;
278
        RootDataEntity rootDataEntity;
279
        Collection<File> untrackedFiles = new HashSet<>();
1✔
280

281
        JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder();
1✔
282

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

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

321
        /**
322
         * A default constructor without any params where the root data entity
323
         * will be plain.
324
         */
325
        public RoCrateBuilder() {
1✔
326
            this.payload = new RoCratePayload();
1✔
327
            this.metadataContext = new RoCrateMetadataContext();
1✔
328
            rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
1✔
329
                    .build();
1✔
330
        }
1✔
331

332
        /**
333
         * A constructor with a crate as template.
334
         *
335
         * @param crate the crate to copy.
336
         */
337
        public RoCrateBuilder(RoCrate crate) {
1✔
338
            this.payload = crate.roCratePayload;
1✔
339
            this.preview = crate.roCratePreview;
1✔
340
            this.metadataContext = crate.metadataContext;
1✔
341
            this.rootDataEntity = crate.rootDataEntity;
1✔
342
            this.untrackedFiles = crate.untrackedFiles;
1✔
343
            this.descriptorBuilder = new JsonDescriptor.Builder(crate);
1✔
344
        }
1✔
345

346
        public RoCrateBuilder addName(String name) {
347
            this.rootDataEntity.addProperty("name", name);
1✔
348
            return this;
1✔
349
        }
350

351
        public RoCrateBuilder addIdentifier(String identifier) {
352
            this.rootDataEntity.addProperty("identifier", identifier.strip());
1✔
353
            return this;
1✔
354
        }
355

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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