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

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

14 Mar 2025 03:02PM UTC coverage: 90.688%. Remained the same
#317

Pull #204

github

web-flow
Merge f11398eb7 into ea55acade
Pull Request #204: PR for v2.0.2

1607 of 1772 relevant lines covered (90.69%)

0.91 hits per line

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

85.99
/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java
1
package edu.kit.datamanager.ro_crate.entities;
2

3
import com.fasterxml.jackson.annotation.JsonIgnore;
4
import com.fasterxml.jackson.annotation.JsonUnwrapped;
5
import com.fasterxml.jackson.databind.JsonNode;
6
import com.fasterxml.jackson.databind.ObjectMapper;
7
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
8
import com.fasterxml.jackson.databind.node.ArrayNode;
9
import com.fasterxml.jackson.databind.node.ObjectNode;
10

11
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
12
import edu.kit.datamanager.ro_crate.entities.serializers.ObjectNodeSerializer;
13
import edu.kit.datamanager.ro_crate.entities.validation.EntityValidation;
14
import edu.kit.datamanager.ro_crate.entities.validation.JsonSchemaValidation;
15
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
16
import edu.kit.datamanager.ro_crate.payload.Observer;
17
import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions;
18
import edu.kit.datamanager.ro_crate.special.IdentifierUtils;
19

20
import java.util.*;
21
import java.util.regex.Matcher;
22
import java.util.regex.Pattern;
23

24
/**
25
 * Abstract Entity parent class of every singe item in the json metadata file.
26
 *
27
 * @author Nikola Tzotchev on 3.2.2022 г.
28
 * @version 1
29
 */
