Skip to content

Commit 53aebf7

Browse files
authored
Merge pull request #111 from kit-data-manager/105-multiple-conformsto-values
Fix #105: "multiple conformsTo values"
2 parents 347e450 + 025c19e commit 53aebf7

File tree

16 files changed

+846
-91
lines changed

16 files changed

+846
-91
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,23 @@
66
[![Publish to Maven Central / OSSRH](https://github.com/kit-data-manager/ro-crate-java/actions/workflows/publishRelease.yml/badge.svg)](https://github.com/kit-data-manager/ro-crate-java/actions/workflows/publishRelease.yml)
77

88
A Java library to create and modify RO-Crates.
9-
Read [Quickstart](#quickstart) for a short overview of the API
9+
The aim of this implementation is to **not** require too deep knowledge of the specification,
10+
and avoiding crates which do not fully comply to the specification, at the same time.
11+
Read [Quick-start](#quick-start) for a short overview of the API
1012
or take a look at [how to adapt the examples from the official specification](#adapting-the-specification-examples).
1113

1214
Build and run tests: `./gradlew build`
1315
Build documentation: `./gradlew javadoc`
1416

1517
On Windows, replace `./gradlew` with `gradlew.bat`.
1618

19+
## RO-Crate Specification Compatibility
20+
21+
- ✅ Version 1.1
22+
- 🛠️ Version 1.2-DRAFT
23+
- ✅ Reading and writing crates with additional profiles or specifications ([examples for reading](src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java), [examples for writing](src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java))
24+
- ✅ Adding profiles or other specifications to a crate ([examples](src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java))
25+
1726
## Quick-start
1827
### Example for a basic crate from [RO-Crate website](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)
1928
```java

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ plugins {
1818
group 'edu.kit.datamanager'
1919
description = "A library for easy creation and modification of valid RO-Crates."
2020

21+
println "Running gradle version: $gradle.gradleVersion"
22+
println "Building ${name} version: ${version}"
23+
println "JDK version: ${JavaVersion.current()}"
24+
2125
repositories {
2226
mavenCentral()
2327
}

src/main/java/edu/kit/datamanager/ro_crate/Crate.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
import java.io.File;
44
import java.util.Collection;
55
import java.util.List;
6+
import java.util.Optional;
67

78
import edu.kit.datamanager.ro_crate.context.CrateMetadataContext;
89
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
910
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
1011
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
1112
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
1213
import edu.kit.datamanager.ro_crate.preview.CratePreview;
14+
import edu.kit.datamanager.ro_crate.special.CrateVersion;
1315

1416
/**
1517
* An interface describing an ROCrate.
@@ -18,6 +20,33 @@
1820
* @version 1
1921
*/
2022
public interface Crate {
23+
24+
/**
25+
* Read version from the crate descriptor and return it as a class
26+
* representation.
27+
*
28+
* NOTE: If there is not version in the crate, it does not comply with the
29+
* specification.
30+
*
31+
* @return the class representation indication the version of this crate, if
32+
* available.
33+
*/
34+
public Optional<CrateVersion> getVersion();
35+
36+
/**
37+
* Returns strings indicating the conformance of a crate with other
38+
* specifications than the RO-Crate version.
39+
*
40+
* If you need the crate version too, refer to {@link #getVersion()}.
41+
*
42+
* This corresponds technically to all conformsTo values, excluding the RO crate
43+
* version / specification.
44+
*
45+
* @return a collection of the profiles or specifications this crate conforms
46+
* to.
47+
*/
48+
public Collection<String> getProfiles();
49+
2150
CratePreview getPreview();
2251

2352
void setMetadataContext(CrateMetadataContext metadataContext);

src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java

Lines changed: 108 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package edu.kit.datamanager.ro_crate;
22

3+
import com.fasterxml.jackson.core.TreeNode;
34
import com.fasterxml.jackson.databind.JsonNode;
45
import com.fasterxml.jackson.databind.ObjectMapper;
56
import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -8,33 +9,41 @@
89
import edu.kit.datamanager.ro_crate.context.RoCrateMetadataContext;
910
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
1011
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
12+
import edu.kit.datamanager.ro_crate.entities.contextual.JsonDescriptor;
1113
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
1214
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
1315
import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite;
1416
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
1517
import edu.kit.datamanager.ro_crate.payload.CratePayload;
1618
import edu.kit.datamanager.ro_crate.payload.RoCratePayload;
1719
import edu.kit.datamanager.ro_crate.preview.CratePreview;
20+
import edu.kit.datamanager.ro_crate.special.CrateVersion;
1821
import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions;
1922
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
2023
import edu.kit.datamanager.ro_crate.validation.Validator;
2124

2225
import java.io.File;
26+
import java.net.URI;
2327
import java.util.ArrayList;
2428
import java.util.Collection;
29+
import java.util.Collections;
2530
import java.util.List;
31+
import java.util.Optional;
32+
import java.util.stream.Collectors;
33+
import java.util.stream.StreamSupport;
2634

2735
/**
2836
* The class that represents a single ROCrate.
37+
*
38+
* To build or modify it, use a instance of {@link RoCrateBuilder}. In the case
39+
* features of RO-Crate DRAFT specifications are needed, refer to
40+
* {@link BuilderWithDraftFeatures} and its documentation.
2941
*
3042
* @author Nikola Tzotchev on 6.2.2022 г.
3143
* @version 1
3244
*/
3345
public class RoCrate implements Crate {
3446

35-
private static final String ID = "ro-crate-metadata.json";
36-
private static final String RO_SPEC = "https://w3id.org/ro/crate/1.1";
37-
3847
private final CratePayload roCratePayload;
3948
private CrateMetadataContext metadataContext;
4049
private CratePreview roCratePreview;
@@ -81,7 +90,7 @@ public RoCrate() {
8190
this.metadataContext = new RoCrateMetadataContext();
8291
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
8392
.build();
84-
jsonDescriptor = createDefaultJsonDescriptor();
93+
jsonDescriptor = new JsonDescriptor();
8594
}
8695

8796
/**
@@ -95,12 +104,44 @@ public RoCrate(RoCrateBuilder roCrateBuilder) {
95104
this.metadataContext = roCrateBuilder.metadataContext;
96105
this.roCratePreview = roCrateBuilder.preview;
97106
this.rootDataEntity = roCrateBuilder.rootDataEntity;
98-
this.jsonDescriptor = roCrateBuilder.jsonDescriptor;
107+
this.jsonDescriptor = roCrateBuilder.descriptorBuilder.build();
99108
this.untrackedFiles = roCrateBuilder.untrackedFiles;
100109
Validator defaultValidation = new Validator(new JsonSchemaValidation());
101110
defaultValidation.validate(this);
102111
}
103112

113+
@Override
114+
public Optional<CrateVersion> getVersion() {
115+
JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
116+
if (conformsTo.isArray()) {
117+
return StreamSupport.stream(conformsTo.spliterator(), false)
118+
.filter(TreeNode::isObject)
119+
.map(obj -> obj.path("@id").asText())
120+
.map(CrateVersion::fromSpecUri)
121+
.filter(Optional::isPresent)
122+
.map(Optional::get)
123+
.findFirst();
124+
} else if (conformsTo.isObject()) {
125+
return CrateVersion.fromSpecUri(conformsTo.get("@id").asText());
126+
} else {
127+
return Optional.empty();
128+
}
129+
}
130+
131+
@Override
132+
public Collection<String> getProfiles() {
133+
JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
134+
if (conformsTo.isArray()) {
135+
return StreamSupport.stream(conformsTo.spliterator(), false)
136+
.filter(TreeNode::isObject)
137+
.map(obj -> obj.path("@id").asText())
138+
.filter(txt -> !CrateVersion.fromSpecUri(txt).isPresent())
139+
.collect(Collectors.toSet());
140+
} else {
141+
return Collections.emptySet();
142+
}
143+
}
144+
104145
@Override
105146
public String getJsonMetadata() {
106147
ObjectMapper objectMapper = MyObjectMapper.getMapper();
@@ -201,28 +242,20 @@ public List<File> getUntrackedFiles() {
201242
return this.untrackedFiles;
202243
}
203244

204-
protected static ContextualEntity createDefaultJsonDescriptor() {
205-
return new ContextualEntity.ContextualEntityBuilder()
206-
.setId(ID)
207-
.addType("CreativeWork")
208-
.addIdProperty("about", "./")
209-
.addIdProperty("conformsTo", RoCrate.RO_SPEC)
210-
.build();
211-
}
212-
213245
/**
214246
* The inner class builder for the easier creation of a ROCrate.
215247
*/
216-
public static final class RoCrateBuilder {
248+
public static class RoCrateBuilder {
217249

218250
CratePayload payload;
219251
CratePreview preview;
220252
CrateMetadataContext metadataContext;
221253
ContextualEntity license;
222254
RootDataEntity rootDataEntity;
223-
ContextualEntity jsonDescriptor;
224255
List<File> untrackedFiles;
225256

257+
JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder();
258+
226259
/**
227260
* The default constructor of a builder.
228261
*
@@ -237,7 +270,6 @@ public RoCrateBuilder(String name, String description) {
237270
.addProperty("name", name)
238271
.addProperty("description", description)
239272
.build();
240-
jsonDescriptor = RoCrate.createDefaultJsonDescriptor();
241273
}
242274

243275
/**
@@ -250,7 +282,6 @@ public RoCrateBuilder() {
250282
this.metadataContext = new RoCrateMetadataContext();
251283
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
252284
.build();
253-
jsonDescriptor = RoCrate.createDefaultJsonDescriptor();
254285
}
255286

256287
/**
@@ -263,8 +294,8 @@ public RoCrateBuilder(RoCrate crate) {
263294
this.preview = crate.roCratePreview;
264295
this.metadataContext = crate.metadataContext;
265296
this.rootDataEntity = crate.rootDataEntity;
266-
this.jsonDescriptor = crate.jsonDescriptor;
267297
this.untrackedFiles = crate.untrackedFiles;
298+
this.descriptorBuilder = new JsonDescriptor.Builder(crate);
268299
}
269300

270301
/**
@@ -322,9 +353,67 @@ public RoCrateBuilder addUntrackedFile(File file) {
322353
return this;
323354
}
324355

356+
/**
357+
* Returns a crate with the information from this builder.
358+
*/
325359
public RoCrate build() {
326360
return new RoCrate(this);
327361
}
328362
}
329363

364+
/**
365+
* Builder for Crates, supporting features which are not in a final
366+
* specification yet.
367+
*
368+
* NOTE: This will change the specification version of your crate.
369+
*
370+
* We only add features we expect to be in the new specification in the
371+
* end.
372+
* In case a feature will not make it into the specification, we will mark it as
373+
* deprecated and remove it in new major versions.
374+
* If a feature is finalized, it will be added to the stable
375+
* {@link RoCrateBuilder} and marked as deprecated in this class.
376+
*/
377+
public static class BuilderWithDraftFeatures extends RoCrateBuilder {
378+
379+
/**
380+
* @see RoCrateBuilder#RoCrateBuilder()
381+
*/
382+
public BuilderWithDraftFeatures() {
383+
super();
384+
}
385+
386+
/**
387+
* @see RoCrateBuilder#RoCrateBuilder(String, String)
388+
*/
389+
public BuilderWithDraftFeatures(String name, String description) {
390+
super();
391+
}
392+
393+
/**
394+
* @see RoCrateBuilder#RoCrateBuilder(RoCrate)
395+
*/
396+
public BuilderWithDraftFeatures(RoCrate crate) {
397+
super(crate);
398+
this.descriptorBuilder = new JsonDescriptor.Builder(crate);
399+
}
400+
401+
/**
402+
* Indicate this crate also conforms to the given specification, in addition to
403+
* the version this builder adds.
404+
*
405+
* This is helpful for profiles or other specifications the crate conforms to.
406+
* Can be called multiple times to add more specifications.
407+
*
408+
* @param specification a specification or profile this crate conforms to.
409+
* @return the builder
410+
*/
411+
public BuilderWithDraftFeatures alsoConformsTo(URI specification) {
412+
descriptorBuilder
413+
.addConformsTo(specification)
414+
// usage of a draft feature results in draft version numbers of the crate
415+
.setVersion(CrateVersion.LATEST_UNSTABLE);
416+
return this;
417+
}
418+
}
330419
}

src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,13 +306,12 @@ protected String getId() {
306306
}
307307

308308
/**
309-
* Setting the id property of the entity.
309+
* Setting the id property of the entity, if the given value is not null.
310310
*
311311
* @param id the String representing the id.
312312
* @return the generic builder.
313313
*/
314314
public T setId(String id) {
315-
// TODO document why this has been implemented this way.
316315
if (id != null) {
317316
this.id = id;
318317
}

0 commit comments

Comments
 (0)