Skip to content

Commit

Permalink
Merge pull request #111 from kit-data-manager/105-multiple-conformsto…
Browse files Browse the repository at this point in the history
…-values

Fix #105: "multiple conformsTo values"
  • Loading branch information
Pfeil authored Jun 23, 2023
2 parents 347e450 + 025c19e commit 53aebf7
Show file tree
Hide file tree
Showing 16 changed files with 846 additions and 91 deletions.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@
[![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)

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

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

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

## RO-Crate Specification Compatibility

- ✅ Version 1.1
- 🛠️ Version 1.2-DRAFT
- ✅ 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))
- ✅ Adding profiles or other specifications to a crate ([examples](src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java))

## Quick-start
### 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)
```java
Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ plugins {
group 'edu.kit.datamanager'
description = "A library for easy creation and modification of valid RO-Crates."

println "Running gradle version: $gradle.gradleVersion"
println "Building ${name} version: ${version}"
println "JDK version: ${JavaVersion.current()}"

repositories {
mavenCentral()
}
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/edu/kit/datamanager/ro_crate/Crate.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import edu.kit.datamanager.ro_crate.context.CrateMetadataContext;
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
import edu.kit.datamanager.ro_crate.preview.CratePreview;
import edu.kit.datamanager.ro_crate.special.CrateVersion;

/**
* An interface describing an ROCrate.
Expand All @@ -18,6 +20,33 @@
* @version 1
*/
public interface Crate {

/**
* Read version from the crate descriptor and return it as a class
* representation.
*
* NOTE: If there is not version in the crate, it does not comply with the
* specification.
*
* @return the class representation indication the version of this crate, if
* available.
*/
public Optional<CrateVersion> getVersion();

/**
* Returns strings indicating the conformance of a crate with other
* specifications than the RO-Crate version.
*
* If you need the crate version too, refer to {@link #getVersion()}.
*
* This corresponds technically to all conformsTo values, excluding the RO crate
* version / specification.
*
* @return a collection of the profiles or specifications this crate conforms
* to.
*/
public Collection<String> getProfiles();

CratePreview getPreview();

void setMetadataContext(CrateMetadataContext metadataContext);
Expand Down
127 changes: 108 additions & 19 deletions src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.kit.datamanager.ro_crate;

import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand All @@ -8,33 +9,41 @@
import edu.kit.datamanager.ro_crate.context.RoCrateMetadataContext;
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
import edu.kit.datamanager.ro_crate.entities.contextual.JsonDescriptor;
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite;
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
import edu.kit.datamanager.ro_crate.payload.CratePayload;
import edu.kit.datamanager.ro_crate.payload.RoCratePayload;
import edu.kit.datamanager.ro_crate.preview.CratePreview;
import edu.kit.datamanager.ro_crate.special.CrateVersion;
import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions;
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
import edu.kit.datamanager.ro_crate.validation.Validator;

import java.io.File;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

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

private static final String ID = "ro-crate-metadata.json";
private static final String RO_SPEC = "https://w3id.org/ro/crate/1.1";

private final CratePayload roCratePayload;
private CrateMetadataContext metadataContext;
private CratePreview roCratePreview;
Expand Down Expand Up @@ -81,7 +90,7 @@ public RoCrate() {
this.metadataContext = new RoCrateMetadataContext();
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
.build();
jsonDescriptor = createDefaultJsonDescriptor();
jsonDescriptor = new JsonDescriptor();
}

/**
Expand All @@ -95,12 +104,44 @@ public RoCrate(RoCrateBuilder roCrateBuilder) {
this.metadataContext = roCrateBuilder.metadataContext;
this.roCratePreview = roCrateBuilder.preview;
this.rootDataEntity = roCrateBuilder.rootDataEntity;
this.jsonDescriptor = roCrateBuilder.jsonDescriptor;
this.jsonDescriptor = roCrateBuilder.descriptorBuilder.build();
this.untrackedFiles = roCrateBuilder.untrackedFiles;
Validator defaultValidation = new Validator(new JsonSchemaValidation());
defaultValidation.validate(this);
}

@Override
public Optional<CrateVersion> getVersion() {
JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
if (conformsTo.isArray()) {
return StreamSupport.stream(conformsTo.spliterator(), false)
.filter(TreeNode::isObject)
.map(obj -> obj.path("@id").asText())
.map(CrateVersion::fromSpecUri)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
} else if (conformsTo.isObject()) {
return CrateVersion.fromSpecUri(conformsTo.get("@id").asText());
} else {
return Optional.empty();
}
}

@Override
public Collection<String> getProfiles() {
JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
if (conformsTo.isArray()) {
return StreamSupport.stream(conformsTo.spliterator(), false)
.filter(TreeNode::isObject)
.map(obj -> obj.path("@id").asText())
.filter(txt -> !CrateVersion.fromSpecUri(txt).isPresent())
.collect(Collectors.toSet());
} else {
return Collections.emptySet();
}
}

@Override
public String getJsonMetadata() {
ObjectMapper objectMapper = MyObjectMapper.getMapper();
Expand Down Expand Up @@ -201,28 +242,20 @@ public List<File> getUntrackedFiles() {
return this.untrackedFiles;
}

protected static ContextualEntity createDefaultJsonDescriptor() {
return new ContextualEntity.ContextualEntityBuilder()
.setId(ID)
.addType("CreativeWork")
.addIdProperty("about", "./")
.addIdProperty("conformsTo", RoCrate.RO_SPEC)
.build();
}

/**
* The inner class builder for the easier creation of a ROCrate.
*/
public static final class RoCrateBuilder {
public static class RoCrateBuilder {

CratePayload payload;
CratePreview preview;
CrateMetadataContext metadataContext;
ContextualEntity license;
RootDataEntity rootDataEntity;
ContextualEntity jsonDescriptor;
List<File> untrackedFiles;

JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder();

/**
* The default constructor of a builder.
*
Expand All @@ -237,7 +270,6 @@ public RoCrateBuilder(String name, String description) {
.addProperty("name", name)
.addProperty("description", description)
.build();
jsonDescriptor = RoCrate.createDefaultJsonDescriptor();
}

/**
Expand All @@ -250,7 +282,6 @@ public RoCrateBuilder() {
this.metadataContext = new RoCrateMetadataContext();
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
.build();
jsonDescriptor = RoCrate.createDefaultJsonDescriptor();
}

/**
Expand All @@ -263,8 +294,8 @@ public RoCrateBuilder(RoCrate crate) {
this.preview = crate.roCratePreview;
this.metadataContext = crate.metadataContext;
this.rootDataEntity = crate.rootDataEntity;
this.jsonDescriptor = crate.jsonDescriptor;
this.untrackedFiles = crate.untrackedFiles;
this.descriptorBuilder = new JsonDescriptor.Builder(crate);
}

/**
Expand Down Expand Up @@ -322,9 +353,67 @@ public RoCrateBuilder addUntrackedFile(File file) {
return this;
}

/**
* Returns a crate with the information from this builder.
*/
public RoCrate build() {
return new RoCrate(this);
}
}

/**
* Builder for Crates, supporting features which are not in a final
* specification yet.
*
* NOTE: This will change the specification version of your crate.
*
* We only add features we expect to be in the new specification in the
* end.
* In case a feature will not make it into the specification, we will mark it as
* deprecated and remove it in new major versions.
* If a feature is finalized, it will be added to the stable
* {@link RoCrateBuilder} and marked as deprecated in this class.
*/
public static class BuilderWithDraftFeatures extends RoCrateBuilder {

/**
* @see RoCrateBuilder#RoCrateBuilder()
*/
public BuilderWithDraftFeatures() {
super();
}

/**
* @see RoCrateBuilder#RoCrateBuilder(String, String)
*/
public BuilderWithDraftFeatures(String name, String description) {
super();
}

/**
* @see RoCrateBuilder#RoCrateBuilder(RoCrate)
*/
public BuilderWithDraftFeatures(RoCrate crate) {
super(crate);
this.descriptorBuilder = new JsonDescriptor.Builder(crate);
}

/**
* Indicate this crate also conforms to the given specification, in addition to
* the version this builder adds.
*
* This is helpful for profiles or other specifications the crate conforms to.
* Can be called multiple times to add more specifications.
*
* @param specification a specification or profile this crate conforms to.
* @return the builder
*/
public BuilderWithDraftFeatures alsoConformsTo(URI specification) {
descriptorBuilder
.addConformsTo(specification)
// usage of a draft feature results in draft version numbers of the crate
.setVersion(CrateVersion.LATEST_UNSTABLE);
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,12 @@ protected String getId() {
}

/**
* Setting the id property of the entity.
* Setting the id property of the entity, if the given value is not null.
*
* @param id the String representing the id.
* @return the generic builder.
*/
public T setId(String id) {
// TODO document why this has been implemented this way.
if (id != null) {
this.id = id;
}
Expand Down
Loading

0 comments on commit 53aebf7

Please sign in to comment.