30
public class AbstractEntity {
31

32
    /**
33
     * This set contains the types of an entity (ex. File, Dataset, ect.) It is
34
     * a set because it does not make sense to have duplicates
35
     */
36
    @JsonIgnore
37
    private Set<String> types;
38

39
    /**
40
     * Contains the whole list of properties of the entity It uses a custom
41
     * serializer because of cases where a single array element should be
42
     * displayed as a single value. ex: "key" : ["value"]
43
     * <=> "key" : "value"
44
     */
45
    @JsonUnwrapped
46
    @JsonSerialize(using = ObjectNodeSerializer.class)
47
    private ObjectNode properties;
48

49
    private static final EntityValidation entityValidation
1✔
50
            = new EntityValidation(new JsonSchemaValidation());
51

52
    @JsonIgnore
53
    private final Set<String> linkedTo;
54

55
    @JsonIgnore
56
    private final Set<Observer> observers;
57

58
    public void addObserver(Observer observer) {
59
        this.observers.add(observer);
1✔
60
    }
1✔
61

62
    private void notifyObservers() {
63
        this.observers.forEach(observer -> observer.update(this.getId()));
1✔
64
    }
1✔
65

66
    /**
67
     * Constructor that takes a builder and instantiates all the fields from it.
68
     *
69
     * @param entityBuilder the entity builder passed to the constructor.
70
     */
71
    public AbstractEntity(AbstractEntityBuilder<?> entityBuilder) {
1✔
72
        this.types = entityBuilder.types;
1✔
73
        this.properties = entityBuilder.properties;
1✔
74
        this.linkedTo = entityBuilder.relatedItems;
1✔
75
        this.observers = new HashSet<>();
1✔
76
        if (this.properties.get("@id") == null) {
1✔
77
            if (entityBuilder.id == null) {
1✔
78
                this.properties.put("@id", UUID.randomUUID().toString());
1✔
79
            } else {
80
                this.properties.put("@id", entityBuilder.id);
1✔
81
            }
82
        }
83
    }
1✔
84

85
    public Set<String> getLinkedTo() {
86
        return linkedTo;
1✔
87
    }
88

89
    /**
90
     * Returns the types of this entity.
91
     *
92
     * @return a set of type strings.
93
     */
94
    public Set<String> getTypes() {
95
        return types;
1✔
96
    }
97

98
    /**
99
     * Returns a Json object containing the properties of the entity.
100
     *
101
     * @return ObjectNode representing the properties.
102
     */
103
    public ObjectNode getProperties() {
104
        if (this.types != null) {
1✔
105
            JsonNode node = MyObjectMapper.getMapper().valueToTree(this.types);
1✔
106
            this.properties.set("@type", node);
1✔
107
        }
108
        return properties;
1✔
109
    }
110

111
    public JsonNode getProperty(String propertyKey) {
112
        return this.properties.get(propertyKey);
1✔
113
    }
114

115
    @JsonIgnore
116
    public String getId() {
117
        JsonNode id = this.properties.get("@id");
1✔
118
        return id == null ? null : id.asText();
1✔
119
    }
120

121
    /**
122
     * Set all the properties from a Json object to the Entity. The entities are
123
     * first validated to filter any invalid entity properties.
124
     *
125
     * @param obj the object that contains all the json properties that should
126
     * be added.
127
     */
128
    public void setProperties(JsonNode obj) {
129
        // validate whole entity
130
        if (entityValidation.entityValidation(obj)) {
1✔
131
            this.properties = obj.deepCopy();
1✔
132
            this.notifyObservers();
1✔
133
        }
134
    }
1✔
135

136
    protected void setId(String id) {
137
        this.properties.put("@id", id);
1✔
138
    }
1✔
139

140
    /**
141
     * removes one property from an entity.
142
     *
143
     * @param key the key of the entity, which will be removed.
144
     */
145
    public void removeProperty(String key) {
146
        this.getProperties().remove(key);
1✔
147
        this.notifyObservers();
1✔
148
    }
1✔
149

150
    /**
151
     * Removes a collection of properties from an entity.
152
     *
153
     * @param keys collection of keys, which will be removed.
154
     */
155
    public void removeProperties(Collection<String> keys) {
156
        this.getProperties().remove(keys);
1✔
157
        this.notifyObservers();
1✔
158
    }
1✔
159

160
    /**
161
     * Adds a property to the entity.
162
     * <p>
163
     * It may override values, if the key already exists.
164
     *
165
     * @param key the key of the property.
166
     * @param value value of the property.
167
     */
168
    public void addProperty(String key, String value) {
169
        if (key != null && value != null) {
1✔
170
            this.properties.put(key, value);
1✔
171
            this.notifyObservers();
1✔
172
        }
173
    }
1✔
174

175
    /**
176
     * Adds a property to the entity.
177
     * <p>
178
     * It may override values, if the key already exists.
179
     *
180
     * @param key the key of the property.
181
     * @param value value of the property.
182
     */
183
    public void addProperty(String key, long value) {
184
        if (key != null) {
×
185
            this.properties.put(key, value);
×
186
            this.notifyObservers();
×
187
        }
188
    }
×
189

190
    /**
191
     * Adds a property to the entity.
192
     * <p>
193
     * It may override values, if the key already exists.
194
     *
195
     * @param key the key of the property.
196
     * @param value value of the property.
197
     */
198
    public void addProperty(String key, double value) {
199
        if (key != null) {
×
200
            this.properties.put(key, value);
×
201
            this.notifyObservers();
×
202
        }
203
    }
×
204

205
    /**
206
     * Adds a generic property to the entity.
207
     * <p>
208
     * It may fail with an error message on stdout, in which case the
209
     * value is not added.
210
     * It may override values, if the key already exists.
211
     * This is the most generic way to add a property. The value is a
212
     * JsonNode that could contain anything possible. It is limited to
213
     * objects allowed to flattened documents, which means any literal,
214
     * an array of literals, or an object with an @id property.
215
     *
216
     * @param key   String key of the property.
217
     * @param value The JsonNode representing the value.
218
     */
219
    public void addProperty(String key, JsonNode value) {
220
        if (addProperty(this.properties, key, value)) {
×
221
            notifyObservers();
×
222
        }
223
    }
×
224

225
    private static boolean addProperty(ObjectNode whereToAdd, String key, JsonNode value) {
226
        boolean validInput = key != null && value != null;
1✔
227
        if (validInput && entityValidation.fieldValidation(value)) {
1✔
228
            whereToAdd.set(key, value);
1✔
229
            return true;
1✔
230
        }
231
        return false;
1✔
232
    }
233

234
    /**
235
     * Add a property that looks like this: "name" : {"@id" : "id"} If the
236
     * name property already exists add a second @id to it.
237
     *
238
     * @param name the "key" of the property.
239
     * @param id the "id" of the property.
240
     */
241
    public void addIdProperty(String name, String id) {
242
        JsonNode jsonNode = addToIdProperty(name, id, this.properties.get(name));
1✔
243
        if (jsonNode != null) {
1✔
244
            this.linkedTo.add(id);
1✔
245
            this.properties.set(name, jsonNode);
1✔
246
            this.notifyObservers();
1✔
247
        }
248
    }
1✔
249

250
    private static JsonNode addToIdProperty(String name, String id, JsonNode property) {
251
        ObjectMapper objectMapper = MyObjectMapper.getMapper();
1✔
252
        if (name != null && id != null) {
1✔
253
            if (property == null) {
1✔
254
                return objectMapper.createObjectNode().put("@id", id);
1✔
255
            } else {
256
                if (property.isArray()) {
1✔
257
                    ArrayNode ns = (ArrayNode) property;
1✔
258
                    ns.add(objectMapper.createObjectNode().put("@id", id));
1✔
259
                    return ns;
1✔
260
                } else {
261
                    ArrayNode newNodes = objectMapper.createArrayNode();
1✔
262
                    newNodes.add(property);
1✔
263
                    newNodes.add(objectMapper.createObjectNode().put("@id", id));
1✔
264
                    return newNodes;
1✔
265
                }
266
            }
267
        }
268
        return null;
1✔
269
    }
270

271
    /**
272
     * Adds everything from the properties to the property "name" as id.
273
     *
274
     * @param name the key of the property.
275
     * @param properties a collection containing all the id as String.
276
     */
277
    public void addIdListProperties(String name, Collection<String> properties) {
278
        ObjectMapper objectMapper = MyObjectMapper.getMapper();
1✔
279
        ArrayNode node = objectMapper.createArrayNode();
1✔
280
        if (this.properties.get(name) == null) {
1✔
281
            node = objectMapper.createArrayNode();
1✔
282
        } else {
283
            if (!this.properties.get(name).isArray()) {
×
284
                node.add(this.properties.get(name));
×
285
            }
286
        }
287
        for (String s : properties) {
1✔
288
            this.linkedTo.add(s);
1✔
289
            node.add(objectMapper.createObjectNode().put("@id", s));
1✔
290
        }
1✔
291
        if (node.size() == 1) {
1✔
292
            this.properties.set(name, node.get(0));
1✔
293
        } else {
294
            this.properties.set(name, node);
1✔
295
        }
296
        notifyObservers();
1✔
297
    }
1✔
298

299
    /**
300
     * Adding new type to the property (which may have multiple such ones).
301
     *
302
     * @param type the String representing the type.
303
     */
304
    public void addType(String type) {
305
        if (this.types == null) {
1✔
306
            this.types = new HashSet<>();
1✔
307
        }
308
        this.types.add(type);
1✔
309
        JsonNode node = MyObjectMapper.getMapper().valueToTree(this.types);
1✔
310
        this.properties.set("@type", node);
1✔
311
    }
1✔
312

313
    /**
314
     * Checks if the date matches the ISO 8601 date format.
315
     *
316
     * @param date the date as a string
317
     * @throws IllegalArgumentException if format does not match
318
     */
319
    private static void checkFormatISO8601(String date) throws IllegalArgumentException {
320
        String regex = "^([\\+-]?\\d{4}(?!\\d{2}\\b))((-?)((0[1-9]|1[0-2])(\\3([12]\\d|0[1-9]|3[01]))?|W([0-4]\\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\\d|[12]\\d{2}|3([0-5]\\d|6[1-6])))([T\\s]((([01]\\d|2[0-3])((:?)[0-5]\\d)?|24\\:?00)([\\.,]\\d+(?!:))?)?(\\17[0-5]\\d([\\.,]\\d+)?)?([zZ]|([\\+-])([01]\\d|2[0-3]):?([0-5]\\d)?)?)?)?$";
1✔
321
        Pattern pattern = Pattern.compile(regex);
1✔
322
        Matcher matcher = pattern.matcher(date);
1✔
323
        if (!matcher.matches()) {
1✔
324
            throw new IllegalArgumentException("Date MUST be a string in ISO 8601 format");
×
325
        }
326
    }
1✔
327

328
    /**
329
     * Adds a property with date time format. The property should match the ISO 8601
330
     * date format.
331
     * 
332
     * Same as {@link #addProperty(String, String)} but with internal check.
333
     *
334
     * @param key   key of the property (e.g. datePublished)
335
     * @param value time string in ISO 8601 format
336
     * @throws IllegalArgumentException if format is not ISO 8601
337
     */
338
    public void addDateTimePropertyWithExceptions(String key, String value) throws IllegalArgumentException {
339
        if (value != null) {
1✔
340
            checkFormatISO8601(value);
1✔
341
            this.properties.put(key, value);
1✔
342
            this.notifyObservers();
1✔
343
        }
344
    }
1✔
345

346
    /**
347
     * This a builder inner class that should allow for an easier creating of
348
     * entities.
349
     *
350
     * @param <T> The type of the child builders so that they to can use the
351
     * methods provided here.
352
     */
353
    public abstract static class AbstractEntityBuilder<T extends AbstractEntityBuilder<T>> {
354

355
        private Set<String> types;
356
        protected Set<String> relatedItems;
357
        private ObjectNode properties;
358
        private String id;
359

360
        protected AbstractEntityBuilder() {
1✔
361
            this.properties = MyObjectMapper.getMapper().createObjectNode();
1✔
362
            this.relatedItems = new HashSet<>();
1✔
363
        }
1✔
364

365
        protected String getId() {
366
            return this.id;
1✔
367
        }
368

369
        /**
370
         * Setting the id property of the entity, if the given value is not
371
         * null. If the id is not encoded, the encoding will be done.
372
         *
373
         * @param id the String representing the id.
374
         * @return the generic builder.
375
         */
376
        public T setId(String id) {
377
            if (id != null && !id.equals(RootDataEntity.ID)) {
1✔
378
                if (IdentifierUtils.isValidUri(id)) {
1✔
379
                    this.id = id;
1✔
380
                } else {
381
                    this.id = IdentifierUtils.encode(id).get();
1✔
382
                }
383
            }
384
            return self();
1✔
385
        }
386

387
        /**
388
         * Adding a type to the builder types.
389
         *
390
         * @param type the type to add.
391
         * @return the generic builder.
392
         */
393
        public T addType(String type) {
394
            if (this.types == null) {
1✔
395
                this.types = new HashSet<>();
1✔
396
            }
397
            this.types.add(type);
1✔
398
            return self();
1✔
399
        }
400

401
        /**
402
         * Adds multiple types in one call.
403
         *
404
         * @param types the types to add.
405
         * @return the generic builder.
406
         */
407
        public T addTypes(Collection<String> types) {
408
            if (this.types == null) {
×
409
                this.types = new HashSet<>();
×
410
            }
411
            this.types.addAll(types);
×
412
            return self();
×
413
        }
414

415
        /**
416
         * Adds a property with date time format. The property should match the ISO 8601
417
         * date format.
418
         * 
419
         * Same as {@link #addProperty(String, String)} but with internal check.
420
         *
421
         * @param key   key of the property (e.g. datePublished)
422
         * @param value time string in ISO 8601 format
423
         * @return this builder
424
         * @throws IllegalArgumentException if format is not ISO 8601
425
         */
426
        public T addDateTimePropertyWithExceptions(String key, String value) throws IllegalArgumentException {
427
            if (value != null) {
1✔
428
                checkFormatISO8601(value);
1✔
429
                this.properties.put(key, value);
1✔
430
            }
431
            return self();
1✔
432
        }
433

434
        /**
435
         * Adding a property to the builder.
436
         *
437
         * @param key the key of the property in a string.
438
         * @param value the JsonNode value of te property.
439
         * @return the generic builder.
440
         */
441
        public T addProperty(String key, JsonNode value) {
442
            if (AbstractEntity.addProperty(this.properties, key, value)) {
1✔
443
                this.relatedItems.addAll(JsonUtilFunctions.getIdPropertiesFromProperty(value));
1✔
444
            }
445
            return self();
1✔
446
        }
447

448
        /**
449
         * Adding a property to the builder.
450
         *
451
         * @param key the key of the property as a string.
452
         * @param value the value of the property as a string.
453
         * @return the generic builder.
454
         */
455
        public T addProperty(String key, String value) {
456
            this.properties.put(key, value);
1✔
457
            return self();
1✔
458
        }
459

460
        public T addProperty(String key, int value) {
461
            this.properties.put(key, value);
×
462
            return self();
×
463
        }
464

465
        public T addProperty(String key, double value) {
466
            this.properties.put(key, value);
×
467
            return self();
×
468
        }
469

470
        public T addProperty(String key, boolean value) {
471
            this.properties.put(key, value);
1✔
472
            return self();
1✔
473
        }
474

475
        /**
476
         * ID properties are often used when referencing other entities within
477
         * the ROCrate. This method adds automatically such one.
478
         * 
479
         * Instead of {@code "name": "id" }
480
         * this will add {@code "name" : {"@id": "id"} }
481
         * 
482
         * Does nothing if name or id are null.
483
         *
484
         * @param name the name of the ID property.
485
         * @param id the ID.
486
         * @return the generic builder
487
         */
488
        public T addIdProperty(String name, String id) {
489
            JsonNode jsonNode = AbstractEntity.addToIdProperty(name, id, this.properties.get(name));
1✔
490
            if (jsonNode != null) {
1✔
491
                this.properties.set(name, jsonNode);
1✔
492
                this.relatedItems.add(id);
1✔
493
            }
494
            return self();
1✔
495
        }
496

497
        /**
498
         * This is another way of adding the ID property, this time the whole
499
         * other Entity is provided.
500
         *
501
         * @param name the name of the property.
502
         * @param entity the other entity that is referenced.
503
         * @return the generic builder.
504
         */
505
        public T addIdProperty(String name, AbstractEntity entity) {
506
            if (entity != null) {
1✔
507
                return addIdProperty(name, entity.getId());
1✔
508
            }
509
            return self();
1✔
510
        }
511

512
        /**
513
         * This adds multiple id entities to a single key.
514
         *
515
         * @param name the name of the property.
516
         * @param entities the Collection containing the multiple entities.
517
         * @return the generic builder.
518
         */
519
        public T addIdFromCollectionOfEntities(String name, Collection<AbstractEntity> entities) {
520
            if (entities != null) {
1✔
521
                for (var e : entities) {
1✔
522
                    addIdProperty(name, e);
1✔
523
                }
1✔
524
            }
525
            return self();
1✔
526
        }
527

528
        /**
529
         * This sets everything from a json object to the property. Can be
530
         * useful when the entity is already available somewhere.
531
         *
532
         * @param properties the Json representing all the properties.
533
         * @return the generic builder.
534
         */
535
        public T setAll(ObjectNode properties) {
536
            if (AbstractEntity.entityValidation.entityValidation(properties)) {
1✔
537
                this.properties = properties;
1✔
538
                this.relatedItems.addAll(JsonUtilFunctions.getIdPropertiesFromJsonNode(properties));
1✔
539
            }
540
            return self();
1✔
541
        }
542

543
        public abstract T self();
544

545
        public abstract AbstractEntity build();
546
    }
547

548
}
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